Merge branch 'main' into guest-promotion

Conrad Irwin created

Change summary

Cargo.lock                                                        |   2 
crates/assistant/src/assistant_panel.rs                           |  34 
crates/auto_update/src/update_notification.rs                     |   4 
crates/call/src/call_settings.rs                                  |   3 
crates/client/src/user.rs                                         |   4 
crates/collab_ui/Cargo.toml                                       |   3 
crates/collab_ui/src/chat_panel.rs                                |  14 
crates/collab_ui/src/collab_panel.rs                              |  62 
crates/collab_ui/src/collab_panel/channel_modal.rs                |   4 
crates/collab_ui/src/collab_panel/contact_finder.rs               |   4 
crates/collab_ui/src/collab_titlebar_item.rs                      |  20 
crates/collab_ui/src/notification_panel.rs                        |  12 
crates/collab_ui/src/notifications.rs                             |  11 
crates/collab_ui/src/notifications/collab_notification.rs         |  52 
crates/collab_ui/src/notifications/incoming_call_notification.rs  |  52 
crates/collab_ui/src/notifications/project_shared_notification.rs |  74 
crates/collab_ui/src/notifications/stories.rs                     |   3 
crates/collab_ui/src/notifications/stories/collab_notification.rs |  50 
crates/copilot_ui/src/copilot_button.rs                           |  12 
crates/copilot_ui/src/sign_in.rs                                  |   4 
crates/diagnostics/src/diagnostics.rs                             |  12 
crates/diagnostics/src/items.rs                                   |  14 
crates/diagnostics/src/toolbar_controls.rs                        |   4 
crates/editor/src/display_map.rs                                  |   1 
crates/editor/src/editor.rs                                       |  32 
crates/editor/src/element.rs                                      | 101 
crates/editor/src/hover_popover.rs                                |  24 
crates/editor/src/scroll.rs                                       |  10 
crates/editor/src/test.rs                                         |   6 
crates/feedback/src/deploy_feedback_button.rs                     |   4 
crates/feedback/src/feedback_modal.rs                             |   2 
crates/gpui/docs/key_dispatch.md                                  |   4 
crates/gpui/src/action.rs                                         |  23 
crates/gpui/src/app.rs                                            |  41 
crates/gpui/src/app/async_context.rs                              |   6 
crates/gpui/src/app/entity_map.rs                                 |   5 
crates/gpui/src/app/test_context.rs                               |  82 
crates/gpui/src/element.rs                                        |  10 
crates/gpui/src/elements/img.rs                                   |   8 
crates/gpui/src/elements/overlay.rs                               |   2 
crates/gpui/src/executor.rs                                       |  44 
crates/gpui/src/gpui.rs                                           |   2 
crates/gpui/src/image_cache.rs                                    |   6 
crates/gpui/src/input.rs                                          |   2 
crates/gpui/src/interactive.rs                                    |  14 
crates/gpui/src/key_dispatch.rs                                   |  77 
crates/gpui/src/platform/mac/display.rs                           |   4 
crates/gpui/src/platform/test/display.rs                          |   2 
crates/gpui/src/platform/test/platform.rs                         |   2 
crates/gpui/src/shared_url.rs                                     |  25 
crates/gpui/src/subscription.rs                                   |   6 
crates/gpui/src/test.rs                                           |  28 
crates/gpui/src/window.rs                                         | 154 
crates/gpui/tests/action_macros.rs                                |  12 
crates/gpui_macros/src/gpui_macros.rs                             |  30 
crates/language/src/syntax_map/syntax_map_tests.rs                |  28 
crates/project_panel/src/project_panel.rs                         |  13 
crates/quick_action_bar/src/quick_action_bar.rs                   |  12 
crates/rope/src/rope.rs                                           |   6 
crates/rpc/src/notification.rs                                    |  55 
crates/search/src/buffer_search.rs                                |  29 
crates/search/src/project_search.rs                               |  27 
crates/search/src/search.rs                                       |   8 
crates/search/src/search_bar.rs                                   |   2 
crates/storybook/Cargo.toml                                       |   1 
crates/storybook/src/story_selector.rs                            |   4 
crates/terminal_view/src/terminal_element.rs                      |  12 
crates/terminal_view/src/terminal_panel.rs                        |  12 
crates/terminal_view/src/terminal_view.rs                         |   4 
crates/text/src/anchor.rs                                         |   2 
crates/theme/src/scale.rs                                         |   9 
crates/theme/src/settings.rs                                      |   2 
crates/theme/src/styles/colors.rs                                 |   5 
crates/theme/src/styles/status.rs                                 |   9 
crates/theme/src/theme.rs                                         |   8 
crates/theme/theme.md                                             |  15 
crates/ui/src/clickable.rs                                        |   2 
crates/ui/src/components/avatar.rs                                |  40 
crates/ui/src/components/button/button.rs                         | 180 
crates/ui/src/components/button/button_icon.rs                    |  12 
crates/ui/src/components/button/button_like.rs                    |   8 
crates/ui/src/components/button/icon_button.rs                    |  10 
crates/ui/src/components/checkbox.rs                              |   6 
crates/ui/src/components/context_menu.rs                          |  10 
crates/ui/src/components/disclosure.rs                            |   6 
crates/ui/src/components/divider.rs                               |   1 
crates/ui/src/components/icon.rs                                  | 202 
crates/ui/src/components/keybinding.rs                            |  44 
crates/ui/src/components/label/label.rs                           |  57 
crates/ui/src/components/label/label_like.rs                      |   1 
crates/ui/src/components/list/list_sub_header.rs                  |  15 
crates/ui/src/components/popover_menu.rs                          |   1 
crates/ui/src/components/right_click_menu.rs                      |   1 
crates/ui/src/components/stack.rs                                 |   8 
crates/ui/src/components/stories/button.rs                        |   6 
crates/ui/src/components/stories/icon.rs                          |   8 
crates/ui/src/components/stories/icon_button.rs                   |  18 
crates/ui/src/components/stories/list_header.rs                   |  14 
crates/ui/src/components/stories/list_item.rs                     |  26 
crates/ui/src/components/stories/tab.rs                           |   2 
crates/ui/src/components/stories/tab_bar.rs                       |  11 
crates/ui/src/disableable.rs                                      |   2 
crates/ui/src/fixed.rs                                            |   2 
crates/ui/src/prelude.rs                                          |   4 
crates/ui/src/selectable.rs                                       |   9 
crates/ui/src/styled_ext.rs                                       |  21 
crates/ui/src/styles/color.rs                                     |   1 
crates/ui/src/styles/elevation.rs                                 |   1 
crates/ui/src/styles/typography.rs                                |   1 
crates/ui/src/ui.rs                                               |   2 
crates/ui/src/utils.rs                                            |   2 
crates/ui/src/utils/format_distance.rs                            |  20 
crates/workspace/src/dock.rs                                      |   9 
crates/workspace/src/modal_layer.rs                               |   4 
crates/workspace/src/notifications.rs                             | 103 
crates/workspace/src/pane.rs                                      |  18 
crates/workspace/src/pane_group.rs                                |  68 
crates/workspace/src/shared_screen.rs                             |   4 
crates/workspace/src/toolbar.rs                                   |  83 
crates/workspace/src/workspace.rs                                 |  13 
crates/zed/src/zed.rs                                             |   1 
121 files changed, 1,644 insertions(+), 899 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1561,6 +1561,7 @@ dependencies = [
  "serde_json",
  "settings",
  "smallvec",
+ "story",
  "theme",
  "theme_selector",
  "time",
@@ -7449,6 +7450,7 @@ dependencies = [
  "backtrace-on-stack-overflow",
  "chrono",
  "clap 4.4.4",
+ "collab_ui",
  "dialoguer",
  "editor",
  "fuzzy",

crates/assistant/src/assistant_panel.rs 🔗

@@ -933,7 +933,7 @@ impl AssistantPanel {
     }
 
     fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
-        IconButton::new("hamburger_button", Icon::Menu)
+        IconButton::new("hamburger_button", IconName::Menu)
             .on_click(cx.listener(|this, _event, cx| {
                 if this.active_editor().is_some() {
                     this.set_active_editor_index(None, cx);
@@ -957,7 +957,7 @@ impl AssistantPanel {
     }
 
     fn render_split_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
-        IconButton::new("split_button", Icon::Snip)
+        IconButton::new("split_button", IconName::Snip)
             .on_click(cx.listener(|this, _event, cx| {
                 if let Some(active_editor) = this.active_editor() {
                     active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
@@ -968,7 +968,7 @@ impl AssistantPanel {
     }
 
     fn render_assist_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
-        IconButton::new("assist_button", Icon::MagicWand)
+        IconButton::new("assist_button", IconName::MagicWand)
             .on_click(cx.listener(|this, _event, cx| {
                 if let Some(active_editor) = this.active_editor() {
                     active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
@@ -979,7 +979,7 @@ impl AssistantPanel {
     }
 
     fn render_quote_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
-        IconButton::new("quote_button", Icon::Quote)
+        IconButton::new("quote_button", IconName::Quote)
             .on_click(cx.listener(|this, _event, cx| {
                 if let Some(workspace) = this.workspace.upgrade() {
                     cx.window_context().defer(move |cx| {
@@ -994,7 +994,7 @@ impl AssistantPanel {
     }
 
     fn render_plus_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
-        IconButton::new("plus_button", Icon::Plus)
+        IconButton::new("plus_button", IconName::Plus)
             .on_click(cx.listener(|this, _event, cx| {
                 this.new_conversation(cx);
             }))
@@ -1004,12 +1004,12 @@ impl AssistantPanel {
 
     fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
         let zoomed = self.zoomed;
-        IconButton::new("zoom_button", Icon::Maximize)
+        IconButton::new("zoom_button", IconName::Maximize)
             .on_click(cx.listener(|this, _event, cx| {
                 this.toggle_zoom(&ToggleZoom, cx);
             }))
             .selected(zoomed)
-            .selected_icon(Icon::Minimize)
+            .selected_icon(IconName::Minimize)
             .icon_size(IconSize::Small)
             .tooltip(move |cx| {
                 Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx)
@@ -1286,8 +1286,8 @@ impl Panel for AssistantPanel {
         }
     }
 
-    fn icon(&self, cx: &WindowContext) -> Option<Icon> {
-        Some(Icon::Ai).filter(|_| AssistantSettings::get_global(cx).button)
+    fn icon(&self, cx: &WindowContext) -> Option<IconName> {
+        Some(IconName::Ai).filter(|_| AssistantSettings::get_global(cx).button)
     }
 
     fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -2349,7 +2349,7 @@ impl ConversationEditor {
                                             div()
                                                 .id("error")
                                                 .tooltip(move |cx| Tooltip::text(error.clone(), cx))
-                                                .child(IconElement::new(Icon::XCircle)),
+                                                .child(Icon::new(IconName::XCircle)),
                                         )
                                     } else {
                                         None
@@ -2645,7 +2645,7 @@ impl Render for InlineAssistant {
                     .justify_center()
                     .w(measurements.gutter_width)
                     .child(
-                        IconButton::new("include_conversation", Icon::Ai)
+                        IconButton::new("include_conversation", IconName::Ai)
                             .on_click(cx.listener(|this, _, cx| {
                                 this.toggle_include_conversation(&ToggleIncludeConversation, cx)
                             }))
@@ -2660,7 +2660,7 @@ impl Render for InlineAssistant {
                     )
                     .children(if SemanticIndex::enabled(cx) {
                         Some(
-                            IconButton::new("retrieve_context", Icon::MagnifyingGlass)
+                            IconButton::new("retrieve_context", IconName::MagnifyingGlass)
                                 .on_click(cx.listener(|this, _, cx| {
                                     this.toggle_retrieve_context(&ToggleRetrieveContext, cx)
                                 }))
@@ -2682,7 +2682,7 @@ impl Render for InlineAssistant {
                             div()
                                 .id("error")
                                 .tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
-                                .child(IconElement::new(Icon::XCircle).color(Color::Error)),
+                                .child(Icon::new(IconName::XCircle).color(Color::Error)),
                         )
                     } else {
                         None
@@ -2957,7 +2957,7 @@ impl InlineAssistant {
                 div()
                     .id("error")
                     .tooltip(|cx| Tooltip::text("Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", cx))
-                    .child(IconElement::new(Icon::XCircle))
+                    .child(Icon::new(IconName::XCircle))
                     .into_any_element()
             ),
 
@@ -2965,7 +2965,7 @@ impl InlineAssistant {
                 div()
                     .id("error")
                     .tooltip(|cx| Tooltip::text("Not Indexed", cx))
-                    .child(IconElement::new(Icon::XCircle))
+                    .child(Icon::new(IconName::XCircle))
                     .into_any_element()
             ),
 
@@ -2996,7 +2996,7 @@ impl InlineAssistant {
                     div()
                         .id("update")
                         .tooltip(move |cx| Tooltip::text(status_text.clone(), cx))
-                        .child(IconElement::new(Icon::Update).color(Color::Info))
+                        .child(Icon::new(IconName::Update).color(Color::Info))
                         .into_any_element()
                 )
             }
@@ -3005,7 +3005,7 @@ impl InlineAssistant {
                 div()
                     .id("check")
                     .tooltip(|cx| Tooltip::text("Index up to date", cx))
-                    .child(IconElement::new(Icon::Check).color(Color::Success))
+                    .child(Icon::new(IconName::Check).color(Color::Success))
                     .into_any_element()
             ),
         }

crates/auto_update/src/update_notification.rs 🔗

@@ -4,7 +4,7 @@ use gpui::{
 };
 use menu::Cancel;
 use util::channel::ReleaseChannel;
-use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
+use workspace::ui::{h_stack, v_stack, Icon, IconName, Label, StyledExt};
 
 pub struct UpdateNotification {
     version: SemanticVersion,
@@ -30,7 +30,7 @@ impl Render for UpdateNotification {
                     .child(
                         div()
                             .id("cancel")
-                            .child(IconElement::new(Icon::Close))
+                            .child(Icon::new(IconName::Close))
                             .cursor_pointer()
                             .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))),
                     ),

crates/call/src/call_settings.rs 🔗

@@ -9,9 +9,12 @@ pub struct CallSettings {
     pub mute_on_join: bool,
 }
 
+/// Configuration of voice calls in Zed.
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
 pub struct CallSettingsContent {
     /// Whether the microphone should be muted when joining a channel or a call.
+    ///
+    /// Default: false
     pub mute_on_join: Option<bool>,
 }
 

crates/client/src/user.rs 🔗

@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result};
 use collections::{hash_map::Entry, HashMap, HashSet};
 use feature_flags::FeatureFlagAppExt;
 use futures::{channel::mpsc, Future, StreamExt};
-use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task};
+use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedUrl, Task};
 use postage::{sink::Sink, watch};
 use rpc::proto::{RequestMessage, UsersResponse};
 use std::sync::{Arc, Weak};
@@ -19,7 +19,7 @@ pub struct ParticipantIndex(pub u32);
 pub struct User {
     pub id: UserId,
     pub github_login: String,
-    pub avatar_uri: SharedString,
+    pub avatar_uri: SharedUrl,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]

crates/collab_ui/Cargo.toml 🔗

@@ -9,6 +9,8 @@ path = "src/collab_ui.rs"
 doctest = false
 
 [features]
+default = []
+stories = ["dep:story"]
 test-support = [
     "call/test-support",
     "client/test-support",
@@ -44,6 +46,7 @@ project = { path = "../project" }
 recent_projects = { path = "../recent_projects" }
 rpc = { path = "../rpc" }
 settings = { path = "../settings" }
+story = { path = "../story", optional = true }
 feature_flags = { path = "../feature_flags"}
 theme = { path = "../theme" }
 theme_selector = { path = "../theme_selector" }

crates/collab_ui/src/chat_panel.rs 🔗

@@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
 use std::sync::Arc;
 use time::{OffsetDateTime, UtcOffset};
-use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip};
+use ui::{prelude::*, Avatar, Button, IconButton, IconName, Label, TabBar, Tooltip};
 use util::{ResultExt, TryFutureExt};
 use workspace::{
     dock::{DockPosition, Panel, PanelEvent},
@@ -281,12 +281,12 @@ impl ChatPanel {
                                 )),
                         )
                         .end_child(
-                            IconButton::new("notes", Icon::File)
+                            IconButton::new("notes", IconName::File)
                                 .on_click(cx.listener(Self::open_notes))
                                 .tooltip(|cx| Tooltip::text("Open notes", cx)),
                         )
                         .end_child(
-                            IconButton::new("call", Icon::AudioOn)
+                            IconButton::new("call", IconName::AudioOn)
                                 .on_click(cx.listener(Self::join_call))
                                 .tooltip(|cx| Tooltip::text("Join call", cx)),
                         ),
@@ -395,7 +395,7 @@ impl ChatPanel {
                     .w_8()
                     .visible_on_hover("")
                     .children(message_id_to_remove.map(|message_id| {
-                        IconButton::new(("remove", message_id), Icon::XCircle).on_click(
+                        IconButton::new(("remove", message_id), IconName::XCircle).on_click(
                             cx.listener(move |this, _, cx| {
                                 this.remove_message(message_id, cx);
                             }),
@@ -429,7 +429,7 @@ impl ChatPanel {
                 Button::new("sign-in", "Sign in")
                     .style(ButtonStyle::Filled)
                     .icon_color(Color::Muted)
-                    .icon(Icon::Github)
+                    .icon(IconName::Github)
                     .icon_position(IconPosition::Start)
                     .full_width()
                     .on_click(cx.listener(move |this, _, cx| {
@@ -622,12 +622,12 @@ impl Panel for ChatPanel {
         "ChatPanel"
     }
 
-    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
+    fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
         if !is_channels_feature_enabled(cx) {
             return None;
         }
 
-        Some(ui::Icon::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)
+        Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)
     }
 
     fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {

crates/collab_ui/src/collab_panel.rs 🔗

@@ -31,7 +31,7 @@ use smallvec::SmallVec;
 use std::{mem, sync::Arc};
 use theme::{ActiveTheme, ThemeSettings};
 use ui::{
-    prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, Label,
+    prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconName, IconSize, Label,
     ListHeader, ListItem, Tooltip,
 };
 use util::{maybe, ResultExt, TryFutureExt};
@@ -844,7 +844,7 @@ impl CollabPanel {
             .end_slot(if is_pending {
                 Label::new("Calling").color(Color::Muted).into_any_element()
             } else if is_current_user {
-                IconButton::new("leave-call", Icon::Exit)
+                IconButton::new("leave-call", IconName::Exit)
                     .style(ButtonStyle::Subtle)
                     .on_click(move |_, cx| Self::leave_call(cx))
                     .tooltip(|cx| Tooltip::text("Leave Call", cx))
@@ -903,7 +903,7 @@ impl CollabPanel {
                 h_stack()
                     .gap_1()
                     .child(render_tree_branch(is_last, false, cx))
-                    .child(IconButton::new(0, Icon::Folder)),
+                    .child(IconButton::new(0, IconName::Folder)),
             )
             .child(Label::new(project_name.clone()))
             .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx))
@@ -924,7 +924,7 @@ impl CollabPanel {
                 h_stack()
                     .gap_1()
                     .child(render_tree_branch(is_last, false, cx))
-                    .child(IconButton::new(0, Icon::Screen)),
+                    .child(IconButton::new(0, IconName::Screen)),
             )
             .child(Label::new("Screen"))
             .when_some(peer_id, |this, _| {
@@ -965,7 +965,7 @@ impl CollabPanel {
                 h_stack()
                     .gap_1()
                     .child(render_tree_branch(false, true, cx))
-                    .child(IconButton::new(0, Icon::File)),
+                    .child(IconButton::new(0, IconName::File)),
             )
             .child(div().h_7().w_full().child(Label::new("notes")))
             .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx))
@@ -986,7 +986,7 @@ impl CollabPanel {
                 h_stack()
                     .gap_1()
                     .child(render_tree_branch(false, false, cx))
-                    .child(IconButton::new(0, Icon::MessageBubbles)),
+                    .child(IconButton::new(0, IconName::MessageBubbles)),
             )
             .child(Label::new("chat"))
             .tooltip(move |cx| Tooltip::text("Open Chat", cx))
@@ -1757,7 +1757,7 @@ impl CollabPanel {
                     .child(
                         Button::new("sign_in", "Sign in")
                             .icon_color(Color::Muted)
-                            .icon(Icon::Github)
+                            .icon(IconName::Github)
                             .icon_position(IconPosition::Start)
                             .style(ButtonStyle::Filled)
                             .full_width()
@@ -1949,7 +1949,7 @@ impl CollabPanel {
         let button = match section {
             Section::ActiveCall => channel_link.map(|channel_link| {
                 let channel_link_copy = channel_link.clone();
-                IconButton::new("channel-link", Icon::Copy)
+                IconButton::new("channel-link", IconName::Copy)
                     .icon_size(IconSize::Small)
                     .size(ButtonSize::None)
                     .visible_on_hover("section-header")
@@ -1961,13 +1961,13 @@ impl CollabPanel {
                     .into_any_element()
             }),
             Section::Contacts => Some(
-                IconButton::new("add-contact", Icon::Plus)
+                IconButton::new("add-contact", IconName::Plus)
                     .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
                     .tooltip(|cx| Tooltip::text("Search for new contact", cx))
                     .into_any_element(),
             ),
             Section::Channels => Some(
-                IconButton::new("add-channel", Icon::Plus)
+                IconButton::new("add-channel", IconName::Plus)
                     .on_click(cx.listener(|this, _, cx| this.new_root_channel(cx)))
                     .tooltip(|cx| Tooltip::text("Create a channel", cx))
                     .into_any_element(),
@@ -2038,7 +2038,7 @@ impl CollabPanel {
                         })
                         .when(!calling, |el| {
                             el.child(
-                                IconButton::new("remove_contact", Icon::Close)
+                                IconButton::new("remove_contact", IconName::Close)
                                     .icon_color(Color::Muted)
                                     .visible_on_hover("")
                                     .tooltip(|cx| Tooltip::text("Remove Contact", cx))
@@ -2099,13 +2099,13 @@ impl CollabPanel {
 
         let controls = if is_incoming {
             vec![
-                IconButton::new("decline-contact", Icon::Close)
+                IconButton::new("decline-contact", IconName::Close)
                     .on_click(cx.listener(move |this, _, cx| {
                         this.respond_to_contact_request(user_id, false, cx);
                     }))
                     .icon_color(color)
                     .tooltip(|cx| Tooltip::text("Decline invite", cx)),
-                IconButton::new("accept-contact", Icon::Check)
+                IconButton::new("accept-contact", IconName::Check)
                     .on_click(cx.listener(move |this, _, cx| {
                         this.respond_to_contact_request(user_id, true, cx);
                     }))
@@ -2114,7 +2114,7 @@ impl CollabPanel {
             ]
         } else {
             let github_login = github_login.clone();
-            vec![IconButton::new("remove_contact", Icon::Close)
+            vec![IconButton::new("remove_contact", IconName::Close)
                 .on_click(cx.listener(move |this, _, cx| {
                     this.remove_contact(user_id, &github_login, cx);
                 }))
@@ -2154,13 +2154,13 @@ impl CollabPanel {
         };
 
         let controls = [
-            IconButton::new("reject-invite", Icon::Close)
+            IconButton::new("reject-invite", IconName::Close)
                 .on_click(cx.listener(move |this, _, cx| {
                     this.respond_to_channel_invite(channel_id, false, cx);
                 }))
                 .icon_color(color)
                 .tooltip(|cx| Tooltip::text("Decline invite", cx)),
-            IconButton::new("accept-invite", Icon::Check)
+            IconButton::new("accept-invite", IconName::Check)
                 .on_click(cx.listener(move |this, _, cx| {
                     this.respond_to_channel_invite(channel_id, true, cx);
                 }))
@@ -2178,7 +2178,7 @@ impl CollabPanel {
                     .child(h_stack().children(controls)),
             )
             .start_slot(
-                IconElement::new(Icon::Hash)
+                Icon::new(IconName::Hash)
                     .size(IconSize::Small)
                     .color(Color::Muted),
             )
@@ -2190,7 +2190,7 @@ impl CollabPanel {
         cx: &mut ViewContext<Self>,
     ) -> ListItem {
         ListItem::new("contact-placeholder")
-            .child(IconElement::new(Icon::Plus))
+            .child(Icon::new(IconName::Plus))
             .child(Label::new("Add a Contact"))
             .selected(is_selected)
             .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
@@ -2274,7 +2274,7 @@ impl CollabPanel {
         };
 
         let messages_button = |cx: &mut ViewContext<Self>| {
-            IconButton::new("channel_chat", Icon::MessageBubbles)
+            IconButton::new("channel_chat", IconName::MessageBubbles)
                 .icon_size(IconSize::Small)
                 .icon_color(if has_messages_notification {
                     Color::Default
@@ -2286,7 +2286,7 @@ impl CollabPanel {
         };
 
         let notes_button = |cx: &mut ViewContext<Self>| {
-            IconButton::new("channel_notes", Icon::File)
+            IconButton::new("channel_notes", IconName::File)
                 .icon_size(IconSize::Small)
                 .icon_color(if has_notes_notification {
                     Color::Default
@@ -2343,9 +2343,13 @@ impl CollabPanel {
                         },
                     ))
                     .start_slot(
-                        IconElement::new(if is_public { Icon::Public } else { Icon::Hash })
-                            .size(IconSize::Small)
-                            .color(Color::Muted),
+                        Icon::new(if is_public {
+                            IconName::Public
+                        } else {
+                            IconName::Hash
+                        })
+                        .size(IconSize::Small)
+                        .color(Color::Muted),
                     )
                     .child(
                         h_stack()
@@ -2414,7 +2418,7 @@ impl CollabPanel {
             .indent_level(depth + 1)
             .indent_step_size(px(20.))
             .start_slot(
-                IconElement::new(Icon::Hash)
+                Icon::new(IconName::Hash)
                     .size(IconSize::Small)
                     .color(Color::Muted),
             );
@@ -2528,10 +2532,10 @@ impl Panel for CollabPanel {
         cx.notify();
     }
 
-    fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::Icon> {
+    fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::IconName> {
         CollaborationPanelSettings::get_global(cx)
             .button
-            .then(|| ui::Icon::Collab)
+            .then(|| ui::IconName::Collab)
     }
 
     fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -2669,11 +2673,11 @@ impl Render for DraggedChannelView {
             .p_1()
             .gap_1()
             .child(
-                IconElement::new(
+                Icon::new(
                     if self.channel.visibility == proto::ChannelVisibility::Public {
-                        Icon::Public
+                        IconName::Public
                     } else {
-                        Icon::Hash
+                        IconName::Hash
                     },
                 )
                 .size(IconSize::Small)

crates/collab_ui/src/collab_panel/channel_modal.rs 🔗

@@ -168,7 +168,7 @@ impl Render for ChannelModal {
                             .w_px()
                             .flex_1()
                             .gap_1()
-                            .child(IconElement::new(Icon::Hash).size(IconSize::Medium))
+                            .child(Icon::new(IconName::Hash).size(IconSize::Medium))
                             .child(Label::new(channel_name)),
                     )
                     .child(
@@ -406,7 +406,7 @@ impl PickerDelegate for ChannelModalDelegate {
                                 Some(ChannelRole::Guest) => Some(Label::new("Guest")),
                                 _ => None,
                             })
-                            .child(IconButton::new("ellipsis", Icon::Ellipsis))
+                            .child(IconButton::new("ellipsis", IconName::Ellipsis))
                             .children(
                                 if let (Some((menu, _)), true) = (&self.context_menu, selected) {
                                     Some(

crates/collab_ui/src/collab_panel/contact_finder.rs 🔗

@@ -155,9 +155,7 @@ impl PickerDelegate for ContactFinderDelegate {
                 .selected(selected)
                 .start_slot(Avatar::new(user.avatar_uri.clone()))
                 .child(Label::new(user.github_login.clone()))
-                .end_slot::<IconElement>(
-                    icon_path.map(|icon_path| IconElement::from_path(icon_path)),
-                ),
+                .end_slot::<Icon>(icon_path.map(|icon_path| Icon::from_path(icon_path))),
         )
     }
 }

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -15,7 +15,7 @@ use std::sync::Arc;
 use theme::{ActiveTheme, PlayerColors};
 use ui::{
     h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
-    IconButton, IconElement, TintColor, Tooltip,
+    IconButton, IconName, TintColor, Tooltip,
 };
 use util::ResultExt;
 use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
@@ -213,7 +213,7 @@ impl Render for CollabTitlebarItem {
                         .child(
                             div()
                                 .child(
-                                    IconButton::new("leave-call", ui::Icon::Exit)
+                                    IconButton::new("leave-call", ui::IconName::Exit)
                                         .style(ButtonStyle::Subtle)
                                         .tooltip(|cx| Tooltip::text("Leave call", cx))
                                         .icon_size(IconSize::Small)
@@ -230,9 +230,9 @@ impl Render for CollabTitlebarItem {
                                 IconButton::new(
                                     "mute-microphone",
                                     if is_muted {
-                                        ui::Icon::MicMute
+                                        ui::IconName::MicMute
                                     } else {
-                                        ui::Icon::Mic
+                                        ui::IconName::Mic
                                     },
                                 )
                                 .tooltip(move |cx| {
@@ -256,9 +256,9 @@ impl Render for CollabTitlebarItem {
                             IconButton::new(
                                 "mute-sound",
                                 if is_deafened {
-                                    ui::Icon::AudioOff
+                                    ui::IconName::AudioOff
                                 } else {
-                                    ui::Icon::AudioOn
+                                    ui::IconName::AudioOn
                                 },
                             )
                             .style(ButtonStyle::Subtle)
@@ -281,7 +281,7 @@ impl Render for CollabTitlebarItem {
                         )
                         .when(!read_only, |this| {
                             this.child(
-                                IconButton::new("screen-share", ui::Icon::Screen)
+                                IconButton::new("screen-share", ui::IconName::Screen)
                                     .style(ButtonStyle::Subtle)
                                     .icon_size(IconSize::Small)
                                     .selected(is_screen_sharing)
@@ -573,7 +573,7 @@ impl CollabTitlebarItem {
             | client::Status::ReconnectionError { .. } => Some(
                 div()
                     .id("disconnected")
-                    .child(IconElement::new(Icon::Disconnected).size(IconSize::Small))
+                    .child(Icon::new(IconName::Disconnected).size(IconSize::Small))
                     .tooltip(|cx| Tooltip::text("Disconnected", cx))
                     .into_any_element(),
             ),
@@ -643,7 +643,7 @@ impl CollabTitlebarItem {
                             h_stack()
                                 .gap_0p5()
                                 .child(Avatar::new(user.avatar_uri.clone()))
-                                .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
+                                .child(Icon::new(IconName::ChevronDown).color(Color::Muted)),
                         )
                         .style(ButtonStyle::Subtle)
                         .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
@@ -665,7 +665,7 @@ impl CollabTitlebarItem {
                         .child(
                             h_stack()
                                 .gap_0p5()
-                                .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
+                                .child(Icon::new(IconName::ChevronDown).color(Color::Muted)),
                         )
                         .style(ButtonStyle::Subtle)
                         .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),

crates/collab_ui/src/notification_panel.rs 🔗

@@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
 use std::{sync::Arc, time::Duration};
 use time::{OffsetDateTime, UtcOffset};
-use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconElement, Label};
+use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconName, Label};
 use util::{ResultExt, TryFutureExt};
 use workspace::{
     dock::{DockPosition, Panel, PanelEvent},
@@ -553,7 +553,7 @@ impl Render for NotificationPanel {
                     .border_b_1()
                     .border_color(cx.theme().colors().border)
                     .child(Label::new("Notifications"))
-                    .child(IconElement::new(Icon::Envelope)),
+                    .child(Icon::new(IconName::Envelope)),
             )
             .map(|this| {
                 if self.client.user_id().is_none() {
@@ -564,7 +564,7 @@ impl Render for NotificationPanel {
                             .child(
                                 Button::new("sign_in_prompt_button", "Sign in")
                                     .icon_color(Color::Muted)
-                                    .icon(Icon::Github)
+                                    .icon(IconName::Github)
                                     .icon_position(IconPosition::Start)
                                     .style(ButtonStyle::Filled)
                                     .full_width()
@@ -655,10 +655,10 @@ impl Panel for NotificationPanel {
         }
     }
 
-    fn icon(&self, cx: &gpui::WindowContext) -> Option<Icon> {
+    fn icon(&self, cx: &gpui::WindowContext) -> Option<IconName> {
         (NotificationPanelSettings::get_global(cx).button
             && self.notification_store.read(cx).notification_count() > 0)
-            .then(|| Icon::Bell)
+            .then(|| IconName::Bell)
     }
 
     fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -716,7 +716,7 @@ impl Render for NotificationToast {
             .children(user.map(|user| Avatar::new(user.avatar_uri.clone())))
             .child(Label::new(self.text.clone()))
             .child(
-                IconButton::new("close", Icon::Close)
+                IconButton::new("close", IconName::Close)
                     .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
             )
             .on_click(cx.listener(|this, _, cx| {

crates/collab_ui/src/notifications.rs 🔗

@@ -1,9 +1,16 @@
+mod collab_notification;
+pub mod incoming_call_notification;
+pub mod project_shared_notification;
+
+#[cfg(feature = "stories")]
+mod stories;
+
 use gpui::AppContext;
 use std::sync::Arc;
 use workspace::AppState;
 
-pub mod incoming_call_notification;
-pub mod project_shared_notification;
+#[cfg(feature = "stories")]
+pub use stories::*;
 
 pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
     incoming_call_notification::init(app_state, cx);

crates/collab_ui/src/notifications/collab_notification.rs 🔗

@@ -0,0 +1,52 @@
+use gpui::{img, prelude::*, AnyElement, SharedUrl};
+use smallvec::SmallVec;
+use ui::prelude::*;
+
+#[derive(IntoElement)]
+pub struct CollabNotification {
+    avatar_uri: SharedUrl,
+    accept_button: Button,
+    dismiss_button: Button,
+    children: SmallVec<[AnyElement; 2]>,
+}
+
+impl CollabNotification {
+    pub fn new(
+        avatar_uri: impl Into<SharedUrl>,
+        accept_button: Button,
+        dismiss_button: Button,
+    ) -> Self {
+        Self {
+            avatar_uri: avatar_uri.into(),
+            accept_button,
+            dismiss_button,
+            children: SmallVec::new(),
+        }
+    }
+}
+
+impl ParentElement for CollabNotification {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
+    }
+}
+
+impl RenderOnce for CollabNotification {
+    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+        h_stack()
+            .text_ui()
+            .justify_between()
+            .size_full()
+            .overflow_hidden()
+            .elevation_3(cx)
+            .p_2()
+            .gap_2()
+            .child(img(self.avatar_uri).w_12().h_12().rounded_full())
+            .child(v_stack().overflow_hidden().children(self.children))
+            .child(
+                v_stack()
+                    .child(self.accept_button)
+                    .child(self.dismiss_button),
+            )
+    }
+}

crates/collab_ui/src/notifications/incoming_call_notification.rs 🔗

@@ -1,15 +1,12 @@
 use crate::notification_window_options;
+use crate::notifications::collab_notification::CollabNotification;
 use call::{ActiveCall, IncomingCall};
 use futures::StreamExt;
-use gpui::{
-    img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext,
-    VisualContext as _, WindowHandle,
-};
+use gpui::{prelude::*, AppContext, WindowHandle};
 use settings::Settings;
 use std::sync::{Arc, Weak};
 use theme::ThemeSettings;
-use ui::prelude::*;
-use ui::{h_stack, v_stack, Button, Label};
+use ui::{prelude::*, Button, Label};
 use util::ResultExt;
 use workspace::AppState;
 
@@ -31,8 +28,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
             if let Some(incoming_call) = incoming_call {
                 let unique_screens = cx.update(|cx| cx.displays()).unwrap();
                 let window_size = gpui::Size {
-                    width: px(380.),
-                    height: px(64.),
+                    width: px(400.),
+                    height: px(72.),
                 };
 
                 for screen in unique_screens {
@@ -129,35 +126,22 @@ impl Render for IncomingCallNotification {
 
         cx.set_rem_size(ui_font_size);
 
-        h_stack()
-            .font(ui_font)
-            .text_ui()
-            .justify_between()
-            .size_full()
-            .overflow_hidden()
-            .elevation_3(cx)
-            .p_2()
-            .gap_2()
-            .child(
-                img(self.state.call.calling_user.avatar_uri.clone())
-                    .w_12()
-                    .h_12()
-                    .rounded_full(),
+        div().size_full().font(ui_font).child(
+            CollabNotification::new(
+                self.state.call.calling_user.avatar_uri.clone(),
+                Button::new("accept", "Accept").on_click({
+                    let state = self.state.clone();
+                    move |_, cx| state.respond(true, cx)
+                }),
+                Button::new("decline", "Decline").on_click({
+                    let state = self.state.clone();
+                    move |_, cx| state.respond(false, cx)
+                }),
             )
             .child(v_stack().overflow_hidden().child(Label::new(format!(
                 "{} is sharing a project in Zed",
                 self.state.call.calling_user.github_login
-            ))))
-            .child(
-                v_stack()
-                    .child(Button::new("accept", "Accept").render(cx).on_click({
-                        let state = self.state.clone();
-                        move |_, cx| state.respond(true, cx)
-                    }))
-                    .child(Button::new("decline", "Decline").render(cx).on_click({
-                        let state = self.state.clone();
-                        move |_, cx| state.respond(false, cx)
-                    })),
-            )
+            )))),
+        )
     }
 }

crates/collab_ui/src/notifications/project_shared_notification.rs 🔗

@@ -1,12 +1,13 @@
 use crate::notification_window_options;
+use crate::notifications::collab_notification::CollabNotification;
 use call::{room, ActiveCall};
 use client::User;
 use collections::HashMap;
-use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext};
+use gpui::{AppContext, Size};
 use settings::Settings;
 use std::sync::{Arc, Weak};
 use theme::ThemeSettings;
-use ui::{h_stack, prelude::*, v_stack, Button, Label};
+use ui::{prelude::*, Button, Label};
 use workspace::AppState;
 
 pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
@@ -130,51 +131,30 @@ impl Render for ProjectSharedNotification {
 
         cx.set_rem_size(ui_font_size);
 
-        h_stack()
-            .font(ui_font)
-            .text_ui()
-            .justify_between()
-            .size_full()
-            .overflow_hidden()
-            .elevation_3(cx)
-            .p_2()
-            .gap_2()
-            .child(
-                img(self.owner.avatar_uri.clone())
-                    .w_12()
-                    .h_12()
-                    .rounded_full(),
-            )
-            .child(
-                v_stack()
-                    .overflow_hidden()
-                    .child(Label::new(self.owner.github_login.clone()))
-                    .child(Label::new(format!(
-                        "is sharing a project in Zed{}",
-                        if self.worktree_root_names.is_empty() {
-                            ""
-                        } else {
-                            ":"
-                        }
-                    )))
-                    .children(if self.worktree_root_names.is_empty() {
-                        None
-                    } else {
-                        Some(Label::new(self.worktree_root_names.join(", ")))
-                    }),
-            )
-            .child(
-                v_stack()
-                    .child(Button::new("open", "Open").on_click(cx.listener(
-                        move |this, _event, cx| {
-                            this.join(cx);
-                        },
-                    )))
-                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
-                        move |this, _event, cx| {
-                            this.dismiss(cx);
-                        },
-                    ))),
+        div().size_full().font(ui_font).child(
+            CollabNotification::new(
+                self.owner.avatar_uri.clone(),
+                Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
+                    this.join(cx);
+                })),
+                Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| {
+                    this.dismiss(cx);
+                })),
             )
+            .child(Label::new(self.owner.github_login.clone()))
+            .child(Label::new(format!(
+                "is sharing a project in Zed{}",
+                if self.worktree_root_names.is_empty() {
+                    ""
+                } else {
+                    ":"
+                }
+            )))
+            .children(if self.worktree_root_names.is_empty() {
+                None
+            } else {
+                Some(Label::new(self.worktree_root_names.join(", ")))
+            }),
+        )
     }
 }

crates/collab_ui/src/notifications/stories/collab_notification.rs 🔗

@@ -0,0 +1,50 @@
+use gpui::prelude::*;
+use story::{StoryContainer, StoryItem, StorySection};
+use ui::prelude::*;
+
+use crate::notifications::collab_notification::CollabNotification;
+
+pub struct CollabNotificationStory;
+
+impl Render for CollabNotificationStory {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let window_container = |width, height| div().w(px(width)).h(px(height));
+
+        StoryContainer::new(
+            "CollabNotification Story",
+            "crates/collab_ui/src/notifications/stories/collab_notification.rs",
+        )
+        .child(
+            StorySection::new().child(StoryItem::new(
+                "Incoming Call Notification",
+                window_container(400., 72.).child(
+                    CollabNotification::new(
+                        "https://avatars.githubusercontent.com/u/1486634?v=4",
+                        Button::new("accept", "Accept"),
+                        Button::new("decline", "Decline"),
+                    )
+                    .child(
+                        v_stack()
+                            .overflow_hidden()
+                            .child(Label::new("maxdeviant is sharing a project in Zed")),
+                    ),
+                ),
+            )),
+        )
+        .child(
+            StorySection::new().child(StoryItem::new(
+                "Project Shared Notification",
+                window_container(400., 72.).child(
+                    CollabNotification::new(
+                        "https://avatars.githubusercontent.com/u/1714999?v=4",
+                        Button::new("open", "Open"),
+                        Button::new("dismiss", "Dismiss"),
+                    )
+                    .child(Label::new("iamnbutler"))
+                    .child(Label::new("is sharing a project in Zed:"))
+                    .child(Label::new("zed")),
+                ),
+            )),
+        )
+    }
+}

crates/copilot_ui/src/copilot_button.rs 🔗

@@ -17,7 +17,9 @@ use util::{paths, ResultExt};
 use workspace::{
     create_and_open_local_file,
     item::ItemHandle,
-    ui::{popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, IconSize, Tooltip},
+    ui::{
+        popover_menu, ButtonCommon, Clickable, ContextMenu, IconButton, IconName, IconSize, Tooltip,
+    },
     StatusItemView, Toast, Workspace,
 };
 use zed_actions::OpenBrowser;
@@ -51,15 +53,15 @@ impl Render for CopilotButton {
             .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
 
         let icon = match status {
-            Status::Error(_) => Icon::CopilotError,
+            Status::Error(_) => IconName::CopilotError,
             Status::Authorized => {
                 if enabled {
-                    Icon::Copilot
+                    IconName::Copilot
                 } else {
-                    Icon::CopilotDisabled
+                    IconName::CopilotDisabled
                 }
             }
-            _ => Icon::CopilotInit,
+            _ => IconName::CopilotInit,
         };
 
         if let Status::Error(e) = status {

crates/copilot_ui/src/sign_in.rs 🔗

@@ -4,7 +4,7 @@ use gpui::{
     FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled,
     Subscription, ViewContext,
 };
-use ui::{prelude::*, Button, Icon, Label};
+use ui::{prelude::*, Button, IconName, Label};
 use workspace::ModalView;
 
 const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
@@ -175,7 +175,7 @@ impl Render for CopilotCodeVerification {
                     .w_32()
                     .h_16()
                     .flex_none()
-                    .path(Icon::ZedXCopilot.path())
+                    .path(IconName::ZedXCopilot.path())
                     .text_color(cx.theme().colors().icon),
             )
             .child(prompt)

crates/diagnostics/src/diagnostics.rs 🔗

@@ -36,7 +36,7 @@ use std::{
 };
 use theme::ActiveTheme;
 pub use toolbar_controls::ToolbarControls;
-use ui::{h_stack, prelude::*, Icon, IconElement, Label};
+use ui::{h_stack, prelude::*, Icon, IconName, Label};
 use util::TryFutureExt;
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
@@ -660,7 +660,7 @@ impl Item for ProjectDiagnosticsEditor {
                     then.child(
                         h_stack()
                             .gap_1()
-                            .child(IconElement::new(Icon::XCircle).color(Color::Error))
+                            .child(Icon::new(IconName::XCircle).color(Color::Error))
                             .child(Label::new(self.summary.error_count.to_string()).color(
                                 if selected {
                                     Color::Default
@@ -674,9 +674,7 @@ impl Item for ProjectDiagnosticsEditor {
                     then.child(
                         h_stack()
                             .gap_1()
-                            .child(
-                                IconElement::new(Icon::ExclamationTriangle).color(Color::Warning),
-                            )
+                            .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning))
                             .child(Label::new(self.summary.warning_count.to_string()).color(
                                 if selected {
                                     Color::Default
@@ -816,10 +814,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
                                 .flex_none()
                                 .map(|icon| {
                                     if diagnostic.severity == DiagnosticSeverity::ERROR {
-                                        icon.path(Icon::XCircle.path())
+                                        icon.path(IconName::XCircle.path())
                                             .text_color(Color::Error.color(cx))
                                     } else {
-                                        icon.path(Icon::ExclamationTriangle.path())
+                                        icon.path(IconName::ExclamationTriangle.path())
                                             .text_color(Color::Warning.color(cx))
                                     }
                                 }),

crates/diagnostics/src/items.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
 };
 use language::Diagnostic;
 use lsp::LanguageServerId;
-use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip};
+use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
 use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
 
 use crate::{Deploy, ProjectDiagnosticsEditor};
@@ -25,7 +25,7 @@ impl Render for DiagnosticIndicator {
         let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
             (0, 0) => h_stack().map(|this| {
                 this.child(
-                    IconElement::new(Icon::Check)
+                    Icon::new(IconName::Check)
                         .size(IconSize::Small)
                         .color(Color::Default),
                 )
@@ -33,7 +33,7 @@ impl Render for DiagnosticIndicator {
             (0, warning_count) => h_stack()
                 .gap_1()
                 .child(
-                    IconElement::new(Icon::ExclamationTriangle)
+                    Icon::new(IconName::ExclamationTriangle)
                         .size(IconSize::Small)
                         .color(Color::Warning),
                 )
@@ -41,7 +41,7 @@ impl Render for DiagnosticIndicator {
             (error_count, 0) => h_stack()
                 .gap_1()
                 .child(
-                    IconElement::new(Icon::XCircle)
+                    Icon::new(IconName::XCircle)
                         .size(IconSize::Small)
                         .color(Color::Error),
                 )
@@ -49,13 +49,13 @@ impl Render for DiagnosticIndicator {
             (error_count, warning_count) => h_stack()
                 .gap_1()
                 .child(
-                    IconElement::new(Icon::XCircle)
+                    Icon::new(IconName::XCircle)
                         .size(IconSize::Small)
                         .color(Color::Error),
                 )
                 .child(Label::new(error_count.to_string()).size(LabelSize::Small))
                 .child(
-                    IconElement::new(Icon::ExclamationTriangle)
+                    Icon::new(IconName::ExclamationTriangle)
                         .size(IconSize::Small)
                         .color(Color::Warning),
                 )
@@ -66,7 +66,7 @@ impl Render for DiagnosticIndicator {
             Some(
                 h_stack()
                     .gap_2()
-                    .child(IconElement::new(Icon::ArrowCircle).size(IconSize::Small))
+                    .child(Icon::new(IconName::ArrowCircle).size(IconSize::Small))
                     .child(
                         Label::new("Checking…")
                             .size(LabelSize::Small)

crates/diagnostics/src/toolbar_controls.rs 🔗

@@ -1,7 +1,7 @@
 use crate::ProjectDiagnosticsEditor;
 use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
 use ui::prelude::*;
-use ui::{Icon, IconButton, Tooltip};
+use ui::{IconButton, IconName, Tooltip};
 use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 
 pub struct ToolbarControls {
@@ -24,7 +24,7 @@ impl Render for ToolbarControls {
         };
 
         div().child(
-            IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
+            IconButton::new("toggle-warnings", IconName::ExclamationTriangle)
                 .tooltip(move |cx| Tooltip::text(tooltip, cx))
                 .on_click(cx.listener(|this, _, cx| {
                     if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {

crates/editor/src/display_map.rs 🔗

@@ -1015,7 +1015,6 @@ pub mod tests {
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
             .unwrap_or(10);
 
-        let _test_platform = &cx.test_platform;
         let mut tab_size = rng.gen_range(1..=4);
         let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
         let excerpt_header_height = rng.gen_range(1..=5);

crates/editor/src/editor.rs 🔗

@@ -99,8 +99,8 @@ use sum_tree::TreeMap;
 use text::{OffsetUtf16, Rope};
 use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};
 use ui::{
-    h_stack, prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, ListItem, Popover,
-    Tooltip,
+    h_stack, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem,
+    Popover, Tooltip,
 };
 use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
 use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};
@@ -507,7 +507,7 @@ pub enum SoftWrap {
     Column(u32),
 }
 
-#[derive(Clone, Default)]
+#[derive(Clone)]
 pub struct EditorStyle {
     pub background: Hsla,
     pub local_player: PlayerColor,
@@ -519,6 +519,24 @@ pub struct EditorStyle {
     pub suggestions_style: HighlightStyle,
 }
 
+impl Default for EditorStyle {
+    fn default() -> Self {
+        Self {
+            background: Hsla::default(),
+            local_player: PlayerColor::default(),
+            text: TextStyle::default(),
+            scrollbar_width: Pixels::default(),
+            syntax: Default::default(),
+            // HACK: Status colors don't have a real default.
+            // We should look into removing the status colors from the editor
+            // style and retrieve them directly from the theme.
+            status: StatusColors::dark(),
+            inlays_style: HighlightStyle::default(),
+            suggestions_style: HighlightStyle::default(),
+        }
+    }
+}
+
 type CompletionId = usize;
 
 // type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
@@ -4223,7 +4241,7 @@ impl Editor {
     ) -> Option<IconButton> {
         if self.available_code_actions.is_some() {
             Some(
-                IconButton::new("code_actions_indicator", ui::Icon::Bolt)
+                IconButton::new("code_actions_indicator", ui::IconName::Bolt)
                     .icon_size(IconSize::Small)
                     .icon_color(Color::Muted)
                     .selected(is_active)
@@ -4257,7 +4275,7 @@ impl Editor {
                 fold_data
                     .map(|(fold_status, buffer_row, active)| {
                         (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
-                            IconButton::new(ix as usize, ui::Icon::ChevronDown)
+                            IconButton::new(ix as usize, ui::IconName::ChevronDown)
                                 .on_click(cx.listener(move |editor, _e, cx| match fold_status {
                                     FoldStatus::Folded => {
                                         editor.unfold_at(&UnfoldAt { buffer_row }, cx);
@@ -4269,7 +4287,7 @@ impl Editor {
                                 .icon_color(ui::Color::Muted)
                                 .icon_size(ui::IconSize::Small)
                                 .selected(fold_status == FoldStatus::Folded)
-                                .selected_icon(ui::Icon::ChevronRight)
+                                .selected_icon(ui::IconName::ChevronRight)
                                 .size(ui::ButtonSize::None)
                         })
                     })
@@ -9739,7 +9757,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
                 ),
             )
             .child(
-                IconButton::new(("copy-block", cx.block_id), Icon::Copy)
+                IconButton::new(("copy-block", cx.block_id), IconName::Copy)
                     .icon_color(Color::Muted)
                     .size(ButtonSize::Compact)
                     .style(ButtonStyle::Transparent)

crates/editor/src/element.rs 🔗

@@ -28,7 +28,7 @@ use gpui::{
     AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners,
     CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds,
     InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,
-    MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, ShapedLine,
+    MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine,
     SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,
     TextStyle, View, ViewContext, WindowContext,
 };
@@ -581,41 +581,6 @@ impl EditorElement {
         }
     }
 
-    fn scroll(
-        editor: &mut Editor,
-        event: &ScrollWheelEvent,
-        position_map: &PositionMap,
-        bounds: &InteractiveBounds,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        if !bounds.visibly_contains(&event.position, cx) {
-            return;
-        }
-
-        let line_height = position_map.line_height;
-        let max_glyph_width = position_map.em_width;
-        let (delta, axis) = match event.delta {
-            gpui::ScrollDelta::Pixels(mut pixels) => {
-                //Trackpad
-                let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
-                (pixels, axis)
-            }
-
-            gpui::ScrollDelta::Lines(lines) => {
-                //Not trackpad
-                let pixels = point(lines.x * max_glyph_width, lines.y * line_height);
-                (pixels, None)
-            }
-        };
-
-        let scroll_position = position_map.snapshot.scroll_position();
-        let x = f32::from((scroll_position.x * max_glyph_width - delta.x) / max_glyph_width);
-        let y = f32::from((scroll_position.y * line_height - delta.y) / line_height);
-        let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
-        editor.scroll(scroll_position, axis, cx);
-        cx.stop_propagation();
-    }
-
     fn paint_background(
         &self,
         gutter_bounds: Bounds<Pixels>,
@@ -2450,34 +2415,78 @@ impl EditorElement {
         )
     }
 
-    fn paint_mouse_listeners(
+    fn paint_scroll_wheel_listener(
         &mut self,
-        bounds: Bounds<Pixels>,
-        gutter_bounds: Bounds<Pixels>,
-        text_bounds: Bounds<Pixels>,
+        interactive_bounds: &InteractiveBounds,
         layout: &LayoutState,
         cx: &mut WindowContext,
     ) {
-        let interactive_bounds = InteractiveBounds {
-            bounds: bounds.intersect(&cx.content_mask().bounds),
-            stacking_order: cx.stacking_order().clone(),
-        };
-
         cx.on_mouse_event({
             let position_map = layout.position_map.clone();
             let editor = self.editor.clone();
             let interactive_bounds = interactive_bounds.clone();
+            let mut delta = ScrollDelta::default();
 
             move |event: &ScrollWheelEvent, phase, cx| {
                 if phase == DispatchPhase::Bubble
                     && interactive_bounds.visibly_contains(&event.position, cx)
                 {
+                    delta = delta.coalesce(event.delta);
                     editor.update(cx, |editor, cx| {
-                        Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
+                        let position = event.position;
+                        let position_map: &PositionMap = &position_map;
+                        let bounds = &interactive_bounds;
+                        if !bounds.visibly_contains(&position, cx) {
+                            return;
+                        }
+
+                        let line_height = position_map.line_height;
+                        let max_glyph_width = position_map.em_width;
+                        let (delta, axis) = match delta {
+                            gpui::ScrollDelta::Pixels(mut pixels) => {
+                                //Trackpad
+                                let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
+                                (pixels, axis)
+                            }
+
+                            gpui::ScrollDelta::Lines(lines) => {
+                                //Not trackpad
+                                let pixels =
+                                    point(lines.x * max_glyph_width, lines.y * line_height);
+                                (pixels, None)
+                            }
+                        };
+
+                        let scroll_position = position_map.snapshot.scroll_position();
+                        let x = f32::from(
+                            (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width,
+                        );
+                        let y =
+                            f32::from((scroll_position.y * line_height - delta.y) / line_height);
+                        let scroll_position =
+                            point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
+                        editor.scroll(scroll_position, axis, cx);
+                        cx.stop_propagation();
                     });
                 }
             }
         });
+    }
+
+    fn paint_mouse_listeners(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        gutter_bounds: Bounds<Pixels>,
+        text_bounds: Bounds<Pixels>,
+        layout: &LayoutState,
+        cx: &mut WindowContext,
+    ) {
+        let interactive_bounds = InteractiveBounds {
+            bounds: bounds.intersect(&cx.content_mask().bounds),
+            stacking_order: cx.stacking_order().clone(),
+        };
+
+        self.paint_scroll_wheel_listener(&interactive_bounds, layout, cx);
 
         cx.on_mouse_event({
             let position_map = layout.position_map.clone();

crates/editor/src/hover_popover.rs 🔗

@@ -16,7 +16,7 @@ use lsp::DiagnosticSeverity;
 use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
 use settings::Settings;
 use std::{ops::Range, sync::Arc, time::Duration};
-use ui::{StyledExt, Tooltip};
+use ui::{prelude::*, Tooltip};
 use util::TryFutureExt;
 use workspace::Workspace;
 
@@ -514,6 +514,8 @@ impl DiagnosticPopover {
             None => self.local_diagnostic.diagnostic.message.clone(),
         };
 
+        let status_colors = cx.theme().status();
+
         struct DiagnosticColors {
             pub background: Hsla,
             pub border: Hsla,
@@ -521,24 +523,24 @@ impl DiagnosticPopover {
 
         let diagnostic_colors = match self.local_diagnostic.diagnostic.severity {
             DiagnosticSeverity::ERROR => DiagnosticColors {
-                background: style.status.error_background,
-                border: style.status.error_border,
+                background: status_colors.error_background,
+                border: status_colors.error_border,
             },
             DiagnosticSeverity::WARNING => DiagnosticColors {
-                background: style.status.warning_background,
-                border: style.status.warning_border,
+                background: status_colors.warning_background,
+                border: status_colors.warning_border,
             },
             DiagnosticSeverity::INFORMATION => DiagnosticColors {
-                background: style.status.info_background,
-                border: style.status.info_border,
+                background: status_colors.info_background,
+                border: status_colors.info_border,
             },
             DiagnosticSeverity::HINT => DiagnosticColors {
-                background: style.status.hint_background,
-                border: style.status.hint_border,
+                background: status_colors.hint_background,
+                border: status_colors.hint_border,
             },
             _ => DiagnosticColors {
-                background: style.status.ignored_background,
-                border: style.status.ignored_border,
+                background: status_colors.ignored_background,
+                border: status_colors.ignored_border,
             },
         };
 

crates/editor/src/scroll.rs 🔗

@@ -384,10 +384,12 @@ impl Editor {
     ) {
         hide_hover(self, cx);
         let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
-        let top_row = scroll_anchor
-            .anchor
-            .to_point(&self.buffer().read(cx).snapshot(cx))
-            .row;
+        let snapshot = &self.buffer().read(cx).snapshot(cx);
+        if !scroll_anchor.anchor.is_valid(snapshot) {
+            log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
+            return;
+        }
+        let top_row = scroll_anchor.anchor.to_point(snapshot).row;
         self.scroll_manager
             .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
     }

crates/editor/src/test.rs 🔗

@@ -60,8 +60,7 @@ pub fn assert_text_with_selections(
 #[allow(dead_code)]
 #[cfg(any(test, feature = "test-support"))]
 pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
-    // todo!()
-    Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx)
+    Editor::new(EditorMode::Full, buffer, None, cx)
 }
 
 pub(crate) fn build_editor_with_project(
@@ -69,6 +68,5 @@ pub(crate) fn build_editor_with_project(
     buffer: Model<MultiBuffer>,
     cx: &mut ViewContext<Editor>,
 ) -> Editor {
-    // todo!()
-    Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx)
+    Editor::new(EditorMode::Full, buffer, Some(project), cx)
 }

crates/feedback/src/deploy_feedback_button.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::{Render, ViewContext, WeakView};
-use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip};
+use ui::{prelude::*, ButtonCommon, IconButton, IconName, Tooltip};
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
 use crate::{feedback_modal::FeedbackModal, GiveFeedback};
@@ -27,7 +27,7 @@ impl Render for DeployFeedbackButton {
                 })
             })
             .is_some();
-        IconButton::new("give-feedback", Icon::Envelope)
+        IconButton::new("give-feedback", IconName::Envelope)
             .style(ui::ButtonStyle::Subtle)
             .icon_size(IconSize::Small)
             .selected(is_open)

crates/feedback/src/feedback_modal.rs 🔗

@@ -488,7 +488,7 @@ impl Render for FeedbackModal {
                     .child(
                         Button::new("community_repository", "Community Repository")
                             .style(ButtonStyle::Transparent)
-                            .icon(Icon::ExternalLink)
+                            .icon(IconName::ExternalLink)
                             .icon_position(IconPosition::End)
                             .icon_size(IconSize::Small)
                             .on_click(open_community_repo),

crates/gpui/docs/key_dispatch.md 🔗

@@ -50,7 +50,7 @@ impl Render for Menu {
             .on_action(|this, move: &MoveDown, cx| {
                 // ...
             })
-            .children(todo!())
+            .children(unimplemented!())
     }
 }
 ```
@@ -68,7 +68,7 @@ impl Render for Menu {
             .on_action(|this, move: &MoveDown, cx| {
                 // ...
             })
-            .children(todo!())
+            .children(unimplemented!())
     }
 }
 ```

crates/gpui/src/action.rs 🔗

@@ -114,14 +114,26 @@ impl ActionRegistry {
     pub(crate) fn load_actions(&mut self) {
         for builder in __GPUI_ACTIONS {
             let action = builder();
-            //todo(remove)
-            let name: SharedString = action.name.into();
-            self.builders_by_name.insert(name.clone(), action.build);
-            self.names_by_type_id.insert(action.type_id, name.clone());
-            self.all_names.push(name);
+            self.insert_action(action);
         }
     }
 
+    #[cfg(test)]
+    pub(crate) fn load_action<A: Action>(&mut self) {
+        self.insert_action(ActionData {
+            name: A::debug_name(),
+            type_id: TypeId::of::<A>(),
+            build: A::build,
+        });
+    }
+
+    fn insert_action(&mut self, action: ActionData) {
+        let name: SharedString = action.name.into();
+        self.builders_by_name.insert(name.clone(), action.build);
+        self.names_by_type_id.insert(action.type_id, name.clone());
+        self.all_names.push(name);
+    }
+
     /// Construct an action based on its name and optional JSON parameters sourced from the keymap.
     pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> {
         let name = self
@@ -203,7 +215,6 @@ macro_rules! __impl_action {
                 )
             }
 
-            // todo!() why is this needed in addition to name?
             fn debug_name() -> &'static str
             where
                 Self: ::std::marker::Sized

crates/gpui/src/app.rs 🔗

@@ -45,11 +45,13 @@ use util::{
 
 /// Temporary(?) wrapper around [`RefCell<AppContext>`] to help us debug any double borrows.
 /// Strongly consider removing after stabilization.
+#[doc(hidden)]
 pub struct AppCell {
     app: RefCell<AppContext>,
 }
 
 impl AppCell {
+    #[doc(hidden)]
     #[track_caller]
     pub fn borrow(&self) -> AppRef {
         if option_env!("TRACK_THREAD_BORROWS").is_some() {
@@ -59,6 +61,7 @@ impl AppCell {
         AppRef(self.app.borrow())
     }
 
+    #[doc(hidden)]
     #[track_caller]
     pub fn borrow_mut(&self) -> AppRefMut {
         if option_env!("TRACK_THREAD_BORROWS").is_some() {
@@ -69,6 +72,7 @@ impl AppCell {
     }
 }
 
+#[doc(hidden)]
 #[derive(Deref, DerefMut)]
 pub struct AppRef<'a>(Ref<'a, AppContext>);
 
@@ -81,6 +85,7 @@ impl<'a> Drop for AppRef<'a> {
     }
 }
 
+#[doc(hidden)]
 #[derive(Deref, DerefMut)]
 pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
 
@@ -93,6 +98,8 @@ impl<'a> Drop for AppRefMut<'a> {
     }
 }
 
+/// A reference to a GPUI application, typically constructed in the `main` function of your app.
+/// You won't interact with this type much outside of initial configuration and startup.
 pub struct App(Rc<AppCell>);
 
 /// Represents an application before it is fully launched. Once your app is
@@ -136,6 +143,8 @@ impl App {
         self
     }
 
+    /// Invokes a handler when an already-running application is launched.
+    /// On macOS, this can occur when the application icon is double-clicked or the app is launched via the dock.
     pub fn on_reopen<F>(&self, mut callback: F) -> &Self
     where
         F: 'static + FnMut(&mut AppContext),
@@ -149,18 +158,22 @@ impl App {
         self
     }
 
+    /// Returns metadata associated with the application
     pub fn metadata(&self) -> AppMetadata {
         self.0.borrow().app_metadata.clone()
     }
 
+    /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background.
     pub fn background_executor(&self) -> BackgroundExecutor {
         self.0.borrow().background_executor.clone()
     }
 
+    /// Returns a handle to the [`ForegroundExecutor`] associated with this app, which can be used to spawn futures in the foreground.
     pub fn foreground_executor(&self) -> ForegroundExecutor {
         self.0.borrow().foreground_executor.clone()
     }
 
+    /// Returns a reference to the [`TextSystem`] associated with this app.
     pub fn text_system(&self) -> Arc<TextSystem> {
         self.0.borrow().text_system.clone()
     }
@@ -174,12 +187,6 @@ type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()
 type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
 type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
 
-// struct FrameConsumer {
-//     next_frame_callbacks: Vec<FrameCallback>,
-//     task: Task<()>,
-//     display_linker
-// }
-
 pub struct AppContext {
     pub(crate) this: Weak<AppCell>,
     pub(crate) platform: Rc<dyn Platform>,
@@ -292,7 +299,7 @@ impl AppContext {
         app
     }
 
-    /// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit`
+    /// Quit the application gracefully. Handlers registered with [`ModelContext::on_app_quit`]
     /// will be given 100ms to complete before exiting.
     pub fn shutdown(&mut self) {
         let mut futures = Vec::new();
@@ -314,10 +321,12 @@ impl AppContext {
         }
     }
 
+    /// Gracefully quit the application via the platform's standard routine.
     pub fn quit(&mut self) {
         self.platform.quit();
     }
 
+    /// Get metadata about the app and platform.
     pub fn app_metadata(&self) -> AppMetadata {
         self.app_metadata.clone()
     }
@@ -340,6 +349,7 @@ impl AppContext {
         result
     }
 
+    /// Arrange a callback to be invoked when the given model or view calls `notify` on its respective context.
     pub fn observe<W, E>(
         &mut self,
         entity: &E,
@@ -355,7 +365,7 @@ impl AppContext {
         })
     }
 
-    pub fn observe_internal<W, E>(
+    pub(crate) fn observe_internal<W, E>(
         &mut self,
         entity: &E,
         mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static,
@@ -380,15 +390,17 @@ impl AppContext {
         subscription
     }
 
-    pub fn subscribe<T, E, Evt>(
+    /// Arrange for the given callback to be invoked whenever the given model or view emits an event of a given type.
+    /// The callback is provided a handle to the emitting entity and a reference to the emitted event.
+    pub fn subscribe<T, E, Event>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static,
+        mut on_event: impl FnMut(E, &Event, &mut AppContext) + 'static,
     ) -> Subscription
     where
-        T: 'static + EventEmitter<Evt>,
+        T: 'static + EventEmitter<Event>,
         E: Entity<T>,
-        Evt: 'static,
+        Event: 'static,
     {
         self.subscribe_internal(entity, move |entity, event, cx| {
             on_event(entity, event, cx);
@@ -426,6 +438,9 @@ impl AppContext {
         subscription
     }
 
+    /// Returns handles to all open windows in the application.
+    /// Each handle could be downcast to a handle typed for the root view of that window.
+    /// To find all windows of a given type, you could filter on
     pub fn windows(&self) -> Vec<AnyWindowHandle> {
         self.windows
             .values()
@@ -565,7 +580,7 @@ impl AppContext {
         self.pending_effects.push_back(effect);
     }
 
-    /// Called at the end of AppContext::update to complete any side effects
+    /// Called at the end of [`AppContext::update`] to complete any side effects
     /// such as notifying observers, emitting events, etc. Effects can themselves
     /// cause effects, so we continue looping until all effects are processed.
     fn flush_effects(&mut self) {

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

@@ -82,6 +82,7 @@ impl Context for AsyncAppContext {
 }
 
 impl AsyncAppContext {
+    /// Schedules all windows in the application to be redrawn.
     pub fn refresh(&mut self) -> Result<()> {
         let app = self
             .app
@@ -92,14 +93,17 @@ impl AsyncAppContext {
         Ok(())
     }
 
+    /// Get an executor which can be used to spawn futures in the background.
     pub fn background_executor(&self) -> &BackgroundExecutor {
         &self.background_executor
     }
 
+    /// Get an executor which can be used to spawn futures in the foreground.
     pub fn foreground_executor(&self) -> &ForegroundExecutor {
         &self.foreground_executor
     }
 
+    /// Invoke the given function in the context of the app, then flush any effects produced during its invocation.
     pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {
         let app = self
             .app
@@ -109,6 +113,7 @@ impl AsyncAppContext {
         Ok(f(&mut lock))
     }
 
+    /// Open a window with the given options based on the root view returned by the given function.
     pub fn open_window<V>(
         &self,
         options: crate::WindowOptions,
@@ -125,6 +130,7 @@ impl AsyncAppContext {
         Ok(lock.open_window(options, build_root_view))
     }
 
+    /// Schedule a future to be polled in the background.
     pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
     where
         Fut: Future<Output = R> + 'static,

crates/gpui/src/app/entity_map.rs 🔗

@@ -19,7 +19,10 @@ use std::{
 #[cfg(any(test, feature = "test-support"))]
 use collections::HashMap;
 
-slotmap::new_key_type! { pub struct EntityId; }
+slotmap::new_key_type! {
+    /// A unique identifier for a model or view across the application.
+    pub struct EntityId;
+}
 
 impl EntityId {
     pub fn as_u64(self) -> u64 {

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

@@ -1,3 +1,5 @@
+#![deny(missing_docs)]
+
 use crate::{
     div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
     BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
@@ -9,13 +11,19 @@ use anyhow::{anyhow, bail};
 use futures::{Stream, StreamExt};
 use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
 
+/// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides
+/// an implementation of `Context` with additional methods that are useful in tests.
 #[derive(Clone)]
 pub struct TestAppContext {
+    #[doc(hidden)]
     pub app: Rc<AppCell>,
+    #[doc(hidden)]
     pub background_executor: BackgroundExecutor,
+    #[doc(hidden)]
     pub foreground_executor: ForegroundExecutor,
+    #[doc(hidden)]
     pub dispatcher: TestDispatcher,
-    pub test_platform: Rc<TestPlatform>,
+    test_platform: Rc<TestPlatform>,
     text_system: Arc<TextSystem>,
 }
 
@@ -76,6 +84,7 @@ impl Context for TestAppContext {
 }
 
 impl TestAppContext {
+    /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you.
     pub fn new(dispatcher: TestDispatcher) -> Self {
         let arc_dispatcher = Arc::new(dispatcher.clone());
         let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
@@ -95,38 +104,47 @@ impl TestAppContext {
         }
     }
 
+    /// returns a new `TestAppContext` re-using the same executors to interleave tasks.
     pub fn new_app(&self) -> TestAppContext {
         Self::new(self.dispatcher.clone())
     }
 
+    /// Simulates quitting the app.
     pub fn quit(&self) {
         self.app.borrow_mut().shutdown();
     }
 
+    /// Schedules all windows to be redrawn on the next effect cycle.
     pub fn refresh(&mut self) -> Result<()> {
         let mut app = self.app.borrow_mut();
         app.refresh();
         Ok(())
     }
 
+    /// Returns an executor (for running tasks in the background)
     pub fn executor(&self) -> BackgroundExecutor {
         self.background_executor.clone()
     }
 
+    /// Returns an executor (for running tasks on the main thread)
     pub fn foreground_executor(&self) -> &ForegroundExecutor {
         &self.foreground_executor
     }
 
+    /// Gives you an `&mut AppContext` for the duration of the closure
     pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
         let mut cx = self.app.borrow_mut();
         cx.update(f)
     }
 
+    /// Gives you an `&AppContext` for the duration of the closure
     pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
         let cx = self.app.borrow();
         f(&*cx)
     }
 
+    /// Adds a new window. The Window will always be backed by a `TestWindow` which
+    /// can be retrieved with `self.test_window(handle)`
     pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
     where
         F: FnOnce(&mut ViewContext<V>) -> V,
@@ -136,12 +154,16 @@ impl TestAppContext {
         cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window))
     }
 
+    /// Adds a new window with no content.
     pub fn add_empty_window(&mut self) -> AnyWindowHandle {
         let mut cx = self.app.borrow_mut();
         cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {}))
             .any_handle
     }
 
+    /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used
+    /// as a `WindowContext` for the rest of the test. Typically you would shadow this context with
+    /// the returned one. `let (view, cx) = cx.add_window_view(...);`
     pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
     where
         F: FnOnce(&mut ViewContext<V>) -> V,
@@ -156,18 +178,23 @@ impl TestAppContext {
         (view, Box::leak(cx))
     }
 
+    /// returns the TextSystem
     pub fn text_system(&self) -> &Arc<TextSystem> {
         &self.text_system
     }
 
+    /// Simulates writing to the platform clipboard
     pub fn write_to_clipboard(&self, item: ClipboardItem) {
         self.test_platform.write_to_clipboard(item)
     }
 
+    /// Simulates reading from the platform clipboard.
+    /// This will return the most recent value from `write_to_clipboard`.
     pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
         self.test_platform.read_from_clipboard()
     }
 
+    /// Simulates choosing a File in the platform's "Open" dialog.
     pub fn simulate_new_path_selection(
         &self,
         select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
@@ -175,22 +202,27 @@ impl TestAppContext {
         self.test_platform.simulate_new_path_selection(select_path);
     }
 
+    /// Simulates clicking a button in an platform-level alert dialog.
     pub fn simulate_prompt_answer(&self, button_ix: usize) {
         self.test_platform.simulate_prompt_answer(button_ix);
     }
 
+    /// Returns true if there's an alert dialog open.
     pub fn has_pending_prompt(&self) -> bool {
         self.test_platform.has_pending_prompt()
     }
 
+    /// Simulates the user resizing the window to the new size.
     pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
         self.test_window(window_handle).simulate_resize(size);
     }
 
+    /// Returns all windows open in the test.
     pub fn windows(&self) -> Vec<AnyWindowHandle> {
         self.app.borrow().windows().clone()
     }
 
+    /// Run the given task on the main thread.
     pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
     where
         Fut: Future<Output = R> + 'static,
@@ -199,16 +231,20 @@ impl TestAppContext {
         self.foreground_executor.spawn(f(self.to_async()))
     }
 
+    /// true if the given global is defined
     pub fn has_global<G: 'static>(&self) -> bool {
         let app = self.app.borrow();
         app.has_global::<G>()
     }
 
+    /// runs the given closure with a reference to the global
+    /// panics if `has_global` would return false.
     pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
         let app = self.app.borrow();
         read(app.global(), &app)
     }
 
+    /// runs the given closure with a reference to the global (if set)
     pub fn try_read_global<G: 'static, R>(
         &self,
         read: impl FnOnce(&G, &AppContext) -> R,
@@ -217,11 +253,13 @@ impl TestAppContext {
         Some(read(lock.try_global()?, &lock))
     }
 
+    /// sets the global in this context.
     pub fn set_global<G: 'static>(&mut self, global: G) {
         let mut lock = self.app.borrow_mut();
         lock.set_global(global);
     }
 
+    /// updates the global in this context. (panics if `has_global` would return false)
     pub fn update_global<G: 'static, R>(
         &mut self,
         update: impl FnOnce(&mut G, &mut AppContext) -> R,
@@ -230,6 +268,8 @@ impl TestAppContext {
         lock.update_global(update)
     }
 
+    /// Returns an `AsyncAppContext` which can be used to run tasks that expect to be on a background
+    /// thread on the current thread in tests.
     pub fn to_async(&self) -> AsyncAppContext {
         AsyncAppContext {
             app: Rc::downgrade(&self.app),
@@ -238,10 +278,12 @@ impl TestAppContext {
         }
     }
 
+    /// Wait until there are no more pending tasks.
     pub fn run_until_parked(&mut self) {
         self.background_executor.run_until_parked()
     }
 
+    /// Simulate dispatching an action to the currently focused node in the window.
     pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
     where
         A: Action,
@@ -255,7 +297,8 @@ impl TestAppContext {
 
     /// simulate_keystrokes takes a space-separated list of keys to type.
     /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
-    /// will run backspace on the current editor through the command palette.
+    /// in Zed, this will run backspace on the current editor through the command palette.
+    /// This will also run the background executor until it's parked.
     pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
         for keystroke in keystrokes
             .split(" ")
@@ -270,7 +313,8 @@ impl TestAppContext {
 
     /// simulate_input takes a string of text to type.
     /// cx.simulate_input("abc")
-    /// will type abc into your current editor.
+    /// will type abc into your current editor
+    /// This will also run the background executor until it's parked.
     pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
         for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
             self.dispatch_keystroke(window, keystroke.into(), false);
@@ -279,6 +323,7 @@ impl TestAppContext {
         self.background_executor.run_until_parked()
     }
 
+    /// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`)
     pub fn dispatch_keystroke(
         &mut self,
         window: AnyWindowHandle,
@@ -289,6 +334,7 @@ impl TestAppContext {
             .simulate_keystroke(keystroke, is_held)
     }
 
+    /// Returns the `TestWindow` backing the given handle.
     pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
         self.app
             .borrow_mut()
@@ -303,6 +349,7 @@ impl TestAppContext {
             .clone()
     }
 
+    /// Returns a stream of notifications whenever the View or Model is updated.
     pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
         let (tx, rx) = futures::channel::mpsc::unbounded();
         self.update(|cx| {
@@ -319,6 +366,7 @@ impl TestAppContext {
         rx
     }
 
+    /// Retuens a stream of events emitted by the given Model.
     pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
         &mut self,
         entity: &Model<T>,
@@ -337,6 +385,8 @@ impl TestAppContext {
         rx
     }
 
+    /// Runs until the given condition becomes true. (Prefer `run_until_parked` if you
+    /// don't need to jump in at a specific time).
     pub async fn condition<T: 'static>(
         &mut self,
         model: &Model<T>,
@@ -366,6 +416,7 @@ impl TestAppContext {
 }
 
 impl<T: Send> Model<T> {
+    /// Block until the next event is emitted by the model, then return it.
     pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
     where
         Evt: Send + Clone + 'static,
@@ -395,6 +446,7 @@ impl<T: Send> Model<T> {
 }
 
 impl<V: 'static> View<V> {
+    /// Returns a future that resolves when the view is next updated.
     pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
         use postage::prelude::{Sink as _, Stream as _};
 
@@ -421,6 +473,7 @@ impl<V: 'static> View<V> {
 }
 
 impl<V> View<V> {
+    /// Returns a future that resolves when the condition becomes true.
     pub fn condition<Evt>(
         &self,
         cx: &TestAppContext,
@@ -471,12 +524,11 @@ impl<V> View<V> {
                         }
                     }
 
-                    // todo!(start_waiting)
-                    // cx.borrow().foreground_executor().start_waiting();
+                    cx.borrow().background_executor().start_waiting();
                     rx.recv()
                         .await
                         .expect("view dropped with pending condition");
-                    // cx.borrow().foreground_executor().finish_waiting();
+                    cx.borrow().background_executor().finish_waiting();
                 }
             })
             .await
@@ -488,6 +540,8 @@ impl<V> View<V> {
 
 use derive_more::{Deref, DerefMut};
 #[derive(Deref, DerefMut, Clone)]
+/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to
+/// run window-specific test code.
 pub struct VisualTestContext {
     #[deref]
     #[deref_mut]
@@ -496,10 +550,14 @@ pub struct VisualTestContext {
 }
 
 impl<'a> VisualTestContext {
+    /// Provides the `WindowContext` for the duration of the closure.
     pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
         self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
     }
 
+    /// Create a new VisualTestContext. You would typically shadow the passed in
+    /// TestAppContext with this, as this is typically more useful.
+    /// `let cx = VisualTestContext::from_window(window, cx);`
     pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
         Self {
             cx: cx.clone(),
@@ -507,10 +565,12 @@ impl<'a> VisualTestContext {
         }
     }
 
+    /// Wait until there are no more pending tasks.
     pub fn run_until_parked(&self) {
         self.cx.background_executor.run_until_parked();
     }
 
+    /// Dispatch the action to the currently focused node.
     pub fn dispatch_action<A>(&mut self, action: A)
     where
         A: Action,
@@ -518,24 +578,32 @@ impl<'a> VisualTestContext {
         self.cx.dispatch_action(self.window, action)
     }
 
+    /// Read the title off the window (set by `WindowContext#set_window_title`)
     pub fn window_title(&mut self) -> Option<String> {
         self.cx.test_window(self.window).0.lock().title.clone()
     }
 
+    /// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")`
+    /// Automatically runs until parked.
     pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
         self.cx.simulate_keystrokes(self.window, keystrokes)
     }
 
+    /// Simulate typing text `cx.simulate_input("hello")`
+    /// Automatically runs until parked.
     pub fn simulate_input(&mut self, input: &str) {
         self.cx.simulate_input(self.window, input)
     }
 
+    /// Simulates the user blurring the window.
     pub fn deactivate_window(&mut self) {
         if Some(self.window) == self.test_platform.active_window() {
             self.test_platform.set_active_window(None)
         }
         self.background_executor.run_until_parked();
     }
+
+    /// Simulates the user closing the window.
     /// Returns true if the window was closed.
     pub fn simulate_close(&mut self) -> bool {
         let handler = self
@@ -672,6 +740,7 @@ impl VisualContext for VisualTestContext {
 }
 
 impl AnyWindowHandle {
+    /// Creates the given view in this window.
     pub fn build_view<V: Render + 'static>(
         &self,
         cx: &mut TestAppContext,
@@ -681,6 +750,7 @@ impl AnyWindowHandle {
     }
 }
 
+/// An EmptyView for testing.
 pub struct EmptyView {}
 
 impl Render for EmptyView {

crates/gpui/src/element.rs 🔗

@@ -31,14 +31,14 @@ pub trait IntoElement: Sized {
     /// The specific type of element into which the implementing type is converted.
     type Element: Element;
 
-    /// The [ElementId] of self once converted into an [Element].
+    /// The [`ElementId`] of self once converted into an [`Element`].
     /// If present, the resulting element's state will be carried across frames.
     fn element_id(&self) -> Option<ElementId>;
 
-    /// Convert self into a type that implements [Element].
+    /// Convert self into a type that implements [`Element`].
     fn into_element(self) -> Self::Element;
 
-    /// Convert self into a dynamically-typed [AnyElement].
+    /// Convert self into a dynamically-typed [`AnyElement`].
     fn into_any_element(self) -> AnyElement {
         self.into_element().into_any()
     }
@@ -115,7 +115,7 @@ pub trait Render: 'static + Sized {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
 }
 
-/// You can derive [IntoElement] on any type that implements this trait.
+/// You can derive [`IntoElement`] on any type that implements this trait.
 /// It is used to allow views to be expressed in terms of abstract data.
 pub trait RenderOnce: 'static {
     fn render(self, cx: &mut WindowContext) -> impl IntoElement;
@@ -224,7 +224,7 @@ enum ElementDrawPhase<S> {
     },
 }
 
-/// A wrapper around an implementer of [Element] that allows it to be drawn in a window.
+/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
 impl<E: Element> DrawableElement<E> {
     fn new(element: E) -> Self {
         DrawableElement {

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

@@ -2,7 +2,7 @@ use std::sync::Arc;
 
 use crate::{
     point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
-    InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size,
+    InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size,
     StyleRefinement, Styled, WindowContext,
 };
 use futures::FutureExt;
@@ -12,13 +12,13 @@ use util::ResultExt;
 #[derive(Clone, Debug)]
 pub enum ImageSource {
     /// Image content will be loaded from provided URI at render time.
-    Uri(SharedString),
+    Uri(SharedUrl),
     Data(Arc<ImageData>),
     Surface(CVImageBuffer),
 }
 
-impl From<SharedString> for ImageSource {
-    fn from(value: SharedString) -> Self {
+impl From<SharedUrl> for ImageSource {
+    fn from(value: SharedUrl) -> Self {
         Self::Uri(value)
     }
 }

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

@@ -14,8 +14,8 @@ pub struct Overlay {
     children: SmallVec<[AnyElement; 2]>,
     anchor_corner: AnchorCorner,
     fit_mode: OverlayFitMode,
-    // todo!();
     anchor_position: Option<Point<Pixels>>,
+    // todo!();
     // position_mode: OverlayPositionMode,
 }
 

crates/gpui/src/executor.rs 🔗

@@ -32,6 +32,12 @@ pub struct ForegroundExecutor {
     not_send: PhantomData<Rc<()>>,
 }
 
+/// Task is a primitive that allows work to happen in the background.
+///
+/// It implements [`Future`] so you can `.await` on it.
+///
+/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows
+/// the task to continue running in the background, but with no way to return a value.
 #[must_use]
 #[derive(Debug)]
 pub enum Task<T> {
@@ -40,10 +46,12 @@ pub enum Task<T> {
 }
 
 impl<T> Task<T> {
+    /// Create a new task that will resolve with the value
     pub fn ready(val: T) -> Self {
         Task::Ready(Some(val))
     }
 
+    /// Detaching a task runs it to completion in the background
     pub fn detach(self) {
         match self {
             Task::Ready(_) => {}
@@ -57,6 +65,8 @@ where
     T: 'static,
     E: 'static + Debug,
 {
+    /// Run the task to completion in the background and log any
+    /// errors that occur.
     #[track_caller]
     pub fn detach_and_log_err(self, cx: &mut AppContext) {
         let location = core::panic::Location::caller();
@@ -97,6 +107,10 @@ type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
 
 type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
 
+/// BackgroundExecutor lets you run things on background threads.
+/// In production this is a thread pool with no ordering guarantees.
+/// In tests this is simalated by running tasks one by one in a deterministic
+/// (but arbitrary) order controlled by the `SEED` environment variable.
 impl BackgroundExecutor {
     pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
         Self { dispatcher }
@@ -135,6 +149,7 @@ impl BackgroundExecutor {
         Task::Spawned(task)
     }
 
+    /// Used by the test harness to run an async test in a syncronous fashion.
     #[cfg(any(test, feature = "test-support"))]
     #[track_caller]
     pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
@@ -145,6 +160,8 @@ impl BackgroundExecutor {
         }
     }
 
+    /// Block the current thread until the given future resolves.
+    /// Consider using `block_with_timeout` instead.
     pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
         if let Ok(value) = self.block_internal(true, future, usize::MAX) {
             value
@@ -206,6 +223,8 @@ impl BackgroundExecutor {
         }
     }
 
+    /// Block the current thread until the given future resolves
+    /// or `duration` has elapsed.
     pub fn block_with_timeout<R>(
         &self,
         duration: Duration,
@@ -238,6 +257,8 @@ impl BackgroundExecutor {
         }
     }
 
+    /// Scoped lets you start a number of tasks and waits
+    /// for all of them to complete before returning.
     pub async fn scoped<'scope, F>(&self, scheduler: F)
     where
         F: FnOnce(&mut Scope<'scope>),
@@ -253,6 +274,9 @@ impl BackgroundExecutor {
         }
     }
 
+    /// Returns a task that will complete after the given duration.
+    /// Depending on other concurrent tasks the elapsed duration may be longer
+    /// than reqested.
     pub fn timer(&self, duration: Duration) -> Task<()> {
         let (runnable, task) = async_task::spawn(async move {}, {
             let dispatcher = self.dispatcher.clone();
@@ -262,65 +286,81 @@ impl BackgroundExecutor {
         Task::Spawned(task)
     }
 
+    /// in tests, start_waiting lets you indicate which task is waiting (for debugging only)
     #[cfg(any(test, feature = "test-support"))]
     pub fn start_waiting(&self) {
         self.dispatcher.as_test().unwrap().start_waiting();
     }
 
+    /// in tests, removes the debugging data added by start_waiting
     #[cfg(any(test, feature = "test-support"))]
     pub fn finish_waiting(&self) {
         self.dispatcher.as_test().unwrap().finish_waiting();
     }
 
+    /// in tests, run an arbitrary number of tasks (determined by the SEED environment variable)
     #[cfg(any(test, feature = "test-support"))]
     pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
         self.dispatcher.as_test().unwrap().simulate_random_delay()
     }
 
+    /// in tests, indicate that a given task from `spawn_labeled` should run after everything else
     #[cfg(any(test, feature = "test-support"))]
     pub fn deprioritize(&self, task_label: TaskLabel) {
         self.dispatcher.as_test().unwrap().deprioritize(task_label)
     }
 
+    /// in tests, move time forward. This does not run any tasks, but does make `timer`s ready.
     #[cfg(any(test, feature = "test-support"))]
     pub fn advance_clock(&self, duration: Duration) {
         self.dispatcher.as_test().unwrap().advance_clock(duration)
     }
 
+    /// in tests, run one task.
     #[cfg(any(test, feature = "test-support"))]
     pub fn tick(&self) -> bool {
         self.dispatcher.as_test().unwrap().tick(false)
     }
 
+    /// in tests, run all tasks that are ready to run. If after doing so
+    /// the test still has outstanding tasks, this will panic. (See also `allow_parking`)
     #[cfg(any(test, feature = "test-support"))]
     pub fn run_until_parked(&self) {
         self.dispatcher.as_test().unwrap().run_until_parked()
     }
 
+    /// in tests, prevents `run_until_parked` from panicking if there are outstanding tasks.
+    /// This is useful when you are integrating other (non-GPUI) futures, like disk access, that
+    /// do take real async time to run.
     #[cfg(any(test, feature = "test-support"))]
     pub fn allow_parking(&self) {
         self.dispatcher.as_test().unwrap().allow_parking();
     }
 
+    /// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable
     #[cfg(any(test, feature = "test-support"))]
     pub fn rng(&self) -> StdRng {
         self.dispatcher.as_test().unwrap().rng()
     }
 
+    /// How many CPUs are available to the dispatcher
     pub fn num_cpus(&self) -> usize {
         num_cpus::get()
     }
 
+    /// Whether we're on the main thread.
     pub fn is_main_thread(&self) -> bool {
         self.dispatcher.is_main_thread()
     }
 
     #[cfg(any(test, feature = "test-support"))]
+    /// in tests, control the number of ticks that `block_with_timeout` will run before timing out.
     pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
         self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
     }
 }
 
+/// ForegroundExecutor runs things on the main thread.
 impl ForegroundExecutor {
     pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
         Self {
@@ -329,8 +369,7 @@ impl ForegroundExecutor {
         }
     }
 
-    /// Enqueues the given closure to be run on any thread. The closure returns
-    /// a future which will be run to completion on any available thread.
+    /// Enqueues the given Task to run on the main thread at some point in the future.
     pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
     where
         R: 'static,
@@ -350,6 +389,7 @@ impl ForegroundExecutor {
     }
 }
 
+/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`].
 pub struct Scope<'a> {
     executor: BackgroundExecutor,
     futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,

crates/gpui/src/gpui.rs 🔗

@@ -18,6 +18,7 @@ mod platform;
 pub mod prelude;
 mod scene;
 mod shared_string;
+mod shared_url;
 mod style;
 mod styled;
 mod subscription;
@@ -67,6 +68,7 @@ pub use refineable::*;
 pub use scene::*;
 use seal::Sealed;
 pub use shared_string::*;
+pub use shared_url::*;
 pub use smol::Timer;
 pub use style::*;
 pub use styled::*;

crates/gpui/src/image_cache.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{ImageData, ImageId, SharedString};
+use crate::{ImageData, ImageId, SharedUrl};
 use collections::HashMap;
 use futures::{
     future::{BoxFuture, Shared},
@@ -44,7 +44,7 @@ impl From<ImageError> for Error {
 
 pub struct ImageCache {
     client: Arc<dyn HttpClient>,
-    images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,
+    images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
 }
 
 type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
@@ -59,7 +59,7 @@ impl ImageCache {
 
     pub fn get(
         &self,
-        uri: impl Into<SharedString>,
+        uri: impl Into<SharedUrl>,
     ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
         let uri = uri.into();
         let mut images = self.images.lock();

crates/gpui/src/input.rs 🔗

@@ -34,7 +34,7 @@ pub trait InputHandler: 'static + Sized {
     ) -> Option<Bounds<Pixels>>;
 }
 
-/// The canonical implementation of `PlatformInputHandler`. Call `WindowContext::handle_input`
+/// The canonical implementation of [`PlatformInputHandler`]. Call [`WindowContext::handle_input`]
 /// with an instance during your element's paint.
 pub struct ElementInputHandler<V> {
     view: View<V>,

crates/gpui/src/interactive.rs 🔗

@@ -178,6 +178,20 @@ impl ScrollDelta {
             ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
         }
     }
+
+    pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
+        match (self, other) {
+            (ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
+                ScrollDelta::Pixels(px_a + px_b)
+            }
+
+            (ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => {
+                ScrollDelta::Lines(lines_a + lines_b)
+            }
+
+            _ => other,
+        }
+    }
 }
 
 #[derive(Clone, Debug, Default)]

crates/gpui/src/key_dispatch.rs 🔗

@@ -192,8 +192,8 @@ impl DispatchTree {
         keymap
             .bindings_for_action(action)
             .filter(|binding| {
-                for i in 1..context_stack.len() {
-                    let context = &context_stack[0..i];
+                for i in 0..context_stack.len() {
+                    let context = &context_stack[0..=i];
                     if keymap.binding_enabled(binding, context) {
                         return true;
                     }
@@ -283,3 +283,76 @@ impl DispatchTree {
         *self.node_stack.last().unwrap()
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use std::{rc::Rc, sync::Arc};
+
+    use parking_lot::Mutex;
+
+    use crate::{Action, ActionRegistry, DispatchTree, KeyBinding, KeyContext, Keymap};
+
+    #[derive(PartialEq, Eq)]
+    struct TestAction;
+
+    impl Action for TestAction {
+        fn name(&self) -> &'static str {
+            "test::TestAction"
+        }
+
+        fn debug_name() -> &'static str
+        where
+            Self: ::std::marker::Sized,
+        {
+            "test::TestAction"
+        }
+
+        fn partial_eq(&self, action: &dyn Action) -> bool {
+            action
+                .as_any()
+                .downcast_ref::<Self>()
+                .map_or(false, |a| self == a)
+        }
+
+        fn boxed_clone(&self) -> std::boxed::Box<dyn Action> {
+            Box::new(TestAction)
+        }
+
+        fn as_any(&self) -> &dyn ::std::any::Any {
+            self
+        }
+
+        fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
+        where
+            Self: Sized,
+        {
+            Ok(Box::new(TestAction))
+        }
+    }
+
+    #[test]
+    fn test_keybinding_for_action_bounds() {
+        let keymap = Keymap::new(vec![KeyBinding::new(
+            "cmd-n",
+            TestAction,
+            Some("ProjectPanel"),
+        )]);
+
+        let mut registry = ActionRegistry::default();
+
+        registry.load_action::<TestAction>();
+
+        let keymap = Arc::new(Mutex::new(keymap));
+
+        let tree = DispatchTree::new(keymap, Rc::new(registry));
+
+        let contexts = vec![
+            KeyContext::parse("Workspace").unwrap(),
+            KeyContext::parse("ProjectPanel").unwrap(),
+        ];
+
+        let keybinding = tree.bindings_for_action(&TestAction, &contexts);
+
+        assert!(keybinding[0].action.partial_eq(&TestAction))
+    }
+}

crates/gpui/src/platform/mac/display.rs 🔗

@@ -14,12 +14,12 @@ pub struct MacDisplay(pub(crate) CGDirectDisplayID);
 unsafe impl Send for MacDisplay {}
 
 impl MacDisplay {
-    /// Get the screen with the given [DisplayId].
+    /// Get the screen with the given [`DisplayId`].
     pub fn find_by_id(id: DisplayId) -> Option<Self> {
         Self::all().find(|screen| screen.id() == id)
     }
 
-    /// Get the screen with the given persistent [Uuid].
+    /// Get the screen with the given persistent [`Uuid`].
     pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
         Self::all().find(|screen| screen.uuid().ok() == Some(uuid))
     }

crates/gpui/src/platform/test/display.rs 🔗

@@ -32,7 +32,7 @@ impl PlatformDisplay for TestDisplay {
     }
 
     fn as_any(&self) -> &dyn std::any::Any {
-        todo!()
+        unimplemented!()
     }
 
     fn bounds(&self) -> crate::Bounds<crate::GlobalPixels> {

crates/gpui/src/platform/test/platform.rs 🔗

@@ -15,6 +15,7 @@ use std::{
     time::Duration,
 };
 
+/// TestPlatform implements the Platform trait for use in tests.
 pub struct TestPlatform {
     background_executor: BackgroundExecutor,
     foreground_executor: ForegroundExecutor,
@@ -103,7 +104,6 @@ impl TestPlatform {
     }
 }
 
-// todo!("implement out what our tests needed in GPUI 1")
 impl Platform for TestPlatform {
     fn background_executor(&self) -> BackgroundExecutor {
         self.background_executor.clone()

crates/gpui/src/shared_url.rs 🔗

@@ -0,0 +1,25 @@
+use derive_more::{Deref, DerefMut};
+
+use crate::SharedString;
+
+/// A [`SharedString`] containing a URL.
+#[derive(Deref, DerefMut, Default, PartialEq, Eq, Hash, Clone)]
+pub struct SharedUrl(SharedString);
+
+impl std::fmt::Debug for SharedUrl {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl std::fmt::Display for SharedUrl {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0.as_ref())
+    }
+}
+
+impl<T: Into<SharedString>> From<T> for SharedUrl {
+    fn from(value: T) -> Self {
+        Self(value.into())
+    }
+}

crates/gpui/src/subscription.rs 🔗

@@ -37,10 +37,10 @@ where
         })))
     }
 
-    /// Inserts a new `[Subscription]` for the given `emitter_key`. By default, subscriptions
+    /// Inserts a new [`Subscription`] for the given `emitter_key`. By default, subscriptions
     /// are inert, meaning that they won't be listed when calling `[SubscriberSet::remove]` or `[SubscriberSet::retain]`.
-    /// This method returns a tuple of a `[Subscription]` and an `impl FnOnce`, and you can use the latter
-    /// to activate the `[Subscription]`.
+    /// This method returns a tuple of a [`Subscription`] and an `impl FnOnce`, and you can use the latter
+    /// to activate the [`Subscription`].
     #[must_use]
     pub fn insert(
         &self,

crates/gpui/src/test.rs 🔗

@@ -1,3 +1,30 @@
+//! Test support for GPUI.
+//!
+//! GPUI provides first-class support for testing, which includes a macro to run test that rely on having a context,
+//! and a test implementation of the `ForegroundExecutor` and `BackgroundExecutor` which ensure that your tests run
+//! deterministically even in the face of arbitrary parallelism.
+//!
+//! The output of the `gpui::test` macro is understood by other rust test runners, so you can use it with `cargo test`
+//! or `cargo-nextest`, or another runner of your choice.
+//!
+//! To make it possible to test collaborative user interfaces (like Zed) you can ask for as many different contexts
+//! as you need.
+//!
+//! ## Example
+//!
+//! ```
+//! use gpui;
+//!
+//! #[gpui::test]
+//! async fn test_example(cx: &TestAppContext) {
+//!   assert!(true)
+//! }
+//!
+//! #[gpui::test]
+//! async fn test_collaboration_example(cx_a: &TestAppContext, cx_b: &TestAppContext) {
+//!   assert!(true)
+//! }
+//! ```
 use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
 use futures::StreamExt as _;
 use rand::prelude::*;
@@ -68,6 +95,7 @@ impl<T: 'static> futures::Stream for Observation<T> {
     }
 }
 
+/// observe returns a stream of the change events from the given `View` or `Model`
 pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
     let (tx, rx) = smol::channel::unbounded();
     let _subscription = cx.update(|cx| {

crates/gpui/src/window.rs 🔗

@@ -1,3 +1,5 @@
+#![deny(missing_docs)]
+
 use crate::{
     px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef,
     AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
@@ -85,10 +87,12 @@ pub enum DispatchPhase {
 }
 
 impl DispatchPhase {
+    /// Returns true if this represents the "bubble" phase.
     pub fn bubble(self) -> bool {
         self == DispatchPhase::Bubble
     }
 
+    /// Returns true if this represents the "capture" phase.
     pub fn capture(self) -> bool {
         self == DispatchPhase::Capture
     }
@@ -103,7 +107,10 @@ struct FocusEvent {
     current_focus_path: SmallVec<[FocusId; 8]>,
 }
 
-slotmap::new_key_type! { pub struct FocusId; }
+slotmap::new_key_type! {
+    /// A globally unique identifier for a focusable element.
+    pub struct FocusId;
+}
 
 thread_local! {
     pub(crate) static ELEMENT_ARENA: RefCell<Arena> = RefCell::new(Arena::new(4 * 1024 * 1024));
@@ -231,6 +238,7 @@ impl Drop for FocusHandle {
 /// FocusableView allows users of your view to easily
 /// focus it (using cx.focus_view(view))
 pub trait FocusableView: 'static + Render {
+    /// Returns the focus handle associated with this view.
     fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
 }
 
@@ -240,9 +248,11 @@ pub trait ManagedView: FocusableView + EventEmitter<DismissEvent> {}
 
 impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}
 
+/// Emitted by implementers of [`ManagedView`] to indicate the view should be dismissed, such as when a view is presented as a modal.
 pub struct DismissEvent;
 
 // Holds the state for a specific window.
+#[doc(hidden)]
 pub struct Window {
     pub(crate) handle: AnyWindowHandle,
     pub(crate) removed: bool,
@@ -434,6 +444,7 @@ impl Window {
 #[derive(Clone, Debug, Default, PartialEq, Eq)]
 #[repr(C)]
 pub struct ContentMask<P: Clone + Default + Debug> {
+    /// The bounds
     pub bounds: Bounds<P>,
 }
 
@@ -453,8 +464,8 @@ impl ContentMask<Pixels> {
 }
 
 /// Provides access to application state in the context of a single window. Derefs
-/// to an `AppContext`, so you can also pass a `WindowContext` to any method that takes
-/// an `AppContext` and call any `AppContext` methods.
+/// to an [`AppContext`], so you can also pass a [`WindowContext`] to any method that takes
+/// an [`AppContext`] and call any [`AppContext`] methods.
 pub struct WindowContext<'a> {
     pub(crate) app: &'a mut AppContext,
     pub(crate) window: &'a mut Window,
@@ -482,20 +493,20 @@ impl<'a> WindowContext<'a> {
         self.window.removed = true;
     }
 
-    /// Obtain a new `FocusHandle`, which allows you to track and manipulate the keyboard focus
+    /// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus
     /// for elements rendered within this window.
     pub fn focus_handle(&mut self) -> FocusHandle {
         FocusHandle::new(&self.window.focus_handles)
     }
 
-    /// Obtain the currently focused `FocusHandle`. If no elements are focused, returns `None`.
+    /// Obtain the currently focused [`FocusHandle`]. If no elements are focused, returns `None`.
     pub fn focused(&self) -> Option<FocusHandle> {
         self.window
             .focus
             .and_then(|id| FocusHandle::for_id(id, &self.window.focus_handles))
     }
 
-    /// Move focus to the element associated with the given `FocusHandle`.
+    /// Move focus to the element associated with the given [`FocusHandle`].
     pub fn focus(&mut self, handle: &FocusHandle) {
         if !self.window.focus_enabled || self.window.focus == Some(handle.id) {
             return;
@@ -525,11 +536,13 @@ impl<'a> WindowContext<'a> {
         self.notify();
     }
 
+    /// Blur the window and don't allow anything in it to be focused again.
     pub fn disable_focus(&mut self) {
         self.blur();
         self.window.focus_enabled = false;
     }
 
+    /// Dispatch the given action on the currently focused element.
     pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
         let focus_handle = self.focused();
 
@@ -591,6 +604,9 @@ impl<'a> WindowContext<'a> {
         });
     }
 
+    /// Subscribe to events emitted by a model or view.
+    /// The entity to which you're subscribing must implement the [`EventEmitter`] trait.
+    /// The callback will be invoked a handle to the emitting entity (either a [`View`] or [`Model`]), the event, and a window context for the current window.
     pub fn subscribe<Emitter, E, Evt>(
         &mut self,
         entity: &E,
@@ -754,6 +770,9 @@ impl<'a> WindowContext<'a> {
             .request_measured_layout(style, rem_size, measure)
     }
 
+    /// Compute the layout for the given id within the given available space.
+    /// This method is called for its side effect, typically by the framework prior to painting.
+    /// After calling it, you can request the bounds of the given layout node id or any descendant.
     pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
         let mut layout_engine = self.window.layout_engine.take().unwrap();
         layout_engine.compute_layout(layout_id, available_space, self);
@@ -788,30 +807,37 @@ impl<'a> WindowContext<'a> {
             .retain(&(), |callback| callback(self));
     }
 
+    /// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays.
     pub fn window_bounds(&self) -> WindowBounds {
         self.window.bounds
     }
 
+    /// Returns the size of the drawable area within the window.
     pub fn viewport_size(&self) -> Size<Pixels> {
         self.window.viewport_size
     }
 
+    /// Returns whether this window is focused by the operating system (receiving key events).
     pub fn is_window_active(&self) -> bool {
         self.window.active
     }
 
+    /// Toggle zoom on the window.
     pub fn zoom_window(&self) {
         self.window.platform_window.zoom();
     }
 
+    /// Update the window's title at the platform level.
     pub fn set_window_title(&mut self, title: &str) {
         self.window.platform_window.set_title(title);
     }
 
+    /// Mark the window as dirty at the platform level.
     pub fn set_window_edited(&mut self, edited: bool) {
         self.window.platform_window.set_edited(edited);
     }
 
+    /// Determine the display on which the window is visible.
     pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
         self.platform
             .displays()
@@ -819,6 +845,7 @@ impl<'a> WindowContext<'a> {
             .find(|display| display.id() == self.window.display_id)
     }
 
+    /// Show the platform character palette.
     pub fn show_character_palette(&self) {
         self.window.platform_window.show_character_palette();
     }
@@ -936,6 +963,7 @@ impl<'a> WindowContext<'a> {
             .on_action(action_type, ArenaRef::from(listener));
     }
 
+    /// Determine whether the given action is available along the dispatch path to the currently focused element.
     pub fn is_action_available(&self, action: &dyn Action) -> bool {
         let target = self
             .focused()
@@ -962,6 +990,7 @@ impl<'a> WindowContext<'a> {
         self.window.modifiers
     }
 
+    /// Update the cursor style at the platform level.
     pub fn set_cursor_style(&mut self, style: CursorStyle) {
         self.window.requested_cursor_style = Some(style)
     }
@@ -991,7 +1020,7 @@ impl<'a> WindowContext<'a> {
         true
     }
 
-    pub fn was_top_layer_under_active_drag(
+    pub(crate) fn was_top_layer_under_active_drag(
         &self,
         point: &Point<Pixels>,
         level: &StackingOrder,
@@ -1649,6 +1678,7 @@ impl<'a> WindowContext<'a> {
         self.dispatch_keystroke_observers(event, None);
     }
 
+    /// Determine whether a potential multi-stroke key binding is in progress on this window.
     pub fn has_pending_keystrokes(&self) -> bool {
         self.window
             .rendered_frame
@@ -1715,27 +1745,34 @@ impl<'a> WindowContext<'a> {
         subscription
     }
 
+    /// Focus the current window and bring it to the foreground at the platform level.
     pub fn activate_window(&self) {
         self.window.platform_window.activate();
     }
 
+    /// Minimize the current window at the platform level.
     pub fn minimize_window(&self) {
         self.window.platform_window.minimize();
     }
 
+    /// Toggle full screen status on the current window at the platform level.
     pub fn toggle_full_screen(&self) {
         self.window.platform_window.toggle_full_screen();
     }
 
+    /// Present a platform dialog.
+    /// The provided message will be presented, along with buttons for each answer.
+    /// When a button is clicked, the returned Receiver will receive the index of the clicked button.
     pub fn prompt(
         &self,
         level: PromptLevel,
-        msg: &str,
+        message: &str,
         answers: &[&str],
     ) -> oneshot::Receiver<usize> {
-        self.window.platform_window.prompt(level, msg, answers)
+        self.window.platform_window.prompt(level, message, answers)
     }
 
+    /// Returns all available actions for the focused element.
     pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
         let node_id = self
             .window
@@ -1754,6 +1791,7 @@ impl<'a> WindowContext<'a> {
             .available_actions(node_id)
     }
 
+    /// Returns key bindings that invoke the given action on the currently focused element.
     pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
         self.window
             .rendered_frame
@@ -1764,6 +1802,7 @@ impl<'a> WindowContext<'a> {
             )
     }
 
+    /// Returns any bindings that would invoke the given action on the given focus handle if it were focused.
     pub fn bindings_for_action_in(
         &self,
         action: &dyn Action,
@@ -1782,6 +1821,7 @@ impl<'a> WindowContext<'a> {
         dispatch_tree.bindings_for_action(action, &context_stack)
     }
 
+    /// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.
     pub fn listener_for<V: Render, E>(
         &self,
         view: &View<V>,
@@ -1793,6 +1833,7 @@ impl<'a> WindowContext<'a> {
         }
     }
 
+    /// Returns a generic handler that invokes the given handler with the view and context associated with the given view handle.
     pub fn handler_for<V: Render>(
         &self,
         view: &View<V>,
@@ -1804,7 +1845,8 @@ impl<'a> WindowContext<'a> {
         }
     }
 
-    //========== ELEMENT RELATED FUNCTIONS ===========
+    /// Invoke the given function with the given focus handle present on the key dispatch stack.
+    /// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint.
     pub fn with_key_dispatch<R>(
         &mut self,
         context: Option<KeyContext>,
@@ -1843,6 +1885,8 @@ impl<'a> WindowContext<'a> {
         }
     }
 
+    /// Register a callback that can interrupt the closing of the current window based the returned boolean.
+    /// If the callback returns false, the window won't be closed.
     pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) {
         let mut this = self.to_async();
         self.window
@@ -2017,19 +2061,24 @@ impl<'a> BorrowMut<AppContext> for WindowContext<'a> {
     }
 }
 
+/// This trait contains functionality that is shared across [`ViewContext`] and [`WindowContext`]
 pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
+    #[doc(hidden)]
     fn app_mut(&mut self) -> &mut AppContext {
         self.borrow_mut()
     }
 
+    #[doc(hidden)]
     fn app(&self) -> &AppContext {
         self.borrow()
     }
 
+    #[doc(hidden)]
     fn window(&self) -> &Window {
         self.borrow()
     }
 
+    #[doc(hidden)]
     fn window_mut(&mut self) -> &mut Window {
         self.borrow_mut()
     }
@@ -2279,6 +2328,10 @@ impl BorrowMut<Window> for WindowContext<'_> {
 
 impl<T> BorrowWindow for T where T: BorrowMut<AppContext> + BorrowMut<Window> {}
 
+/// Provides access to application state that is specialized for a particular [`View`].
+/// Allows you to interact with focus, emit events, etc.
+/// ViewContext also derefs to [`WindowContext`], giving you access to all of its methods as well.
+/// When you call [`View::update`], you're passed a `&mut V` and an `&mut ViewContext<V>`.
 pub struct ViewContext<'a, V> {
     window_cx: WindowContext<'a>,
     view: &'a View<V>,
@@ -2316,14 +2369,17 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         }
     }
 
+    /// Get the entity_id of this view.
     pub fn entity_id(&self) -> EntityId {
         self.view.entity_id()
     }
 
+    /// Get the view pointer underlying this context.
     pub fn view(&self) -> &View<V> {
         self.view
     }
 
+    /// Get the model underlying this view.
     pub fn model(&self) -> &Model<V> {
         &self.view.model
     }
@@ -2333,6 +2389,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         &mut self.window_cx
     }
 
+    /// Set a given callback to be run on the next frame.
     pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + 'static)
     where
         V: 'static,
@@ -2350,6 +2407,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         });
     }
 
+    /// Observe another model or view for changes to its state, as tracked by [`ModelContext::notify`].
     pub fn observe<V2, E>(
         &mut self,
         entity: &E,
@@ -2383,6 +2441,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         subscription
     }
 
+    /// Subscribe to events emitted by another model or view.
+    /// The entity to which you're subscribing must implement the [`EventEmitter`] trait.
+    /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [`View`] or [`Model`]), the event, and a view context for the current view.
     pub fn subscribe<V2, E, Evt>(
         &mut self,
         entity: &E,
@@ -2440,6 +2501,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         subscription
     }
 
+    /// Register a callback to be invoked when the given Model or View is released.
     pub fn observe_release<V2, E>(
         &mut self,
         entity: &E,
@@ -2466,6 +2528,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         subscription
     }
 
+    /// Indicate that this view has changed, which will invoke any observers and also mark the window as dirty.
+    /// If this view or any of its ancestors are *cached*, notifying it will cause it or its ancestors to be redrawn.
     pub fn notify(&mut self) {
         if !self.window.drawing {
             self.window_cx.notify();
@@ -2475,6 +2539,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         }
     }
 
+    /// Register a callback to be invoked when the window is resized.
     pub fn observe_window_bounds(
         &mut self,
         mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
@@ -2488,6 +2553,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         subscription
     }
 
+    /// Register a callback to be invoked when the window is activated or deactivated.
     pub fn observe_window_activation(
         &mut self,
         mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
@@ -2620,6 +2686,10 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         subscription
     }
 
+    /// Schedule a future to be run asynchronously.
+    /// The given callback is invoked with a [`WeakView<V>`] to avoid leaking the view for a long-running process.
+    /// It's also given an [`AsyncWindowContext`], which can be used to access the state of the view across await points.
+    /// The returned future will be polled on the main thread.
     pub fn spawn<Fut, R>(
         &mut self,
         f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,
@@ -2632,6 +2702,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         self.window_cx.spawn(|cx| f(view, cx))
     }
 
+    /// Update the global state of the given type.
     pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
     where
         G: 'static,
@@ -2642,6 +2713,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         result
     }
 
+    /// Register a callback to be invoked when the given global state changes.
     pub fn observe_global<G: 'static>(
         &mut self,
         mut f: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static,
@@ -2660,6 +2732,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         subscription
     }
 
+    /// Add a listener for any mouse event that occurs in the window.
+    /// This is a fairly low level method.
+    /// Typically, you'll want to use methods on UI elements, which perform bounds checking etc.
     pub fn on_mouse_event<Event: 'static>(
         &mut self,
         handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
@@ -2672,6 +2747,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         });
     }
 
+    /// Register a callback to be invoked when the given Key Event is dispatched to the window.
     pub fn on_key_event<Event: 'static>(
         &mut self,
         handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
@@ -2684,6 +2760,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         });
     }
 
+    /// Register a callback to be invoked when the given Action type is dispatched to the window.
     pub fn on_action(
         &mut self,
         action_type: TypeId,
@@ -2698,6 +2775,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
             });
     }
 
+    /// Emit an event to be handled any other views that have subscribed via [ViewContext::subscribe].
     pub fn emit<Evt>(&mut self, event: Evt)
     where
         Evt: 'static,
@@ -2711,6 +2789,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         });
     }
 
+    /// Move focus to the current view, assuming it implements [`FocusableView`].
     pub fn focus_self(&mut self)
     where
         V: FocusableView,
@@ -2718,6 +2797,11 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         self.defer(|view, cx| view.focus_handle(cx).focus(cx))
     }
 
+    /// Convenience method for accessing view state in an event callback.
+    ///
+    /// Many GPUI callbacks take the form of `Fn(&E, &mut WindowContext)`,
+    /// but it's often useful to be able to access view state in these
+    /// callbacks. This method provides a convenient way to do so.
     pub fn listener<E>(
         &self,
         f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
@@ -2827,14 +2911,20 @@ impl<'a, V> std::ops::DerefMut for ViewContext<'a, V> {
 }
 
 // #[derive(Clone, Copy, Eq, PartialEq, Hash)]
-slotmap::new_key_type! { pub struct WindowId; }
+slotmap::new_key_type! {
+    /// A unique identifier for a window.
+    pub struct WindowId;
+}
 
 impl WindowId {
+    /// Converts this window ID to a `u64`.
     pub fn as_u64(&self) -> u64 {
         self.0.as_ffi()
     }
 }
 
+/// A handle to a window with a specific root view type.
+/// Note that this does not keep the window alive on its own.
 #[derive(Deref, DerefMut)]
 pub struct WindowHandle<V> {
     #[deref]
@@ -2844,6 +2934,8 @@ pub struct WindowHandle<V> {
 }
 
 impl<V: 'static + Render> WindowHandle<V> {
+    /// Create a new handle from a window ID.
+    /// This does not check if the root type of the window is `V`.
     pub fn new(id: WindowId) -> Self {
         WindowHandle {
             any_handle: AnyWindowHandle {
@@ -2854,6 +2946,9 @@ impl<V: 'static + Render> WindowHandle<V> {
         }
     }
 
+    /// Get the root view out of this window.
+    ///
+    /// This will fail if the window is closed or if the root view's type does not match `V`.
     pub fn root<C>(&self, cx: &mut C) -> Result<View<V>>
     where
         C: Context,
@@ -2865,6 +2960,9 @@ impl<V: 'static + Render> WindowHandle<V> {
         }))
     }
 
+    /// Update the root view of this window.
+    ///
+    /// This will fail if the window has been closed or if the root view's type does not match
     pub fn update<C, R>(
         &self,
         cx: &mut C,
@@ -2881,6 +2979,9 @@ impl<V: 'static + Render> WindowHandle<V> {
         })?
     }
 
+    /// Read the root view out of this window.
+    ///
+    /// This will fail if the window is closed or if the root view's type does not match `V`.
     pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> {
         let x = cx
             .windows
@@ -2897,6 +2998,9 @@ impl<V: 'static + Render> WindowHandle<V> {
         Ok(x.read(cx))
     }
 
+    /// Read the root view out of this window, with a callback
+    ///
+    /// This will fail if the window is closed or if the root view's type does not match `V`.
     pub fn read_with<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R>
     where
         C: Context,
@@ -2904,6 +3008,9 @@ impl<V: 'static + Render> WindowHandle<V> {
         cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx))
     }
 
+    /// Read the root view pointer off of this window.
+    ///
+    /// This will fail if the window is closed or if the root view's type does not match `V`.
     pub fn root_view<C>(&self, cx: &C) -> Result<View<V>>
     where
         C: Context,
@@ -2911,6 +3018,9 @@ impl<V: 'static + Render> WindowHandle<V> {
         cx.read_window(self, |root_view, _cx| root_view.clone())
     }
 
+    /// Check if this window is 'active'.
+    ///
+    /// Will return `None` if the window is closed.
     pub fn is_active(&self, cx: &AppContext) -> Option<bool> {
         cx.windows
             .get(self.id)
@@ -2946,6 +3056,7 @@ impl<V: 'static> From<WindowHandle<V>> for AnyWindowHandle {
     }
 }
 
+/// A handle to a window with any root view type, which can be downcast to a window with a specific root view type.
 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
 pub struct AnyWindowHandle {
     pub(crate) id: WindowId,
@@ -2953,10 +3064,13 @@ pub struct AnyWindowHandle {
 }
 
 impl AnyWindowHandle {
+    /// Get the ID of this window.
     pub fn window_id(&self) -> WindowId {
         self.id
     }
 
+    /// Attempt to convert this handle to a window handle with a specific root view type.
+    /// If the types do not match, this will return `None`.
     pub fn downcast<T: 'static>(&self) -> Option<WindowHandle<T>> {
         if TypeId::of::<T>() == self.state_type {
             Some(WindowHandle {
@@ -2968,6 +3082,9 @@ impl AnyWindowHandle {
         }
     }
 
+    /// Update the state of the root view of this window.
+    ///
+    /// This will fail if the window has been closed.
     pub fn update<C, R>(
         self,
         cx: &mut C,
@@ -2979,6 +3096,9 @@ impl AnyWindowHandle {
         cx.update_window(self, update)
     }
 
+    /// Read the state of the root view of this window.
+    ///
+    /// This will fail if the window has been closed.
     pub fn read<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R>
     where
         C: Context,
@@ -2999,12 +3119,21 @@ impl AnyWindowHandle {
 //     }
 // }
 
+/// An identifier for an [`Element`](crate::Element).
+///
+/// Can be constructed with a string, a number, or both, as well
+/// as other internal representations.
 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
 pub enum ElementId {
+    /// The ID of a View element
     View(EntityId),
+    /// An integer ID.
     Integer(usize),
+    /// A string based ID.
     Name(SharedString),
+    /// An ID that's equated with a focus handle.
     FocusHandle(FocusId),
+    /// A combination of a name and an integer.
     NamedInteger(SharedString, usize),
 }
 
@@ -3074,7 +3203,8 @@ impl From<(&'static str, u64)> for ElementId {
     }
 }
 
-/// A rectangle, to be rendered on the screen by GPUI at the given position and size.
+/// A rectangle to be rendered in the window at the given position and size.
+/// Passed as an argument [`WindowContext::paint_quad`].
 #[derive(Clone)]
 pub struct PaintQuad {
     bounds: Bounds<Pixels>,

crates/gpui/tests/action_macros.rs 🔗

@@ -18,33 +18,33 @@ fn test_action_macros() {
 
     impl gpui::Action for RegisterableAction {
         fn boxed_clone(&self) -> Box<dyn gpui::Action> {
-            todo!()
+            unimplemented!()
         }
 
         fn as_any(&self) -> &dyn std::any::Any {
-            todo!()
+            unimplemented!()
         }
 
         fn partial_eq(&self, _action: &dyn gpui::Action) -> bool {
-            todo!()
+            unimplemented!()
         }
 
         fn name(&self) -> &str {
-            todo!()
+            unimplemented!()
         }
 
         fn debug_name() -> &'static str
         where
             Self: Sized,
         {
-            todo!()
+            unimplemented!()
         }
 
         fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn gpui::Action>>
         where
             Self: Sized,
         {
-            todo!()
+            unimplemented!()
         }
     }
 }

crates/gpui_macros/src/gpui_macros.rs 🔗

@@ -7,26 +7,56 @@ mod test;
 use proc_macro::TokenStream;
 
 #[proc_macro]
+/// register_action! can be used to register an action with the GPUI runtime.
+/// You should typically use `gpui::actions!` or `gpui::impl_actions!` instead,
+/// but this can be used for fine grained customization.
 pub fn register_action(ident: TokenStream) -> TokenStream {
     register_action::register_action_macro(ident)
 }
 
 #[proc_macro_derive(IntoElement)]
+// #[derive(IntoElement)] is used to create a Component out of anything that implements
+// the `RenderOnce` trait.
 pub fn derive_into_element(input: TokenStream) -> TokenStream {
     derive_into_element::derive_into_element(input)
 }
 
 #[proc_macro_derive(Render)]
+#[doc(hidden)]
 pub fn derive_render(input: TokenStream) -> TokenStream {
     derive_render::derive_render(input)
 }
 
+// Used by gpui to generate the style helpers.
 #[proc_macro]
+#[doc(hidden)]
 pub fn style_helpers(input: TokenStream) -> TokenStream {
     style_helpers::style_helpers(input)
 }
 
 #[proc_macro_attribute]
+/// #[gpui::test] can be used to annotate test functions that run with GPUI support.
+/// it supports both synchronous and asynchronous tests, and can provide you with
+/// as many `TestAppContext` instances as you need.
+/// The output contains a `#[test]` annotation so this can be used with any existing
+/// test harness (`cargo test` or `cargo-nextest`).
+///
+/// ```
+/// #[gpui::test]
+/// async fn test_foo(mut cx: &TestAppContext) { }
+/// ```
+///
+/// In addition to passing a TestAppContext, you can also ask for a `StdRnd` instance.
+/// this will be seeded with the `SEED` environment variable and is used internally by
+/// the ForegroundExecutor and BackgroundExecutor to run tasks deterministically in tests.
+/// Using the same `StdRng` for behaviour in your test will allow you to exercise a wide
+/// variety of scenarios and interleavings just by changing the seed.
+///
+/// #[gpui::test] also takes three different arguments:
+/// - `#[gpui::test(interations=10)]` will run the test ten times with a different initial SEED.
+/// - `#[gpui::test(retries=3)]` will run the test up to four times if it fails to try and make it pass.
+/// - `#[gpui::test(on_failure="crate::test::report_failure")]` will call the specified function after the
+///    tests fail so that you can write out more detail about the failure.
 pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
     test::test(args, function)
 }

crates/language/src/syntax_map/syntax_map_tests.rs 🔗

@@ -258,19 +258,19 @@ fn test_typing_multiple_new_injections() {
     let (buffer, syntax_map) = test_edit_sequence(
         "Rust",
         &[
-            "fn a() { dbg }",
-            "fn a() { dbg«!» }",
-            "fn a() { dbg!«()» }",
-            "fn a() { dbg!(«b») }",
-            "fn a() { dbg!(b«.») }",
-            "fn a() { dbg!(b.«c») }",
-            "fn a() { dbg!(b.c«()») }",
-            "fn a() { dbg!(b.c(«vec»)) }",
-            "fn a() { dbg!(b.c(vec«!»)) }",
-            "fn a() { dbg!(b.c(vec!«[]»)) }",
-            "fn a() { dbg!(b.c(vec![«d»])) }",
-            "fn a() { dbg!(b.c(vec![d«.»])) }",
-            "fn a() { dbg!(b.c(vec![d.«e»])) }",
+            "fn a() { test_macro }",
+            "fn a() { test_macro«!» }",
+            "fn a() { test_macro!«()» }",
+            "fn a() { test_macro!(«b») }",
+            "fn a() { test_macro!(b«.») }",
+            "fn a() { test_macro!(b.«c») }",
+            "fn a() { test_macro!(b.c«()») }",
+            "fn a() { test_macro!(b.c(«vec»)) }",
+            "fn a() { test_macro!(b.c(vec«!»)) }",
+            "fn a() { test_macro!(b.c(vec!«[]»)) }",
+            "fn a() { test_macro!(b.c(vec![«d»])) }",
+            "fn a() { test_macro!(b.c(vec![d«.»])) }",
+            "fn a() { test_macro!(b.c(vec![d.«e»])) }",
         ],
     );
 
@@ -278,7 +278,7 @@ fn test_typing_multiple_new_injections() {
         &syntax_map,
         &buffer,
         &["field"],
-        "fn a() { dbg!(b.«c»(vec![d.«e»])) }",
+        "fn a() { test_macro!(b.«c»(vec![d.«e»])) }",
     );
 }
 

crates/project_panel/src/project_panel.rs 🔗

@@ -30,7 +30,7 @@ use std::{
     sync::Arc,
 };
 use theme::ThemeSettings;
-use ui::{prelude::*, v_stack, ContextMenu, IconElement, KeyBinding, Label, ListItem};
+use ui::{prelude::*, v_stack, ContextMenu, Icon, KeyBinding, Label, ListItem};
 use unicase::UniCase;
 use util::{maybe, ResultExt, TryFutureExt};
 use workspace::{
@@ -1403,7 +1403,7 @@ impl ProjectPanel {
                     .indent_step_size(px(settings.indent_size))
                     .selected(is_selected)
                     .child(if let Some(icon) = &icon {
-                        div().child(IconElement::from_path(icon.to_string()).color(Color::Muted))
+                        div().child(Icon::from_path(icon.to_string()).color(Color::Muted))
                     } else {
                         div().size(IconSize::default().rems()).invisible()
                     })
@@ -1433,6 +1433,9 @@ impl ProjectPanel {
                     }))
                     .on_secondary_mouse_down(cx.listener(
                         move |this, event: &MouseDownEvent, cx| {
+                            // Stop propagation to prevent the catch-all context menu for the project
+                            // panel from being deployed.
+                            cx.stop_propagation();
                             this.deploy_context_menu(event.position, entry_id, cx);
                         },
                     )),
@@ -1587,7 +1590,7 @@ impl Render for DraggedProjectEntryView {
                     .indent_level(self.details.depth)
                     .indent_step_size(px(settings.indent_size))
                     .child(if let Some(icon) = &self.details.icon {
-                        div().child(IconElement::from_path(icon.to_string()))
+                        div().child(Icon::from_path(icon.to_string()))
                     } else {
                         div()
                     })
@@ -1637,8 +1640,8 @@ impl Panel for ProjectPanel {
         cx.notify();
     }
 
-    fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
-        Some(ui::Icon::FileTree)
+    fn icon(&self, _: &WindowContext) -> Option<ui::IconName> {
+        Some(ui::IconName::FileTree)
     }
 
     fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {

crates/quick_action_bar/src/quick_action_bar.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
     Subscription, View, ViewContext, WeakView,
 };
 use search::{buffer_search, BufferSearchBar};
-use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip};
+use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip};
 use workspace::{
     item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
 };
@@ -43,7 +43,7 @@ impl Render for QuickActionBar {
 
         let inlay_hints_button = Some(QuickActionBarButton::new(
             "toggle inlay hints",
-            Icon::InlayHint,
+            IconName::InlayHint,
             editor.read(cx).inlay_hints_enabled(),
             Box::new(editor::ToggleInlayHints),
             "Toggle Inlay Hints",
@@ -60,7 +60,7 @@ impl Render for QuickActionBar {
 
         let search_button = Some(QuickActionBarButton::new(
             "toggle buffer search",
-            Icon::MagnifyingGlass,
+            IconName::MagnifyingGlass,
             !self.buffer_search_bar.read(cx).is_dismissed(),
             Box::new(buffer_search::Deploy { focus: false }),
             "Buffer Search",
@@ -77,7 +77,7 @@ impl Render for QuickActionBar {
 
         let assistant_button = QuickActionBarButton::new(
             "toggle inline assistant",
-            Icon::MagicWand,
+            IconName::MagicWand,
             false,
             Box::new(InlineAssist),
             "Inline Assist",
@@ -107,7 +107,7 @@ impl EventEmitter<ToolbarItemEvent> for QuickActionBar {}
 #[derive(IntoElement)]
 struct QuickActionBarButton {
     id: ElementId,
-    icon: Icon,
+    icon: IconName,
     toggled: bool,
     action: Box<dyn Action>,
     tooltip: SharedString,
@@ -117,7 +117,7 @@ struct QuickActionBarButton {
 impl QuickActionBarButton {
     fn new(
         id: impl Into<ElementId>,
-        icon: Icon,
+        icon: IconName,
         toggled: bool,
         action: Box<dyn Action>,
         tooltip: impl Into<SharedString>,

crates/rope/src/rope.rs 🔗

@@ -25,10 +25,10 @@ const CHUNK_BASE: usize = 6;
 #[cfg(not(test))]
 const CHUNK_BASE: usize = 16;
 
-/// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances
+/// Type alias to [`HashMatrix`], an implementation of a homomorphic hash function. Two [`Rope`] instances
 /// containing the same text will produce the same fingerprint. This hash function is special in that
-/// it allows us to hash individual chunks and aggregate them up the [Rope]'s tree, with the resulting
-/// hash being equivalent to hashing all the text contained in the [Rope] at once.
+/// it allows us to hash individual chunks and aggregate them up the [`Rope`]'s tree, with the resulting
+/// hash being equivalent to hashing all the text contained in the [`Rope`] at once.
 pub type RopeFingerprint = HashMatrix;
 
 #[derive(Clone, Default)]

crates/rpc/src/notification.rs 🔗

@@ -76,30 +76,35 @@ impl Notification {
     }
 }
 
-#[test]
-fn test_notification() {
-    // Notifications can be serialized and deserialized.
-    for notification in [
-        Notification::ContactRequest { sender_id: 1 },
-        Notification::ContactRequestAccepted { responder_id: 2 },
-        Notification::ChannelInvitation {
-            channel_id: 100,
-            channel_name: "the-channel".into(),
-            inviter_id: 50,
-        },
-        Notification::ChannelMessageMention {
-            sender_id: 200,
-            channel_id: 30,
-            message_id: 1,
-        },
-    ] {
-        let message = notification.to_proto();
-        let deserialized = Notification::from_proto(&message).unwrap();
-        assert_eq!(deserialized, notification);
-    }
+#[cfg(test)]
+mod tests {
+    use crate::Notification;
+
+    #[test]
+    fn test_notification() {
+        // Notifications can be serialized and deserialized.
+        for notification in [
+            Notification::ContactRequest { sender_id: 1 },
+            Notification::ContactRequestAccepted { responder_id: 2 },
+            Notification::ChannelInvitation {
+                channel_id: 100,
+                channel_name: "the-channel".into(),
+                inviter_id: 50,
+            },
+            Notification::ChannelMessageMention {
+                sender_id: 200,
+                channel_id: 30,
+                message_id: 1,
+            },
+        ] {
+            let message = notification.to_proto();
+            let deserialized = Notification::from_proto(&message).unwrap();
+            assert_eq!(deserialized, notification);
+        }
 
-    // When notifications are serialized, the `kind` and `actor_id` fields are
-    // stored separately, and do not appear redundantly in the JSON.
-    let notification = Notification::ContactRequest { sender_id: 1 };
-    assert_eq!(notification.to_proto().content, "{}");
+        // When notifications are serialized, the `kind` and `actor_id` fields are
+        // stored separately, and do not appear redundantly in the JSON.
+        let notification = Notification::ContactRequest { sender_id: 1 };
+        assert_eq!(notification.to_proto().content, "{}");
+    }
 }

crates/search/src/buffer_search.rs 🔗

@@ -21,7 +21,7 @@ use settings::Settings;
 use std::{any::Any, sync::Arc};
 use theme::ThemeSettings;
 
-use ui::{h_stack, prelude::*, Icon, IconButton, IconElement, ToggleButton, Tooltip};
+use ui::{h_stack, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip};
 use util::ResultExt;
 use workspace::{
     item::ItemHandle,
@@ -43,7 +43,7 @@ pub enum Event {
 }
 
 pub fn init(cx: &mut AppContext) {
-    cx.observe_new_views(|editor: &mut Workspace, _| BufferSearchBar::register(editor))
+    cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace))
         .detach();
 }
 
@@ -225,7 +225,7 @@ impl Render for BufferSearchBar {
                     .border_color(editor_border)
                     .min_w(rems(384. / 16.))
                     .rounded_lg()
-                    .child(IconElement::new(Icon::MagnifyingGlass))
+                    .child(Icon::new(IconName::MagnifyingGlass))
                     .child(self.render_text_input(&self.query_editor, cx))
                     .children(supported_options.case.then(|| {
                         self.render_search_option_button(
@@ -287,7 +287,7 @@ impl Render for BufferSearchBar {
                         this.child(
                             IconButton::new(
                                 "buffer-search-bar-toggle-replace-button",
-                                Icon::Replace,
+                                IconName::Replace,
                             )
                             .style(ButtonStyle::Subtle)
                             .when(self.replace_enabled, |button| {
@@ -323,7 +323,7 @@ impl Render for BufferSearchBar {
                         )
                         .when(should_show_replace_input, |this| {
                             this.child(
-                                IconButton::new("search-replace-next", ui::Icon::ReplaceNext)
+                                IconButton::new("search-replace-next", ui::IconName::ReplaceNext)
                                     .tooltip(move |cx| {
                                         Tooltip::for_action("Replace next", &ReplaceNext, cx)
                                     })
@@ -332,7 +332,7 @@ impl Render for BufferSearchBar {
                                     })),
                             )
                             .child(
-                                IconButton::new("search-replace-all", ui::Icon::ReplaceAll)
+                                IconButton::new("search-replace-all", ui::IconName::ReplaceAll)
                                     .tooltip(move |cx| {
                                         Tooltip::for_action("Replace all", &ReplaceAll, cx)
                                     })
@@ -350,7 +350,7 @@ impl Render for BufferSearchBar {
                     .gap_0p5()
                     .flex_none()
                     .child(
-                        IconButton::new("select-all", ui::Icon::SelectAll)
+                        IconButton::new("select-all", ui::IconName::SelectAll)
                             .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
                             .tooltip(|cx| {
                                 Tooltip::for_action("Select all matches", &SelectAllMatches, cx)
@@ -358,13 +358,13 @@ impl Render for BufferSearchBar {
                     )
                     .children(match_count)
                     .child(render_nav_button(
-                        ui::Icon::ChevronLeft,
+                        ui::IconName::ChevronLeft,
                         self.active_match_index.is_some(),
                         "Select previous match",
                         &SelectPrevMatch,
                     ))
                     .child(render_nav_button(
-                        ui::Icon::ChevronRight,
+                        ui::IconName::ChevronRight,
                         self.active_match_index.is_some(),
                         "Select next match",
                         &SelectNextMatch,
@@ -479,6 +479,11 @@ impl SearchActionsRegistrar for Workspace {
         callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
     ) {
         self.register_action(move |workspace, action: &A, cx| {
+            if workspace.has_active_modal(cx) {
+                cx.propagate();
+                return;
+            }
+
             let pane = workspace.active_pane();
             pane.update(cx, move |this, cx| {
                 this.toolbar().update(cx, move |this, cx| {
@@ -539,11 +544,11 @@ impl BufferSearchBar {
             this.select_all_matches(action, cx);
         });
         registrar.register_handler(|this, _: &editor::Cancel, cx| {
-            if !this.dismissed {
+            if this.dismissed {
+                cx.propagate();
+            } else {
                 this.dismiss(&Dismiss, cx);
-                return;
             }
-            cx.propagate();
         });
         registrar.register_handler(|this, deploy, cx| {
             this.deploy(deploy, cx);

crates/search/src/project_search.rs 🔗

@@ -38,7 +38,7 @@ use std::{
 use theme::ThemeSettings;
 
 use ui::{
-    h_stack, prelude::*, v_stack, Icon, IconButton, IconElement, Label, LabelCommon, LabelSize,
+    h_stack, prelude::*, v_stack, Icon, IconButton, IconName, Label, LabelCommon, LabelSize,
     Selectable, ToggleButton, Tooltip,
 };
 use util::{paths::PathMatcher, ResultExt as _};
@@ -424,7 +424,8 @@ impl Item for ProjectSearchView {
             .current()
             .as_ref()
             .map(|query| {
-                let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN);
+                let query = query.replace('\n', "");
+                let query_text = util::truncate_and_trailoff(&query, MAX_TAB_TITLE_LEN);
                 query_text.into()
             });
         let tab_name = last_query
@@ -432,7 +433,7 @@ impl Item for ProjectSearchView {
             .unwrap_or_else(|| "Project search".into());
         h_stack()
             .gap_2()
-            .child(IconElement::new(Icon::MagnifyingGlass).color(if selected {
+            .child(Icon::new(IconName::MagnifyingGlass).color(if selected {
                 Color::Default
             } else {
                 Color::Muted
@@ -1616,12 +1617,12 @@ impl Render for ProjectSearchBar {
                 .on_action(cx.listener(|this, action, cx| this.confirm(action, cx)))
                 .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx)))
                 .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx)))
-                .child(IconElement::new(Icon::MagnifyingGlass))
+                .child(Icon::new(IconName::MagnifyingGlass))
                 .child(self.render_text_input(&search.query_editor, cx))
                 .child(
                     h_stack()
                         .child(
-                            IconButton::new("project-search-filter-button", Icon::Filter)
+                            IconButton::new("project-search-filter-button", IconName::Filter)
                                 .tooltip(|cx| {
                                     Tooltip::for_action("Toggle filters", &ToggleFilters, cx)
                                 })
@@ -1639,7 +1640,7 @@ impl Render for ProjectSearchBar {
                             this.child(
                                 IconButton::new(
                                     "project-search-case-sensitive",
-                                    Icon::CaseSensitive,
+                                    IconName::CaseSensitive,
                                 )
                                 .tooltip(|cx| {
                                     Tooltip::for_action(
@@ -1659,7 +1660,7 @@ impl Render for ProjectSearchBar {
                                 )),
                             )
                             .child(
-                                IconButton::new("project-search-whole-word", Icon::WholeWord)
+                                IconButton::new("project-search-whole-word", IconName::WholeWord)
                                     .tooltip(|cx| {
                                         Tooltip::for_action(
                                             "Toggle whole word",
@@ -1738,7 +1739,7 @@ impl Render for ProjectSearchBar {
                         }),
                 )
                 .child(
-                    IconButton::new("project-search-toggle-replace", Icon::Replace)
+                    IconButton::new("project-search-toggle-replace", IconName::Replace)
                         .on_click(cx.listener(|this, _, cx| {
                             this.toggle_replace(&ToggleReplace, cx);
                         }))
@@ -1755,7 +1756,7 @@ impl Render for ProjectSearchBar {
                 .border_1()
                 .border_color(cx.theme().colors().border)
                 .rounded_lg()
-                .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small))
+                .child(Icon::new(IconName::Replace).size(ui::IconSize::Small))
                 .child(self.render_text_input(&search.replacement_editor, cx))
         } else {
             // Fill out the space if we don't have a replacement editor.
@@ -1764,7 +1765,7 @@ impl Render for ProjectSearchBar {
         let actions_column = h_stack()
             .when(search.replace_enabled, |this| {
                 this.child(
-                    IconButton::new("project-search-replace-next", Icon::ReplaceNext)
+                    IconButton::new("project-search-replace-next", IconName::ReplaceNext)
                         .on_click(cx.listener(|this, _, cx| {
                             if let Some(search) = this.active_project_search.as_ref() {
                                 search.update(cx, |this, cx| {
@@ -1775,7 +1776,7 @@ impl Render for ProjectSearchBar {
                         .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)),
                 )
                 .child(
-                    IconButton::new("project-search-replace-all", Icon::ReplaceAll)
+                    IconButton::new("project-search-replace-all", IconName::ReplaceAll)
                         .on_click(cx.listener(|this, _, cx| {
                             if let Some(search) = this.active_project_search.as_ref() {
                                 search.update(cx, |this, cx| {
@@ -1796,7 +1797,7 @@ impl Render for ProjectSearchBar {
                 this
             })
             .child(
-                IconButton::new("project-search-prev-match", Icon::ChevronLeft)
+                IconButton::new("project-search-prev-match", IconName::ChevronLeft)
                     .disabled(search.active_match_index.is_none())
                     .on_click(cx.listener(|this, _, cx| {
                         if let Some(search) = this.active_project_search.as_ref() {
@@ -1810,7 +1811,7 @@ impl Render for ProjectSearchBar {
                     }),
             )
             .child(
-                IconButton::new("project-search-next-match", Icon::ChevronRight)
+                IconButton::new("project-search-next-match", IconName::ChevronRight)
                     .disabled(search.active_match_index.is_none())
                     .on_click(cx.listener(|this, _, cx| {
                         if let Some(search) = this.active_project_search.as_ref() {

crates/search/src/search.rs 🔗

@@ -60,11 +60,11 @@ impl SearchOptions {
         }
     }
 
-    pub fn icon(&self) -> ui::Icon {
+    pub fn icon(&self) -> ui::IconName {
         match *self {
-            SearchOptions::WHOLE_WORD => ui::Icon::WholeWord,
-            SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive,
-            SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit,
+            SearchOptions::WHOLE_WORD => ui::IconName::WholeWord,
+            SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive,
+            SearchOptions::INCLUDE_IGNORED => ui::IconName::FileGit,
             _ => panic!("{:?} is not a named SearchOption", self),
         }
     }

crates/search/src/search_bar.rs 🔗

@@ -3,7 +3,7 @@ use ui::IconButton;
 use ui::{prelude::*, Tooltip};
 
 pub(super) fn render_nav_button(
-    icon: ui::Icon,
+    icon: ui::IconName,
     active: bool,
     tooltip: &'static str,
     action: &'static dyn Action,

crates/storybook/Cargo.toml 🔗

@@ -14,6 +14,7 @@ anyhow.workspace = true
 backtrace-on-stack-overflow = "0.3.0"
 chrono = "0.4"
 clap = { version = "4.4", features = ["derive", "string"] }
+collab_ui = { path = "../collab_ui", features = ["stories"] }
 strum = { version = "0.25.0", features = ["derive"] }
 dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
 editor = { path = "../editor" }

crates/storybook/src/story_selector.rs 🔗

@@ -16,6 +16,7 @@ pub enum ComponentStory {
     Avatar,
     Button,
     Checkbox,
+    CollabNotification,
     ContextMenu,
     Cursor,
     Disclosure,
@@ -45,6 +46,9 @@ impl ComponentStory {
             Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(),
             Self::Button => cx.new_view(|_| ui::ButtonStory).into(),
             Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(),
+            Self::CollabNotification => cx
+                .new_view(|_| collab_ui::notifications::CollabNotificationStory)
+                .into(),
             Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(),
             Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(),
             Self::Disclosure => cx.new_view(|_| ui::DisclosureStory).into(),

crates/terminal_view/src/terminal_element.rs 🔗

@@ -451,6 +451,18 @@ impl TerminalElement {
             }
         });
 
+        let interactive_text_bounds = InteractiveBounds {
+            bounds,
+            stacking_order: cx.stacking_order().clone(),
+        };
+        if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
+            if self.can_navigate_to_selected_word && last_hovered_word.is_some() {
+                cx.set_cursor_style(gpui::CursorStyle::PointingHand)
+            } else {
+                cx.set_cursor_style(gpui::CursorStyle::IBeam)
+            }
+        }
+
         let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {
             div()
                 .size_full()

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -19,7 +19,7 @@ use workspace::{
     dock::{DockPosition, Panel, PanelEvent},
     item::Item,
     pane,
-    ui::Icon,
+    ui::IconName,
     DraggedTab, Pane, Workspace,
 };
 
@@ -71,7 +71,7 @@ impl TerminalPanel {
                 h_stack()
                     .gap_2()
                     .child(
-                        IconButton::new("plus", Icon::Plus)
+                        IconButton::new("plus", IconName::Plus)
                             .icon_size(IconSize::Small)
                             .on_click(move |_, cx| {
                                 terminal_panel
@@ -82,10 +82,10 @@ impl TerminalPanel {
                     )
                     .child({
                         let zoomed = pane.is_zoomed();
-                        IconButton::new("toggle_zoom", Icon::Maximize)
+                        IconButton::new("toggle_zoom", IconName::Maximize)
                             .icon_size(IconSize::Small)
                             .selected(zoomed)
-                            .selected_icon(Icon::Minimize)
+                            .selected_icon(IconName::Minimize)
                             .on_click(cx.listener(|pane, _, cx| {
                                 pane.toggle_zoom(&workspace::ToggleZoom, cx);
                             }))
@@ -477,8 +477,8 @@ impl Panel for TerminalPanel {
         "TerminalPanel"
     }
 
-    fn icon(&self, _cx: &WindowContext) -> Option<Icon> {
-        Some(Icon::Terminal)
+    fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
+        Some(IconName::Terminal)
     }
 
     fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {

crates/terminal_view/src/terminal_view.rs 🔗

@@ -20,7 +20,7 @@ use terminal::{
     Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal,
 };
 use terminal_element::TerminalElement;
-use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label};
+use ui::{h_stack, prelude::*, ContextMenu, Icon, IconName, Label};
 use util::{paths::PathLikeWithPosition, ResultExt};
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent},
@@ -690,7 +690,7 @@ impl Item for TerminalView {
         let title = self.terminal().read(cx).title(true);
         h_stack()
             .gap_2()
-            .child(IconElement::new(Icon::Terminal))
+            .child(Icon::new(IconName::Terminal))
             .child(Label::new(title).color(if selected {
                 Color::Default
             } else {

crates/text/src/anchor.rs 🔗

@@ -90,7 +90,7 @@ impl Anchor {
         content.summary_for_anchor(self)
     }
 
-    /// Returns true when the [Anchor] is located inside a visible fragment.
+    /// Returns true when the [`Anchor`] is located inside a visible fragment.
     pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool {
         if *self == Anchor::MIN || *self == Anchor::MAX {
             true

crates/theme/src/scale.rs 🔗

@@ -2,7 +2,9 @@ use gpui::{AppContext, Hsla, SharedString};
 
 use crate::{ActiveTheme, Appearance};
 
-/// A one-based step in a [`ColorScale`].
+/// A collection of colors that are used to style the UI.
+///
+/// Each step has a semantic meaning, and is used to style different parts of the UI.
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
 pub struct ColorScaleStep(usize);
 
@@ -37,6 +39,10 @@ impl ColorScaleStep {
     ];
 }
 
+/// A scale of colors for a given [`ColorScaleSet`].
+///
+/// Each [`ColorScale`] contains exactly 12 colors. Refer to
+/// [`ColorScaleStep`] for a reference of what each step is used for.
 pub struct ColorScale(Vec<Hsla>);
 
 impl FromIterator<Hsla> for ColorScale {
@@ -229,6 +235,7 @@ impl IntoIterator for ColorScales {
     }
 }
 
+/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale.
 pub struct ColorScaleSet {
     name: SharedString,
     light: ColorScale,

crates/theme/src/settings.rs 🔗

@@ -27,7 +27,7 @@ pub struct ThemeSettings {
 }
 
 #[derive(Default)]
-pub struct AdjustedBufferFontSize(Pixels);
+pub(crate) struct AdjustedBufferFontSize(Pixels);
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 pub struct ThemeSettingsContent {

crates/theme/src/styles/colors.rs 🔗

@@ -2,11 +2,12 @@ use gpui::Hsla;
 use refineable::Refineable;
 use std::sync::Arc;
 
-use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors};
+use crate::{PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, SystemColors};
 
 #[derive(Refineable, Clone, Debug)]
 #[refineable(Debug, serde::Deserialize)]
 pub struct ThemeColors {
+    /// Border color. Used for most borders, is usually a high contrast color.
     pub border: Hsla,
     /// Border color. Used for deemphasized borders, like a visual divider between two sections
     pub border_variant: Hsla,
@@ -219,6 +220,8 @@ pub struct ThemeStyles {
 
     #[refineable]
     pub colors: ThemeColors,
+
+    #[refineable]
     pub status: StatusColors,
     pub player: PlayerColors,
     pub syntax: Arc<SyntaxTheme>,

crates/theme/src/styles/status.rs 🔗

@@ -78,15 +78,6 @@ pub struct StatusColors {
     pub warning_border: Hsla,
 }
 
-impl Default for StatusColors {
-    /// Don't use this!
-    /// We have to have a default to be `[refineable::Refinable]`.
-    /// todo!("Find a way to not need this for Refinable")
-    fn default() -> Self {
-        Self::dark()
-    }
-}
-
 pub struct DiagnosticColors {
     pub error: Hsla,
     pub warning: Hsla,

crates/theme/src/theme.rs 🔗

@@ -1,3 +1,11 @@
+//! # Theme
+//!
+//! This crate provides the theme system for Zed.
+//!
+//! ## Overview
+//!
+//! A theme is a collection of colors used to build a consistent appearance for UI components across the application.
+
 mod default_colors;
 mod default_theme;
 mod one_themes;

crates/theme/theme.md 🔗

@@ -0,0 +1,15 @@
+ # Theme
+
+ This crate provides the theme system for Zed.
+
+ ## Overview
+
+ A theme is a collection of colors used to build a consistent appearance for UI components across the application.
+ To produce a theme in Zed,
+
+ A theme is made of of two parts: A [ThemeFamily] and one or more [Theme]s.
+
+//
+ A [ThemeFamily] contains metadata like theme name, author, and theme-specific [ColorScales] as well as a series of themes.
+
+ - [ThemeColors] - A set of colors that are used to style the UI. Refer to the [ThemeColors] documentation for more information.

crates/ui/src/clickable.rs 🔗

@@ -1,6 +1,6 @@
 use gpui::{ClickEvent, WindowContext};
 
-/// A trait for elements that can be clicked.
+/// A trait for elements that can be clicked. Enables the use of the `on_click` method.
 pub trait Clickable {
     /// Sets the click handler that will fire whenever the element is clicked.
     fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self;

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

@@ -1,13 +1,26 @@
 use crate::prelude::*;
 use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled};
 
+/// The shape of an [`Avatar`].
 #[derive(Debug, Default, PartialEq, Clone)]
-pub enum Shape {
+pub enum AvatarShape {
+    /// The avatar is shown in a circle.
     #[default]
     Circle,
+    /// The avatar is shown in a rectangle with rounded corners.
     RoundedRectangle,
 }
 
+/// An element that renders a user avatar with customizable appearance options.
+///
+/// # Examples
+///
+/// ```
+/// Avatar::new("path/to/image.png")
+///     .shape(AvatarShape::Circle)
+///     .grayscale(true)
+///     .border_color(cx.theme().colors().border)
+/// ```
 #[derive(IntoElement)]
 pub struct Avatar {
     image: Img,
@@ -18,7 +31,7 @@ pub struct Avatar {
 impl RenderOnce for Avatar {
     fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {
         if self.image.style().corner_radii.top_left.is_none() {
-            self = self.shape(Shape::Circle);
+            self = self.shape(AvatarShape::Circle);
         }
 
         let size = cx.rem_size();
@@ -66,14 +79,31 @@ impl Avatar {
         }
     }
 
-    pub fn shape(mut self, shape: Shape) -> Self {
+    /// Sets the shape of the avatar image.
+    ///
+    /// This method allows the shape of the avatar to be specified using a [`Shape`].
+    /// It modifies the corner radius of the image to match the specified shape.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle);
+    /// ```
+    pub fn shape(mut self, shape: AvatarShape) -> Self {
         self.image = match shape {
-            Shape::Circle => self.image.rounded_full(),
-            Shape::RoundedRectangle => self.image.rounded_md(),
+            AvatarShape::Circle => self.image.rounded_full(),
+            AvatarShape::RoundedRectangle => self.image.rounded_md(),
         };
         self
     }
 
+    /// Applies a grayscale filter to the avatar image.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// let avatar = Avatar::new("path/to/image.png").grayscale(true);
+    /// ```
     pub fn grayscale(mut self, grayscale: bool) -> Self {
         self.image = self.image.grayscale(grayscale);
         self

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

@@ -2,11 +2,69 @@ use gpui::{AnyView, DefiniteLength};
 
 use crate::{prelude::*, IconPosition, KeyBinding};
 use crate::{
-    ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle,
+    ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle,
 };
 
 use super::button_icon::ButtonIcon;
 
+/// An element that creates a button with a label and an optional icon.
+///
+/// Common buttons:
+/// - Label, Icon + Label: [`Button`] (this component)
+/// - Icon only: [`IconButton`]
+/// - Custom: [`ButtonLike`]
+///
+/// To create a more complex button than what the [`Button`] or [`IconButton`] components provide, use
+/// [`ButtonLike`] directly.
+///
+/// # Examples
+///
+/// **A button with a label**, is typically used in scenarios such as a form, where the button's label
+/// indicates what action will be performed when the button is clicked.
+///
+/// ```
+/// Button::new("button_id", "Click me!")
+///     .on_click(|event, cx| {
+///         // Handle click event
+///     });
+/// ```
+///
+/// **A toggleable button**, is typically used in scenarios such as a toolbar,
+/// where the button's state indicates whether a feature is enabled or not, or
+/// a trigger for a popover menu, where clicking the button toggles the visibility of the menu.
+///
+/// ```
+/// Button::new("button_id", "Click me!")
+///     .icon(IconName::Check)
+///     .selected(some_bool)
+///     .on_click(|event, cx| {
+///         // Handle click event
+///     });
+/// ```
+///
+/// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method.
+///
+/// ```
+/// Button::new("button_id", "Click me!")
+///     .selected(some_bool)
+///     .selected_style(ButtonStyle::Tinted(TintColor::Accent))
+///     .on_click(|event, cx| {
+///         // Handle click event
+///     });
+/// ```
+/// This will create a button with a blue tinted background when selected.
+///
+/// **A full-width button**, is typically used in scenarios such as the bottom of a modal or form, where it occupies the entire width of its container.
+/// The button's content, including text and icons, is centered by default.
+///
+/// ```
+/// let button = Button::new("button_id", "Click me!")
+///     .full_width()
+///     .on_click(|event, cx| {
+///         // Handle click event
+///     });
+/// ```
+///
 #[derive(IntoElement)]
 pub struct Button {
     base: ButtonLike,
@@ -14,15 +72,21 @@ pub struct Button {
     label_color: Option<Color>,
     label_size: Option<LabelSize>,
     selected_label: Option<SharedString>,
-    icon: Option<Icon>,
+    icon: Option<IconName>,
     icon_position: Option<IconPosition>,
     icon_size: Option<IconSize>,
     icon_color: Option<Color>,
-    selected_icon: Option<Icon>,
+    selected_icon: Option<IconName>,
     key_binding: Option<KeyBinding>,
 }
 
 impl Button {
+    /// Creates a new [`Button`] with a specified identifier and label.
+    ///
+    /// This is the primary constructor for a [`Button`] component. It initializes
+    /// the button with the provided identifier and label text, setting all other
+    /// properties to their default values, which can be customized using the
+    /// builder pattern methods provided by this struct.
     pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
         Self {
             base: ButtonLike::new(id),
@@ -39,46 +103,55 @@ impl Button {
         }
     }
 
+    /// Sets the color of the button's label.
     pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
         self.label_color = label_color.into();
         self
     }
 
+    /// Defines the size of the button's label.
     pub fn label_size(mut self, label_size: impl Into<Option<LabelSize>>) -> Self {
         self.label_size = label_size.into();
         self
     }
 
+    /// Sets the label used when the button is in a selected state.
     pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
         self.selected_label = label.into().map(Into::into);
         self
     }
 
-    pub fn icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+    /// Assigns an icon to the button.
+    pub fn icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
         self.icon = icon.into();
         self
     }
 
+    /// Sets the position of the icon relative to the label.
     pub fn icon_position(mut self, icon_position: impl Into<Option<IconPosition>>) -> Self {
         self.icon_position = icon_position.into();
         self
     }
 
+    /// Specifies the size of the button's icon.
     pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {
         self.icon_size = icon_size.into();
         self
     }
 
+    /// Sets the color of the button's icon.
     pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {
         self.icon_color = icon_color.into();
         self
     }
 
-    pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+    /// Chooses an icon to display when the button is in a selected state.
+    pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
         self.selected_icon = icon.into();
         self
     }
 
+    /// Binds a key combination to the button for keyboard shortcuts.
     pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
         self.key_binding = key_binding.into();
         self
@@ -86,6 +159,22 @@ impl Button {
 }
 
 impl Selectable for Button {
+    /// Sets the selected state of the button.
+    ///
+    /// This method allows the selection state of the button to be specified.
+    /// It modifies the button's appearance to reflect its selected state.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// Button::new("button_id", "Click me!")
+    ///     .selected(true)
+    ///     .on_click(|event, cx| {
+    ///         // Handle click event
+    ///     });
+    /// ```
+    ///
+    /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected.
     fn selected(mut self, selected: bool) -> Self {
         self.base = self.base.selected(selected);
         self
@@ -93,6 +182,19 @@ impl Selectable for Button {
 }
 
 impl SelectableButton for Button {
+    /// Sets the style for the button when selected.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// Button::new("button_id", "Click me!")
+    ///     .selected(true)
+    ///     .selected_style(ButtonStyle::Tinted(TintColor::Accent))
+    ///     .on_click(|event, cx| {
+    ///         // Handle click event
+    ///     });
+    /// ```
+    /// This results in a button with a blue tinted background when selected.
     fn selected_style(mut self, style: ButtonStyle) -> Self {
         self.base = self.base.selected_style(style);
         self
@@ -100,6 +202,22 @@ impl SelectableButton for Button {
 }
 
 impl Disableable for Button {
+    /// Disables the button.
+    ///
+    /// This method allows the button to be disabled. When a button is disabled,
+    /// it doesn't react to user interactions and its appearance is updated to reflect this.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// Button::new("button_id", "Click me!")
+    ///     .disabled(true)
+    ///     .on_click(|event, cx| {
+    ///         // Handle click event
+    ///     });
+    /// ```
+    ///
+    /// This results in a button that is disabled and does not respond to click events.
     fn disabled(mut self, disabled: bool) -> Self {
         self.base = self.base.disabled(disabled);
         self
@@ -107,6 +225,7 @@ impl Disableable for Button {
 }
 
 impl Clickable for Button {
+    /// Sets the click event handler for the button.
     fn on_click(
         mut self,
         handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
@@ -117,11 +236,40 @@ impl Clickable for Button {
 }
 
 impl FixedWidth for Button {
+    /// Sets a fixed width for the button.
+    ///
+    /// This function allows a button to have a fixed width instead of automatically growing or shrinking.
+    /// Sets a fixed width for the button.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// Button::new("button_id", "Click me!")
+    ///     .width(DefiniteLength::Pixels(100))
+    ///     .on_click(|event, cx| {
+    ///         // Handle click event
+    ///     });
+    /// ```
+    ///
+    /// This sets the button's width to be exactly 100 pixels.
     fn width(mut self, width: DefiniteLength) -> Self {
         self.base = self.base.width(width);
         self
     }
 
+    /// Sets the button to occupy the full width of its container.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// Button::new("button_id", "Click me!")
+    ///     .full_width()
+    ///     .on_click(|event, cx| {
+    ///         // Handle click event
+    ///     });
+    /// ```
+    ///
+    /// This stretches the button to the full width of its container.
     fn full_width(mut self) -> Self {
         self.base = self.base.full_width();
         self
@@ -129,20 +277,42 @@ impl FixedWidth for Button {
 }
 
 impl ButtonCommon for Button {
+    /// Sets the button's id.
     fn id(&self) -> &ElementId {
         self.base.id()
     }
 
+    /// Sets the visual style of the button using a [`ButtonStyle`].
     fn style(mut self, style: ButtonStyle) -> Self {
         self.base = self.base.style(style);
         self
     }
 
+    /// Sets the button's size using a [`ButtonSize`].
     fn size(mut self, size: ButtonSize) -> Self {
         self.base = self.base.size(size);
         self
     }
 
+    /// Sets a tooltip for the button.
+    ///
+    /// This method allows a tooltip to be set for the button. The tooltip is a function that
+    /// takes a mutable reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip
+    /// is displayed when the user hovers over the button.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// Button::new("button_id", "Click me!")
+    ///     .tooltip(|cx| {
+    ///         Text::new("This is a tooltip").into()
+    ///     })
+    ///     .on_click(|event, cx| {
+    ///         // Handle click event
+    ///     });
+    /// ```
+    ///
+    /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over.
     fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
         self.base = self.base.tooltip(tooltip);
         self

crates/ui/src/components/button/button_icon.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{prelude::*, Icon, IconElement, IconSize};
+use crate::{prelude::*, Icon, IconName, IconSize};
 
 /// An icon that appears within a button.
 ///
@@ -6,17 +6,17 @@ use crate::{prelude::*, Icon, IconElement, IconSize};
 /// or as a standalone icon, like in [`IconButton`](crate::IconButton).
 #[derive(IntoElement)]
 pub(super) struct ButtonIcon {
-    icon: Icon,
+    icon: IconName,
     size: IconSize,
     color: Color,
     disabled: bool,
     selected: bool,
-    selected_icon: Option<Icon>,
+    selected_icon: Option<IconName>,
     selected_style: Option<ButtonStyle>,
 }
 
 impl ButtonIcon {
-    pub fn new(icon: Icon) -> Self {
+    pub fn new(icon: IconName) -> Self {
         Self {
             icon,
             size: IconSize::default(),
@@ -44,7 +44,7 @@ impl ButtonIcon {
         self
     }
 
-    pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+    pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
         self.selected_icon = icon.into();
         self
     }
@@ -88,6 +88,6 @@ impl RenderOnce for ButtonIcon {
             self.color
         };
 
-        IconElement::new(icon).size(self.size).color(icon_color)
+        Icon::new(icon).size(self.size).color(icon_color)
     }
 }

crates/ui/src/components/button/button_like.rs 🔗

@@ -4,10 +4,12 @@ use smallvec::SmallVec;
 
 use crate::prelude::*;
 
+/// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected.
 pub trait SelectableButton: Selectable {
     fn selected_style(self, style: ButtonStyle) -> Self;
 }
 
+/// A common set of traits all buttons must implement.
 pub trait ButtonCommon: Clickable + Disableable {
     /// A unique element ID to identify the button.
     fn id(&self) -> &ElementId;
@@ -93,6 +95,7 @@ impl From<ButtonStyle> for Color {
     }
 }
 
+/// The visual appearance of a button.
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
 pub enum ButtonStyle {
     /// A filled button with a solid background color. Provides emphasis versus
@@ -260,8 +263,9 @@ impl ButtonStyle {
     }
 }
 
-/// ButtonSize can also be used to help build  non-button elements
-/// that are consistently sized with buttons.
+/// The height of a button.
+///
+/// Can also be used to size non-button elements to align with [`Button`]s.
 #[derive(Default, PartialEq, Clone, Copy)]
 pub enum ButtonSize {
     Large,

crates/ui/src/components/button/icon_button.rs 🔗

@@ -1,21 +1,21 @@
 use gpui::{AnyView, DefiniteLength};
 
 use crate::{prelude::*, SelectableButton};
-use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};
+use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize};
 
 use super::button_icon::ButtonIcon;
 
 #[derive(IntoElement)]
 pub struct IconButton {
     base: ButtonLike,
-    icon: Icon,
+    icon: IconName,
     icon_size: IconSize,
     icon_color: Color,
-    selected_icon: Option<Icon>,
+    selected_icon: Option<IconName>,
 }
 
 impl IconButton {
-    pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
+    pub fn new(id: impl Into<ElementId>, icon: IconName) -> Self {
         Self {
             base: ButtonLike::new(id),
             icon,
@@ -35,7 +35,7 @@ impl IconButton {
         self
     }
 
-    pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
+    pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
         self.selected_icon = icon.into();
         self
     }

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

@@ -1,7 +1,7 @@
 use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
 
 use crate::prelude::*;
-use crate::{Color, Icon, IconElement, Selection};
+use crate::{Color, Icon, IconName, Selection};
 
 pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;
 
@@ -47,7 +47,7 @@ impl RenderOnce for Checkbox {
         let group_id = format!("checkbox_group_{:?}", self.id);
 
         let icon = match self.checked {
-            Selection::Selected => Some(IconElement::new(Icon::Check).size(IconSize::Small).color(
+            Selection::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color(
                 if self.disabled {
                     Color::Disabled
                 } else {
@@ -55,7 +55,7 @@ impl RenderOnce for Checkbox {
                 },
             )),
             Selection::Indeterminate => Some(
-                IconElement::new(Icon::Dash)
+                Icon::new(IconName::Dash)
                     .size(IconSize::Small)
                     .color(if self.disabled {
                         Color::Disabled

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

@@ -1,6 +1,6 @@
 use crate::{
-    h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem,
-    ListSeparator, ListSubHeader,
+    h_stack, prelude::*, v_stack, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator,
+    ListSubHeader,
 };
 use gpui::{
     px, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
@@ -14,7 +14,7 @@ enum ContextMenuItem {
     Header(SharedString),
     Entry {
         label: SharedString,
-        icon: Option<Icon>,
+        icon: Option<IconName>,
         handler: Rc<dyn Fn(&mut WindowContext)>,
         action: Option<Box<dyn Action>>,
     },
@@ -117,7 +117,7 @@ impl ContextMenu {
             label: label.into(),
             action: Some(action.boxed_clone()),
             handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
-            icon: Some(Icon::Link),
+            icon: Some(IconName::Link),
         });
         self
     }
@@ -280,7 +280,7 @@ impl Render for ContextMenu {
                                 h_stack()
                                     .gap_1()
                                     .child(Label::new(label.clone()))
-                                    .child(IconElement::new(*icon))
+                                    .child(Icon::new(*icon))
                                     .into_any_element()
                             } else {
                                 Label::new(label.clone()).into_any_element()

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

@@ -1,6 +1,6 @@
 use gpui::ClickEvent;
 
-use crate::{prelude::*, Color, Icon, IconButton, IconSize};
+use crate::{prelude::*, Color, IconButton, IconName, IconSize};
 
 #[derive(IntoElement)]
 pub struct Disclosure {
@@ -32,8 +32,8 @@ impl RenderOnce for Disclosure {
         IconButton::new(
             self.id,
             match self.is_open {
-                true => Icon::ChevronDown,
-                false => Icon::ChevronRight,
+                true => IconName::ChevronDown,
+                false => IconName::ChevronRight,
             },
         )
         .icon_color(Color::Muted)

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

@@ -22,7 +22,7 @@ impl IconSize {
 }
 
 #[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
-pub enum Icon {
+pub enum IconName {
     Ai,
     ArrowDown,
     ArrowLeft,
@@ -111,118 +111,108 @@ pub enum Icon {
     ZedXCopilot,
 }
 
-impl Icon {
+impl IconName {
     pub fn path(self) -> &'static str {
         match self {
-            Icon::Ai => "icons/ai.svg",
-            Icon::ArrowDown => "icons/arrow_down.svg",
-            Icon::ArrowLeft => "icons/arrow_left.svg",
-            Icon::ArrowRight => "icons/arrow_right.svg",
-            Icon::ArrowUp => "icons/arrow_up.svg",
-            Icon::ArrowUpRight => "icons/arrow_up_right.svg",
-            Icon::ArrowCircle => "icons/arrow_circle.svg",
-            Icon::AtSign => "icons/at_sign.svg",
-            Icon::AudioOff => "icons/speaker_off.svg",
-            Icon::AudioOn => "icons/speaker_loud.svg",
-            Icon::Backspace => "icons/backspace.svg",
-            Icon::Bell => "icons/bell.svg",
-            Icon::BellOff => "icons/bell_off.svg",
-            Icon::BellRing => "icons/bell_ring.svg",
-            Icon::Bolt => "icons/bolt.svg",
-            Icon::CaseSensitive => "icons/case_insensitive.svg",
-            Icon::Check => "icons/check.svg",
-            Icon::ChevronDown => "icons/chevron_down.svg",
-            Icon::ChevronLeft => "icons/chevron_left.svg",
-            Icon::ChevronRight => "icons/chevron_right.svg",
-            Icon::ChevronUp => "icons/chevron_up.svg",
-            Icon::Close => "icons/x.svg",
-            Icon::Collab => "icons/user_group_16.svg",
-            Icon::Command => "icons/command.svg",
-            Icon::Control => "icons/control.svg",
-            Icon::Copilot => "icons/copilot.svg",
-            Icon::CopilotDisabled => "icons/copilot_disabled.svg",
-            Icon::CopilotError => "icons/copilot_error.svg",
-            Icon::CopilotInit => "icons/copilot_init.svg",
-            Icon::Copy => "icons/copy.svg",
-            Icon::Dash => "icons/dash.svg",
-            Icon::Delete => "icons/delete.svg",
-            Icon::Disconnected => "icons/disconnected.svg",
-            Icon::Ellipsis => "icons/ellipsis.svg",
-            Icon::Envelope => "icons/feedback.svg",
-            Icon::Escape => "icons/escape.svg",
-            Icon::ExclamationTriangle => "icons/warning.svg",
-            Icon::Exit => "icons/exit.svg",
-            Icon::ExternalLink => "icons/external_link.svg",
-            Icon::File => "icons/file.svg",
-            Icon::FileDoc => "icons/file_icons/book.svg",
-            Icon::FileGeneric => "icons/file_icons/file.svg",
-            Icon::FileGit => "icons/file_icons/git.svg",
-            Icon::FileLock => "icons/file_icons/lock.svg",
-            Icon::FileRust => "icons/file_icons/rust.svg",
-            Icon::FileToml => "icons/file_icons/toml.svg",
-            Icon::FileTree => "icons/project.svg",
-            Icon::Filter => "icons/filter.svg",
-            Icon::Folder => "icons/file_icons/folder.svg",
-            Icon::FolderOpen => "icons/file_icons/folder_open.svg",
-            Icon::FolderX => "icons/stop_sharing.svg",
-            Icon::Github => "icons/github.svg",
-            Icon::Hash => "icons/hash.svg",
-            Icon::InlayHint => "icons/inlay_hint.svg",
-            Icon::Link => "icons/link.svg",
-            Icon::MagicWand => "icons/magic_wand.svg",
-            Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
-            Icon::MailOpen => "icons/mail_open.svg",
-            Icon::Maximize => "icons/maximize.svg",
-            Icon::Menu => "icons/menu.svg",
-            Icon::MessageBubbles => "icons/conversations.svg",
-            Icon::Mic => "icons/mic.svg",
-            Icon::MicMute => "icons/mic_mute.svg",
-            Icon::Minimize => "icons/minimize.svg",
-            Icon::Option => "icons/option.svg",
-            Icon::PageDown => "icons/page_down.svg",
-            Icon::PageUp => "icons/page_up.svg",
-            Icon::Plus => "icons/plus.svg",
-            Icon::Public => "icons/public.svg",
-            Icon::Quote => "icons/quote.svg",
-            Icon::Replace => "icons/replace.svg",
-            Icon::ReplaceAll => "icons/replace_all.svg",
-            Icon::ReplaceNext => "icons/replace_next.svg",
-            Icon::Return => "icons/return.svg",
-            Icon::Screen => "icons/desktop.svg",
-            Icon::SelectAll => "icons/select_all.svg",
-            Icon::Shift => "icons/shift.svg",
-            Icon::Snip => "icons/snip.svg",
-            Icon::Space => "icons/space.svg",
-            Icon::Split => "icons/split.svg",
-            Icon::Tab => "icons/tab.svg",
-            Icon::Terminal => "icons/terminal.svg",
-            Icon::Update => "icons/update.svg",
-            Icon::WholeWord => "icons/word_search.svg",
-            Icon::XCircle => "icons/error.svg",
-            Icon::ZedXCopilot => "icons/zed_x_copilot.svg",
+            IconName::Ai => "icons/ai.svg",
+            IconName::ArrowDown => "icons/arrow_down.svg",
+            IconName::ArrowLeft => "icons/arrow_left.svg",
+            IconName::ArrowRight => "icons/arrow_right.svg",
+            IconName::ArrowUp => "icons/arrow_up.svg",
+            IconName::ArrowUpRight => "icons/arrow_up_right.svg",
+            IconName::ArrowCircle => "icons/arrow_circle.svg",
+            IconName::AtSign => "icons/at_sign.svg",
+            IconName::AudioOff => "icons/speaker_off.svg",
+            IconName::AudioOn => "icons/speaker_loud.svg",
+            IconName::Backspace => "icons/backspace.svg",
+            IconName::Bell => "icons/bell.svg",
+            IconName::BellOff => "icons/bell_off.svg",
+            IconName::BellRing => "icons/bell_ring.svg",
+            IconName::Bolt => "icons/bolt.svg",
+            IconName::CaseSensitive => "icons/case_insensitive.svg",
+            IconName::Check => "icons/check.svg",
+            IconName::ChevronDown => "icons/chevron_down.svg",
+            IconName::ChevronLeft => "icons/chevron_left.svg",
+            IconName::ChevronRight => "icons/chevron_right.svg",
+            IconName::ChevronUp => "icons/chevron_up.svg",
+            IconName::Close => "icons/x.svg",
+            IconName::Collab => "icons/user_group_16.svg",
+            IconName::Command => "icons/command.svg",
+            IconName::Control => "icons/control.svg",
+            IconName::Copilot => "icons/copilot.svg",
+            IconName::CopilotDisabled => "icons/copilot_disabled.svg",
+            IconName::CopilotError => "icons/copilot_error.svg",
+            IconName::CopilotInit => "icons/copilot_init.svg",
+            IconName::Copy => "icons/copy.svg",
+            IconName::Dash => "icons/dash.svg",
+            IconName::Delete => "icons/delete.svg",
+            IconName::Disconnected => "icons/disconnected.svg",
+            IconName::Ellipsis => "icons/ellipsis.svg",
+            IconName::Envelope => "icons/feedback.svg",
+            IconName::Escape => "icons/escape.svg",
+            IconName::ExclamationTriangle => "icons/warning.svg",
+            IconName::Exit => "icons/exit.svg",
+            IconName::ExternalLink => "icons/external_link.svg",
+            IconName::File => "icons/file.svg",
+            IconName::FileDoc => "icons/file_icons/book.svg",
+            IconName::FileGeneric => "icons/file_icons/file.svg",
+            IconName::FileGit => "icons/file_icons/git.svg",
+            IconName::FileLock => "icons/file_icons/lock.svg",
+            IconName::FileRust => "icons/file_icons/rust.svg",
+            IconName::FileToml => "icons/file_icons/toml.svg",
+            IconName::FileTree => "icons/project.svg",
+            IconName::Filter => "icons/filter.svg",
+            IconName::Folder => "icons/file_icons/folder.svg",
+            IconName::FolderOpen => "icons/file_icons/folder_open.svg",
+            IconName::FolderX => "icons/stop_sharing.svg",
+            IconName::Github => "icons/github.svg",
+            IconName::Hash => "icons/hash.svg",
+            IconName::InlayHint => "icons/inlay_hint.svg",
+            IconName::Link => "icons/link.svg",
+            IconName::MagicWand => "icons/magic_wand.svg",
+            IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
+            IconName::MailOpen => "icons/mail_open.svg",
+            IconName::Maximize => "icons/maximize.svg",
+            IconName::Menu => "icons/menu.svg",
+            IconName::MessageBubbles => "icons/conversations.svg",
+            IconName::Mic => "icons/mic.svg",
+            IconName::MicMute => "icons/mic_mute.svg",
+            IconName::Minimize => "icons/minimize.svg",
+            IconName::Option => "icons/option.svg",
+            IconName::PageDown => "icons/page_down.svg",
+            IconName::PageUp => "icons/page_up.svg",
+            IconName::Plus => "icons/plus.svg",
+            IconName::Public => "icons/public.svg",
+            IconName::Quote => "icons/quote.svg",
+            IconName::Replace => "icons/replace.svg",
+            IconName::ReplaceAll => "icons/replace_all.svg",
+            IconName::ReplaceNext => "icons/replace_next.svg",
+            IconName::Return => "icons/return.svg",
+            IconName::Screen => "icons/desktop.svg",
+            IconName::SelectAll => "icons/select_all.svg",
+            IconName::Shift => "icons/shift.svg",
+            IconName::Snip => "icons/snip.svg",
+            IconName::Space => "icons/space.svg",
+            IconName::Split => "icons/split.svg",
+            IconName::Tab => "icons/tab.svg",
+            IconName::Terminal => "icons/terminal.svg",
+            IconName::Update => "icons/update.svg",
+            IconName::WholeWord => "icons/word_search.svg",
+            IconName::XCircle => "icons/error.svg",
+            IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
         }
     }
 }
 
 #[derive(IntoElement)]
-pub struct IconElement {
+pub struct Icon {
     path: SharedString,
     color: Color,
     size: IconSize,
 }
 
-impl RenderOnce for IconElement {
-    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
-        svg()
-            .size(self.size.rems())
-            .flex_none()
-            .path(self.path)
-            .text_color(self.color.color(cx))
-    }
-}
-
-impl IconElement {
-    pub fn new(icon: Icon) -> Self {
+impl Icon {
+    pub fn new(icon: IconName) -> Self {
         Self {
             path: icon.path().into(),
             color: Color::default(),
@@ -248,3 +238,13 @@ impl IconElement {
         self
     }
 }
+
+impl RenderOnce for Icon {
+    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+        svg()
+            .size(self.size.rems())
+            .flex_none()
+            .path(self.path)
+            .text_color(self.color.color(cx))
+    }
+}

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

@@ -1,4 +1,4 @@
-use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
+use crate::{h_stack, prelude::*, Icon, IconName, IconSize};
 use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke};
 
 #[derive(IntoElement, Clone)]
@@ -26,16 +26,16 @@ impl RenderOnce for KeyBinding {
                     .text_color(cx.theme().colors().text_muted)
                     .when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
                     .when(keystroke.modifiers.control, |el| {
-                        el.child(KeyIcon::new(Icon::Control))
+                        el.child(KeyIcon::new(IconName::Control))
                     })
                     .when(keystroke.modifiers.alt, |el| {
-                        el.child(KeyIcon::new(Icon::Option))
+                        el.child(KeyIcon::new(IconName::Option))
                     })
                     .when(keystroke.modifiers.command, |el| {
-                        el.child(KeyIcon::new(Icon::Command))
+                        el.child(KeyIcon::new(IconName::Command))
                     })
                     .when(keystroke.modifiers.shift, |el| {
-                        el.child(KeyIcon::new(Icon::Shift))
+                        el.child(KeyIcon::new(IconName::Shift))
                     })
                     .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon)))
                     .when(key_icon.is_none(), |el| {
@@ -62,21 +62,21 @@ impl KeyBinding {
         Some(Self::new(key_binding))
     }
 
-    fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {
+    fn icon_for_key(keystroke: &Keystroke) -> Option<IconName> {
         match keystroke.key.as_str() {
-            "left" => Some(Icon::ArrowLeft),
-            "right" => Some(Icon::ArrowRight),
-            "up" => Some(Icon::ArrowUp),
-            "down" => Some(Icon::ArrowDown),
-            "backspace" => Some(Icon::Backspace),
-            "delete" => Some(Icon::Delete),
-            "return" => Some(Icon::Return),
-            "enter" => Some(Icon::Return),
-            "tab" => Some(Icon::Tab),
-            "space" => Some(Icon::Space),
-            "escape" => Some(Icon::Escape),
-            "pagedown" => Some(Icon::PageDown),
-            "pageup" => Some(Icon::PageUp),
+            "left" => Some(IconName::ArrowLeft),
+            "right" => Some(IconName::ArrowRight),
+            "up" => Some(IconName::ArrowUp),
+            "down" => Some(IconName::ArrowDown),
+            "backspace" => Some(IconName::Backspace),
+            "delete" => Some(IconName::Delete),
+            "return" => Some(IconName::Return),
+            "enter" => Some(IconName::Return),
+            "tab" => Some(IconName::Tab),
+            "space" => Some(IconName::Space),
+            "escape" => Some(IconName::Escape),
+            "pagedown" => Some(IconName::PageDown),
+            "pageup" => Some(IconName::PageUp),
             _ => None,
         }
     }
@@ -120,13 +120,13 @@ impl Key {
 
 #[derive(IntoElement)]
 pub struct KeyIcon {
-    icon: Icon,
+    icon: IconName,
 }
 
 impl RenderOnce for KeyIcon {
     fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
         div().w(rems(14. / 16.)).child(
-            IconElement::new(self.icon)
+            Icon::new(self.icon)
                 .size(IconSize::Small)
                 .color(Color::Muted),
         )
@@ -134,7 +134,7 @@ impl RenderOnce for KeyIcon {
 }
 
 impl KeyIcon {
-    pub fn new(icon: Icon) -> Self {
+    pub fn new(icon: IconName) -> Self {
         Self { icon }
     }
 }

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

@@ -2,6 +2,28 @@ use gpui::WindowContext;
 
 use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
 
+/// A struct representing a label element in the UI.
+///
+/// The `Label` struct stores the label text and common properties for a label element.
+/// It provides methods for modifying these properties.
+///
+/// # Examples
+///
+/// ```
+/// Label::new("Hello, World!")
+/// ```
+///
+/// **A colored label**, for example labeling a dangerous action:
+///
+/// ```
+/// let my_label = Label::new("Delete").color(Color::Error);
+/// ```
+///
+/// **A label with a strikethrough**, for example labeling something that has been deleted:
+///
+/// ```
+/// let my_label = Label::new("Deleted").strikethrough(true);
+/// ```
 #[derive(IntoElement)]
 pub struct Label {
     base: LabelLike,
@@ -9,6 +31,13 @@ pub struct Label {
 }
 
 impl Label {
+    /// Create a new `Label` with the given text.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// let my_label = Label::new("Hello, World!");
+    /// ```
     pub fn new(label: impl Into<SharedString>) -> Self {
         Self {
             base: LabelLike::new(),
@@ -18,21 +47,49 @@ impl Label {
 }
 
 impl LabelCommon for Label {
+    /// Sets the size of the label using a [`LabelSize`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// let my_label = Label::new("Hello, World!").size(LabelSize::Large);
+    /// ```
     fn size(mut self, size: LabelSize) -> Self {
         self.base = self.base.size(size);
         self
     }
 
+    /// Sets the line height style of the label using a [`LineHeightStyle`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::Normal);
+    /// ```
     fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
         self.base = self.base.line_height_style(line_height_style);
         self
     }
 
+    /// Sets the color of the label using a [`Color`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// let my_label = Label::new("Hello, World!").color(Color::Primary);
+    /// ```
     fn color(mut self, color: Color) -> Self {
         self.base = self.base.color(color);
         self
     }
 
+    /// Sets the strikethrough property of the label.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// let my_label = Label::new("Hello, World!").strikethrough(true);
+    /// ```
     fn strikethrough(mut self, strikethrough: bool) -> Self {
         self.base = self.base.strikethrough(strikethrough);
         self

crates/ui/src/components/label/label_like.rs 🔗

@@ -19,6 +19,7 @@ pub enum LineHeightStyle {
     UiLabel,
 }
 
+/// A common set of traits all labels must implement.
 pub trait LabelCommon {
     fn size(self, size: LabelSize) -> Self;
     fn line_height_style(self, line_height_style: LineHeightStyle) -> Self;

crates/ui/src/components/list/list_sub_header.rs 🔗

@@ -1,10 +1,10 @@
 use crate::prelude::*;
-use crate::{h_stack, Icon, IconElement, IconSize, Label};
+use crate::{h_stack, Icon, IconName, IconSize, Label};
 
 #[derive(IntoElement)]
 pub struct ListSubHeader {
     label: SharedString,
-    start_slot: Option<Icon>,
+    start_slot: Option<IconName>,
     inset: bool,
 }
 
@@ -17,7 +17,7 @@ impl ListSubHeader {
         }
     }
 
-    pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
+    pub fn left_icon(mut self, left_icon: Option<IconName>) -> Self {
         self.start_slot = left_icon;
         self
     }
@@ -40,11 +40,10 @@ impl RenderOnce for ListSubHeader {
                         .flex()
                         .gap_1()
                         .items_center()
-                        .children(self.start_slot.map(|i| {
-                            IconElement::new(i)
-                                .color(Color::Muted)
-                                .size(IconSize::Small)
-                        }))
+                        .children(
+                            self.start_slot
+                                .map(|i| Icon::new(i).color(Color::Muted).size(IconSize::Small)),
+                        )
                         .child(Label::new(self.label.clone()).color(Color::Muted)),
                 ),
         )

crates/ui/src/components/popover_menu.rs 🔗

@@ -108,6 +108,7 @@ impl<M: ManagedView> PopoverMenu<M> {
     }
 }
 
+/// Creates a [`PopoverMenu`]
 pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M> {
     PopoverMenu {
         id: id.into(),

crates/ui/src/components/right_click_menu.rs 🔗

@@ -39,6 +39,7 @@ impl<M: ManagedView> RightClickMenu<M> {
     }
 }
 
+/// Creates a [`RightClickMenu`]
 pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> {
     RightClickMenu {
         id: id.into(),

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

@@ -2,17 +2,13 @@ use gpui::{div, Div};
 
 use crate::StyledExt;
 
-/// Horizontally stacks elements.
-///
-/// Sets `flex()`, `flex_row()`, `items_center()`
+/// Horizontally stacks elements. Sets `flex()`, `flex_row()`, `items_center()`
 #[track_caller]
 pub fn h_stack() -> Div {
     div().h_flex()
 }
 
-/// Vertically stacks elements.
-///
-/// Sets `flex()`, `flex_col()`
+/// Vertically stacks elements. Sets `flex()`, `flex_col()`
 #[track_caller]
 pub fn v_stack() -> Div {
     div().v_flex()

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

@@ -1,7 +1,7 @@
 use gpui::Render;
 use story::Story;
 
-use crate::{prelude::*, Icon};
+use crate::{prelude::*, IconName};
 use crate::{Button, ButtonStyle};
 
 pub struct ButtonStory;
@@ -23,12 +23,12 @@ impl Render for ButtonStory {
             .child(Story::label("With `label_color`"))
             .child(Button::new("filled_with_label_color", "Click me").color(Color::Created))
             .child(Story::label("With `icon`"))
-            .child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit))
+            .child(Button::new("filled_with_icon", "Click me").icon(IconName::FileGit))
             .child(Story::label("Selected with `icon`"))
             .child(
                 Button::new("filled_and_selected_with_icon", "Click me")
                     .selected(true)
-                    .icon(Icon::FileGit),
+                    .icon(IconName::FileGit),
             )
             .child(Story::label("Default (Subtle)"))
             .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle))

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

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

crates/ui/src/components/stories/icon_button.rs 🔗

@@ -2,7 +2,7 @@ use gpui::Render;
 use story::{StoryContainer, StoryItem, StorySection};
 
 use crate::{prelude::*, Tooltip};
-use crate::{Icon, IconButton};
+use crate::{IconButton, IconName};
 
 pub struct IconButtonStory;
 
@@ -10,7 +10,7 @@ impl Render for IconButtonStory {
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
         let default_button = StoryItem::new(
             "Default",
-            IconButton::new("default_icon_button", Icon::Hash),
+            IconButton::new("default_icon_button", IconName::Hash),
         )
         .description("Displays an icon button.")
         .usage(
@@ -21,7 +21,7 @@ impl Render for IconButtonStory {
 
         let selected_button = StoryItem::new(
             "Selected",
-            IconButton::new("selected_icon_button", Icon::Hash).selected(true),
+            IconButton::new("selected_icon_button", IconName::Hash).selected(true),
         )
         .description("Displays an icon button that is selected.")
         .usage(
@@ -32,9 +32,9 @@ impl Render for IconButtonStory {
 
         let selected_with_selected_icon = StoryItem::new(
             "Selected with `selected_icon`",
-            IconButton::new("selected_with_selected_icon_button", Icon::AudioOn)
+            IconButton::new("selected_with_selected_icon_button", IconName::AudioOn)
                 .selected(true)
-                .selected_icon(Icon::AudioOff),
+                .selected_icon(IconName::AudioOff),
         )
         .description(
             "Displays an icon button that is selected and shows a different icon when selected.",
@@ -49,7 +49,7 @@ impl Render for IconButtonStory {
 
         let disabled_button = StoryItem::new(
             "Disabled",
-            IconButton::new("disabled_icon_button", Icon::Hash).disabled(true),
+            IconButton::new("disabled_icon_button", IconName::Hash).disabled(true),
         )
         .description("Displays an icon button that is disabled.")
         .usage(
@@ -60,7 +60,7 @@ impl Render for IconButtonStory {
 
         let with_on_click_button = StoryItem::new(
             "With `on_click`",
-            IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| {
+            IconButton::new("with_on_click_button", IconName::Ai).on_click(|_event, _cx| {
                 println!("Clicked!");
             }),
         )
@@ -75,7 +75,7 @@ impl Render for IconButtonStory {
 
         let with_tooltip_button = StoryItem::new(
             "With `tooltip`",
-            IconButton::new("with_tooltip_button", Icon::MessageBubbles)
+            IconButton::new("with_tooltip_button", IconName::MessageBubbles)
                 .tooltip(|cx| Tooltip::text("Open messages", cx)),
         )
         .description("Displays an icon button that has a tooltip when hovered.")
@@ -88,7 +88,7 @@ impl Render for IconButtonStory {
 
         let selected_with_tooltip_button = StoryItem::new(
             "Selected with `tooltip`",
-            IconButton::new("selected_with_tooltip_button", Icon::InlayHint)
+            IconButton::new("selected_with_tooltip_button", IconName::InlayHint)
                 .selected(true)
                 .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)),
         )

crates/ui/src/components/stories/list_header.rs 🔗

@@ -2,7 +2,7 @@ use gpui::Render;
 use story::Story;
 
 use crate::{prelude::*, IconButton};
-use crate::{Icon, ListHeader};
+use crate::{IconName, ListHeader};
 
 pub struct ListHeaderStory;
 
@@ -13,19 +13,19 @@ impl Render for ListHeaderStory {
             .child(Story::label("Default"))
             .child(ListHeader::new("Section 1"))
             .child(Story::label("With left icon"))
-            .child(ListHeader::new("Section 2").start_slot(IconElement::new(Icon::Bell)))
+            .child(ListHeader::new("Section 2").start_slot(Icon::new(IconName::Bell)))
             .child(Story::label("With left icon and meta"))
             .child(
                 ListHeader::new("Section 3")
-                    .start_slot(IconElement::new(Icon::BellOff))
-                    .end_slot(IconButton::new("action_1", Icon::Bolt)),
+                    .start_slot(Icon::new(IconName::BellOff))
+                    .end_slot(IconButton::new("action_1", IconName::Bolt)),
             )
             .child(Story::label("With multiple meta"))
             .child(
                 ListHeader::new("Section 4")
-                    .end_slot(IconButton::new("action_1", Icon::Bolt))
-                    .end_slot(IconButton::new("action_2", Icon::ExclamationTriangle))
-                    .end_slot(IconButton::new("action_3", Icon::Plus)),
+                    .end_slot(IconButton::new("action_1", IconName::Bolt))
+                    .end_slot(IconButton::new("action_2", IconName::ExclamationTriangle))
+                    .end_slot(IconButton::new("action_3", IconName::Plus)),
             )
     }
 }

crates/ui/src/components/stories/list_item.rs 🔗

@@ -1,8 +1,8 @@
-use gpui::Render;
+use gpui::{Render, SharedUrl};
 use story::Story;
 
 use crate::{prelude::*, Avatar};
-use crate::{Icon, ListItem};
+use crate::{IconName, ListItem};
 
 pub struct ListItemStory;
 
@@ -18,13 +18,13 @@ impl Render for ListItemStory {
                 ListItem::new("inset_list_item")
                     .inset(true)
                     .start_slot(
-                        IconElement::new(Icon::Bell)
+                        Icon::new(IconName::Bell)
                             .size(IconSize::Small)
                             .color(Color::Muted),
                     )
                     .child("Hello, world!")
                     .end_slot(
-                        IconElement::new(Icon::Bell)
+                        Icon::new(IconName::Bell)
                             .size(IconSize::Small)
                             .color(Color::Muted),
                     ),
@@ -34,7 +34,7 @@ impl Render for ListItemStory {
                 ListItem::new("with start slot_icon")
                     .child("Hello, world!")
                     .start_slot(
-                        IconElement::new(Icon::Bell)
+                        Icon::new(IconName::Bell)
                             .size(IconSize::Small)
                             .color(Color::Muted),
                     ),
@@ -43,7 +43,7 @@ impl Render for ListItemStory {
             .child(
                 ListItem::new("with_start slot avatar")
                     .child("Hello, world!")
-                    .start_slot(Avatar::new(SharedString::from(
+                    .start_slot(Avatar::new(SharedUrl::from(
                         "https://avatars.githubusercontent.com/u/1714999?v=4",
                     ))),
             )
@@ -51,7 +51,7 @@ impl Render for ListItemStory {
             .child(
                 ListItem::new("with_left_avatar")
                     .child("Hello, world!")
-                    .end_slot(Avatar::new(SharedString::from(
+                    .end_slot(Avatar::new(SharedUrl::from(
                         "https://avatars.githubusercontent.com/u/1714999?v=4",
                     ))),
             )
@@ -62,23 +62,23 @@ impl Render for ListItemStory {
                     .end_slot(
                         h_stack()
                             .gap_2()
-                            .child(Avatar::new(SharedString::from(
+                            .child(Avatar::new(SharedUrl::from(
                                 "https://avatars.githubusercontent.com/u/1789?v=4",
                             )))
-                            .child(Avatar::new(SharedString::from(
+                            .child(Avatar::new(SharedUrl::from(
                                 "https://avatars.githubusercontent.com/u/1789?v=4",
                             )))
-                            .child(Avatar::new(SharedString::from(
+                            .child(Avatar::new(SharedUrl::from(
                                 "https://avatars.githubusercontent.com/u/1789?v=4",
                             )))
-                            .child(Avatar::new(SharedString::from(
+                            .child(Avatar::new(SharedUrl::from(
                                 "https://avatars.githubusercontent.com/u/1789?v=4",
                             )))
-                            .child(Avatar::new(SharedString::from(
+                            .child(Avatar::new(SharedUrl::from(
                                 "https://avatars.githubusercontent.com/u/1789?v=4",
                             ))),
                     )
-                    .end_hover_slot(Avatar::new(SharedString::from(
+                    .end_hover_slot(Avatar::new(SharedUrl::from(
                         "https://avatars.githubusercontent.com/u/1714999?v=4",
                     ))),
             )

crates/ui/src/components/stories/tab.rs 🔗

@@ -27,7 +27,7 @@ impl Render for TabStory {
                 h_stack().child(
                     Tab::new("tab_1")
                         .end_slot(
-                            IconButton::new("close_button", Icon::Close)
+                            IconButton::new("close_button", IconName::Close)
                                 .icon_color(Color::Muted)
                                 .size(ButtonSize::None)
                                 .icon_size(IconSize::XSmall),

crates/ui/src/components/stories/tab_bar.rs 🔗

@@ -38,16 +38,19 @@ impl Render for TabBarStory {
                 h_stack().child(
                     TabBar::new("tab_bar_1")
                         .start_child(
-                            IconButton::new("navigate_backward", Icon::ArrowLeft)
+                            IconButton::new("navigate_backward", IconName::ArrowLeft)
                                 .icon_size(IconSize::Small),
                         )
                         .start_child(
-                            IconButton::new("navigate_forward", Icon::ArrowRight)
+                            IconButton::new("navigate_forward", IconName::ArrowRight)
                                 .icon_size(IconSize::Small),
                         )
-                        .end_child(IconButton::new("new", Icon::Plus).icon_size(IconSize::Small))
                         .end_child(
-                            IconButton::new("split_pane", Icon::Split).icon_size(IconSize::Small),
+                            IconButton::new("new", IconName::Plus).icon_size(IconSize::Small),
+                        )
+                        .end_child(
+                            IconButton::new("split_pane", IconName::Split)
+                                .icon_size(IconSize::Small),
                         )
                         .children(tabs),
                 ),

crates/ui/src/disableable.rs 🔗

@@ -1,4 +1,4 @@
-/// A trait for elements that can be disabled.
+/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing it's appearance to reflect that it is disabled.
 pub trait Disableable {
     /// Sets whether the element is disabled.
     fn disabled(self, disabled: bool) -> Self;

crates/ui/src/fixed.rs 🔗

@@ -1,6 +1,6 @@
 use gpui::DefiniteLength;
 
-/// A trait for elements that have a fixed with.
+/// A trait for elements that can have a fixed with. Enables the use of the `width` and `full_width` methods.
 pub trait FixedWidth {
     /// Sets the width of the element.
     fn width(self, width: DefiniteLength) -> Self;

crates/ui/src/prelude.rs 🔗

@@ -1,3 +1,5 @@
+//! The prelude of this crate. When building UI in Zed you almost always want to import this.
+
 pub use gpui::prelude::*;
 pub use gpui::{
     div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
@@ -15,6 +17,6 @@ pub use crate::{h_stack, v_stack};
 pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton};
 pub use crate::{ButtonCommon, Color, StyledExt};
 pub use crate::{Headline, HeadlineSize};
-pub use crate::{Icon, IconElement, IconPosition, IconSize};
+pub use crate::{Icon, IconName, IconPosition, IconSize};
 pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle};
 pub use theme::ActiveTheme;

crates/ui/src/selectable.rs 🔗

@@ -1,18 +1,27 @@
 /// A trait for elements that can be selected.
+///
+/// Generally used to enable "toggle" or "active" behavior and styles on an element through the [`Selection`] status.
 pub trait Selectable {
     /// Sets whether the element is selected.
     fn selected(self, selected: bool) -> Self;
 }
 
+/// Represents the selection status of an element.
 #[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
 pub enum Selection {
+    /// The element is not selected.
     #[default]
     Unselected,
+    /// The selection state of the element is indeterminate.
     Indeterminate,
+    /// The element is selected.
     Selected,
 }
 
 impl Selection {
+    /// Returns the inverse of the current selection status.
+    ///
+    /// Indeterminate states become selected if inverted.
     pub fn inverse(&self) -> Self {
         match self {
             Self::Unselected | Self::Indeterminate => Self::Selected,

crates/ui/src/styled_ext.rs 🔗

@@ -14,7 +14,7 @@ fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -
         .shadow(index.shadow())
 }
 
-/// Extends [`Styled`](gpui::Styled) with Zed specific styling methods.
+/// Extends [`gpui::Styled`] with Zed-specific styling methods.
 pub trait StyledExt: Styled + Sized {
     /// Horizontally stacks elements.
     ///
@@ -30,6 +30,7 @@ pub trait StyledExt: Styled + Sized {
         self.flex().flex_col()
     }
 
+    /// Sets the text size using a [`UiTextSize`].
     fn text_ui_size(self, size: UiTextSize) -> Self {
         self.text_size(size.rems())
     }
@@ -40,7 +41,7 @@ pub trait StyledExt: Styled + Sized {
     ///
     /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
     ///
-    /// Use [`text_ui_sm`] for regular-sized text.
+    /// Use `text_ui_sm` for smaller text.
     fn text_ui(self) -> Self {
         self.text_size(UiTextSize::default().rems())
     }
@@ -51,7 +52,7 @@ pub trait StyledExt: Styled + Sized {
     ///
     /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
     ///
-    /// Use [`text_ui`] for regular-sized text.
+    /// Use `text_ui` for regular-sized text.
     fn text_ui_sm(self) -> Self {
         self.text_size(UiTextSize::Small.rems())
     }
@@ -62,7 +63,7 @@ pub trait StyledExt: Styled + Sized {
     ///
     /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
     ///
-    /// Use [`text_ui`] for regular-sized text.
+    /// Use `text_ui` for regular-sized text.
     fn text_ui_xs(self) -> Self {
         self.text_size(UiTextSize::XSmall.rems())
     }
@@ -78,7 +79,7 @@ pub trait StyledExt: Styled + Sized {
         self.text_size(settings.buffer_font_size(cx))
     }
 
-    /// The [`Surface`](ui::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
+    /// The [`Surface`](ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
     ///
     /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
     ///
@@ -87,7 +88,7 @@ pub trait StyledExt: Styled + Sized {
         elevated(self, cx, ElevationIndex::Surface)
     }
 
-    /// Non-Modal Elevated Surfaces appear above the [`Surface`](ui::ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc.
+    /// Non-Modal Elevated Surfaces appear above the [`Surface`](ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc.
     ///
     /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
     ///
@@ -100,7 +101,7 @@ pub trait StyledExt: Styled + Sized {
     ///
     /// Elements rendered at this layer should have an enforced behavior: Any interaction outside of the modal will either dismiss the modal or prompt an action (Save your progress, etc) then dismiss the modal.
     ///
-    /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ui::ElevationIndex::ElevatedSurface) layer.
+    /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ElevationIndex::ElevatedSurface) layer.
     ///
     /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
     ///
@@ -119,26 +120,32 @@ pub trait StyledExt: Styled + Sized {
         self.border_color(cx.theme().colors().border_variant)
     }
 
+    /// Sets the background color to red for debugging when building UI.
     fn debug_bg_red(self) -> Self {
         self.bg(hsla(0. / 360., 1., 0.5, 1.))
     }
 
+    /// Sets the background color to green for debugging when building UI.
     fn debug_bg_green(self) -> Self {
         self.bg(hsla(120. / 360., 1., 0.5, 1.))
     }
 
+    /// Sets the background color to blue for debugging when building UI.
     fn debug_bg_blue(self) -> Self {
         self.bg(hsla(240. / 360., 1., 0.5, 1.))
     }
 
+    /// Sets the background color to yellow for debugging when building UI.
     fn debug_bg_yellow(self) -> Self {
         self.bg(hsla(60. / 360., 1., 0.5, 1.))
     }
 
+    /// Sets the background color to cyan for debugging when building UI.
     fn debug_bg_cyan(self) -> Self {
         self.bg(hsla(160. / 360., 1., 0.5, 1.))
     }
 
+    /// Sets the background color to magenta for debugging when building UI.
     fn debug_bg_magenta(self) -> Self {
         self.bg(hsla(300. / 360., 1., 0.5, 1.))
     }

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

@@ -1,6 +1,7 @@
 use gpui::{Hsla, WindowContext};
 use theme::ActiveTheme;
 
+/// Sets a color that has a consistent meaning across all themes.
 #[derive(Debug, Default, PartialEq, Copy, Clone)]
 pub enum Color {
     #[default]

crates/ui/src/styles/elevation.rs 🔗

@@ -85,6 +85,7 @@ impl LayerIndex {
     }
 }
 
+/// An appropriate z-index for the given layer based on its intended useage.
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum ElementIndex {
     Effect,

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

@@ -38,6 +38,7 @@ impl UiTextSize {
     }
 }
 
+/// The size of a [`Headline`] element
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
 pub enum HeadlineSize {
     XSmall,

crates/ui/src/ui.rs 🔗

@@ -3,8 +3,6 @@
 //! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI.
 //!
 
-#![doc = include_str!("../docs/building-ui.md")]
-
 mod clickable;
 mod components;
 mod disableable;

crates/ui/src/utils.rs 🔗

@@ -1,3 +1,5 @@
+//! UI-related utilities (e.g. converting dates to a human-readable form).
+
 mod format_distance;
 
 pub use format_distance::*;

crates/ui/src/utils/format_distance.rs 🔗

@@ -7,10 +7,10 @@ pub enum DateTimeType {
 }
 
 impl DateTimeType {
-    /// Converts the DateTimeType to a NaiveDateTime.
+    /// Converts the [`DateTimeType`] to a [`NaiveDateTime`].
     ///
-    /// If the DateTimeType is already a NaiveDateTime, it will be returned as is.
-    /// If the DateTimeType is a DateTime<Local>, it will be converted to a NaiveDateTime.
+    /// If the [`DateTimeType`] is already a [`NaiveDateTime`], it will be returned as is.
+    /// If the [`DateTimeType`] is a [`DateTime<Local>`], it will be converted to a [`NaiveDateTime`].
     pub fn to_naive(&self) -> NaiveDateTime {
         match self {
             DateTimeType::Naive(naive) => *naive,
@@ -68,13 +68,13 @@ impl FormatDistance {
     }
 }
 
-/// Calculates the distance in seconds between two NaiveDateTime objects.
+/// Calculates the distance in seconds between two [`NaiveDateTime`] objects.
 /// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative.
 ///
 /// ## Arguments
 ///
-/// * `date` - A NaiveDateTime object representing the date of interest
-/// * `base_date` - A NaiveDateTime object representing the base date against which the comparison is made
+/// * `date` - A [NaiveDateTime`] object representing the date of interest
+/// * `base_date` - A [NaiveDateTime`] object representing the base date against which the comparison is made
 fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
     let duration = date.signed_duration_since(base_date);
     -duration.num_seconds()
@@ -233,12 +233,12 @@ fn distance_string(
 ///
 /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc.
 ///
-/// Use [naive_format_distance_from_now] to compare a NaiveDateTime against now.
+/// Use [`format_distance_from_now`] to compare a NaiveDateTime against now.
 ///
 /// # Arguments
 ///
-/// * `date` - The NaiveDateTime to compare.
-/// * `base_date` - The NaiveDateTime to compare against.
+/// * `date` - The [`NaiveDateTime`] to compare.
+/// * `base_date` - The [`NaiveDateTime`] to compare against.
 /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed
 /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future
 ///
@@ -274,7 +274,7 @@ pub fn format_distance(
 ///
 /// # Arguments
 ///
-/// * `datetime` - The NaiveDateTime to compare with the current time.
+/// * `datetime` - The [`NaiveDateTime`] to compare with the current time.
 /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed
 /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future
 ///

crates/workspace/src/dock.rs 🔗

@@ -28,7 +28,7 @@ pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
     fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
     fn size(&self, cx: &WindowContext) -> Pixels;
     fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>);
-    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
+    fn icon(&self, cx: &WindowContext) -> Option<ui::IconName>;
     fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
     fn toggle_action(&self) -> Box<dyn Action>;
     fn icon_label(&self, _: &WindowContext) -> Option<String> {
@@ -52,7 +52,7 @@ pub trait PanelHandle: Send + Sync {
     fn set_active(&self, active: bool, cx: &mut WindowContext);
     fn size(&self, cx: &WindowContext) -> Pixels;
     fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext);
-    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
+    fn icon(&self, cx: &WindowContext) -> Option<ui::IconName>;
     fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
     fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>;
     fn icon_label(&self, cx: &WindowContext) -> Option<String>;
@@ -104,7 +104,7 @@ where
         self.update(cx, |this, cx| this.set_size(size, cx))
     }
 
-    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
+    fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
         self.read(cx).icon(cx)
     }
 
@@ -395,7 +395,6 @@ impl Dock {
                         })
                         .ok();
                 }
-                // todo!() we do not use this event in the production code (even in zed1), remove it
                 PanelEvent::Activate => {
                     if let Some(ix) = this
                         .panel_entries
@@ -775,7 +774,7 @@ pub mod test {
             self.size = size.unwrap_or(px(300.));
         }
 
-        fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
+        fn icon(&self, _: &WindowContext) -> Option<ui::IconName> {
             None
         }
 

crates/workspace/src/modal_layer.rs 🔗

@@ -101,6 +101,10 @@ impl ModalLayer {
         let active_modal = self.active_modal.as_ref()?;
         active_modal.modal.view().downcast::<V>().ok()
     }
+
+    pub fn has_active_modal(&self) -> bool {
+        self.active_modal.is_some()
+    }
 }
 
 impl Render for ModalLayer {

crates/workspace/src/notifications.rs 🔗

@@ -175,7 +175,7 @@ pub mod simple_message_notification {
     };
     use std::sync::Arc;
     use ui::prelude::*;
-    use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
+    use ui::{h_stack, v_stack, Button, Icon, IconName, Label, StyledExt};
 
     pub struct MessageNotification {
         message: SharedString,
@@ -230,7 +230,7 @@ pub mod simple_message_notification {
                         .child(
                             div()
                                 .id("cancel")
-                                .child(IconElement::new(Icon::Close))
+                                .child(Icon::new(IconName::Close))
                                 .cursor_pointer()
                                 .on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
                         ),
@@ -247,105 +247,6 @@ pub mod simple_message_notification {
                 }))
         }
     }
-    // todo!()
-    //     impl View for MessageNotification {
-    //         fn ui_name() -> &'static str {
-    //             "MessageNotification"
-    //         }
-
-    //         fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
-    //             let theme = theme::current(cx).clone();
-    //             let theme = &theme.simple_message_notification;
-
-    //             enum MessageNotificationTag {}
-
-    //             let click_message = self.click_message.clone();
-    //             let message = match &self.message {
-    //                 NotificationMessage::Text(text) => {
-    //                     Text::new(text.to_owned(), theme.message.text.clone()).into_any()
-    //                 }
-    //                 NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
-    //             };
-    //             let on_click = self.on_click.clone();
-    //             let has_click_action = on_click.is_some();
-
-    //             Flex::column()
-    //                 .with_child(
-    //                     Flex::row()
-    //                         .with_child(
-    //                             message
-    //                                 .contained()
-    //                                 .with_style(theme.message.container)
-    //                                 .aligned()
-    //                                 .top()
-    //                                 .left()
-    //                                 .flex(1., true),
-    //                         )
-    //                         .with_child(
-    //                             MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
-    //                                 let style = theme.dismiss_button.style_for(state);
-    //                                 Svg::new("icons/x.svg")
-    //                                     .with_color(style.color)
-    //                                     .constrained()
-    //                                     .with_width(style.icon_width)
-    //                                     .aligned()
-    //                                     .contained()
-    //                                     .with_style(style.container)
-    //                                     .constrained()
-    //                                     .with_width(style.button_width)
-    //                                     .with_height(style.button_width)
-    //                             })
-    //                             .with_padding(Padding::uniform(5.))
-    //                             .on_click(MouseButton::Left, move |_, this, cx| {
-    //                                 this.dismiss(&Default::default(), cx);
-    //                             })
-    //                             .with_cursor_style(CursorStyle::PointingHand)
-    //                             .aligned()
-    //                             .constrained()
-    //                             .with_height(cx.font_cache().line_height(theme.message.text.font_size))
-    //                             .aligned()
-    //                             .top()
-    //                             .flex_float(),
-    //                         ),
-    //                 )
-    //                 .with_children({
-    //                     click_message
-    //                         .map(|click_message| {
-    //                             MouseEventHandler::new::<MessageNotificationTag, _>(
-    //                                 0,
-    //                                 cx,
-    //                                 |state, _| {
-    //                                     let style = theme.action_message.style_for(state);
-
-    //                                     Flex::row()
-    //                                         .with_child(
-    //                                             Text::new(click_message, style.text.clone())
-    //                                                 .contained()
-    //                                                 .with_style(style.container),
-    //                                         )
-    //                                         .contained()
-    //                                 },
-    //                             )
-    //                             .on_click(MouseButton::Left, move |_, this, cx| {
-    //                                 if let Some(on_click) = on_click.as_ref() {
-    //                                     on_click(cx);
-    //                                     this.dismiss(&Default::default(), cx);
-    //                                 }
-    //                             })
-    //                             // Since we're not using a proper overlay, we have to capture these extra events
-    //                             .on_down(MouseButton::Left, |_, _, _| {})
-    //                             .on_up(MouseButton::Left, |_, _, _| {})
-    //                             .with_cursor_style(if has_click_action {
-    //                                 CursorStyle::PointingHand
-    //                             } else {
-    //                                 CursorStyle::Arrow
-    //                             })
-    //                         })
-    //                         .into_iter()
-    //                 })
-    //                 .into_any()
-    //         }
-    //     }
 }
 
 pub trait NotifyResultExt {

crates/workspace/src/pane.rs 🔗

@@ -31,8 +31,8 @@ use std::{
 use theme::ThemeSettings;
 
 use ui::{
-    prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label,
-    Tab, TabBar, TabPosition, Tooltip,
+    prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconName, IconSize, Indicator,
+    Label, Tab, TabBar, TabPosition, Tooltip,
 };
 use ui::{v_stack, ContextMenu};
 use util::{maybe, truncate_and_remove_front, ResultExt};
@@ -384,7 +384,7 @@ impl Pane {
                 h_stack()
                     .gap_2()
                     .child(
-                        IconButton::new("plus", Icon::Plus)
+                        IconButton::new("plus", IconName::Plus)
                             .icon_size(IconSize::Small)
                             .icon_color(Color::Muted)
                             .on_click(cx.listener(|pane, _, cx| {
@@ -406,7 +406,7 @@ impl Pane {
                         el.child(Self::render_menu_overlay(new_item_menu))
                     })
                     .child(
-                        IconButton::new("split", Icon::Split)
+                        IconButton::new("split", IconName::Split)
                             .icon_size(IconSize::Small)
                             .icon_color(Color::Muted)
                             .on_click(cx.listener(|pane, _, cx| {
@@ -427,11 +427,11 @@ impl Pane {
                     )
                     .child({
                         let zoomed = pane.is_zoomed();
-                        IconButton::new("toggle_zoom", Icon::Maximize)
+                        IconButton::new("toggle_zoom", IconName::Maximize)
                             .icon_size(IconSize::Small)
                             .icon_color(Color::Muted)
                             .selected(zoomed)
-                            .selected_icon(Icon::Minimize)
+                            .selected_icon(IconName::Minimize)
                             .on_click(cx.listener(|pane, _, cx| {
                                 pane.toggle_zoom(&crate::ToggleZoom, cx);
                             }))
@@ -1570,7 +1570,7 @@ impl Pane {
             })
             .start_slot::<Indicator>(indicator)
             .end_slot(
-                IconButton::new("close tab", Icon::Close)
+                IconButton::new("close tab", IconName::Close)
                     .icon_color(Color::Muted)
                     .size(ButtonSize::None)
                     .icon_size(IconSize::XSmall)
@@ -1676,7 +1676,7 @@ impl Pane {
                     h_stack()
                         .gap_2()
                         .child(
-                            IconButton::new("navigate_backward", Icon::ArrowLeft)
+                            IconButton::new("navigate_backward", IconName::ArrowLeft)
                                 .icon_size(IconSize::Small)
                                 .on_click({
                                     let view = cx.view().clone();
@@ -1686,7 +1686,7 @@ impl Pane {
                                 .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)),
                         )
                         .child(
-                            IconButton::new("navigate_forward", Icon::ArrowRight)
+                            IconButton::new("navigate_forward", IconName::ArrowRight)
                                 .icon_size(IconSize::Small)
                                 .on_click({
                                     let view = cx.view().clone();

crates/workspace/src/pane_group.rs 🔗

@@ -12,7 +12,7 @@ use serde::Deserialize;
 use std::sync::Arc;
 use ui::{prelude::*, Button};
 
-const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4)
+const HANDLE_HITBOX_SIZE: f32 = 4.0;
 const HORIZONTAL_MIN_SIZE: f32 = 80.;
 const VERTICAL_MIN_SIZE: f32 = 100.;
 
@@ -579,12 +579,15 @@ mod element {
         Size, Style, WeakView, WindowContext,
     };
     use parking_lot::Mutex;
+    use settings::Settings;
     use smallvec::SmallVec;
     use ui::prelude::*;
     use util::ResultExt;
 
     use crate::Workspace;
 
+    use crate::WorkspaceSettings;
+
     use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
 
     const DIVIDER_SIZE: f32 = 1.0;
@@ -704,7 +707,6 @@ mod element {
                 proposed_current_pixel_change -= current_pixel_change;
             }
 
-            // todo!(schedule serialize)
             workspace
                 .update(cx, |this, cx| this.schedule_serialize(cx))
                 .log_err();
@@ -834,20 +836,39 @@ mod element {
             debug_assert!(flexes.len() == len);
             debug_assert!(flex_values_in_bounds(flexes.as_slice()));
 
+            let magnification_value = WorkspaceSettings::get(None, cx).active_pane_magnification;
+            let active_pane_magnification = if magnification_value == 1. {
+                None
+            } else {
+                Some(magnification_value)
+            };
+
+            let total_flex = if let Some(flex) = active_pane_magnification {
+                self.children.len() as f32 - 1. + flex
+            } else {
+                len as f32
+            };
+
             let mut origin = bounds.origin;
-            let space_per_flex = bounds.size.along(self.axis) / len as f32;
+            let space_per_flex = bounds.size.along(self.axis) / total_flex;
 
             let mut bounding_boxes = self.bounding_boxes.lock();
             bounding_boxes.clear();
 
             for (ix, child) in self.children.iter_mut().enumerate() {
-                //todo!(active_pane_magnification)
-                // If using active pane magnification, need to switch to using
-                // 1 for all non-active panes, and then the magnification for the
-                // active pane.
+                let child_flex = active_pane_magnification
+                    .map(|magnification| {
+                        if self.active_pane_ix == Some(ix) {
+                            magnification
+                        } else {
+                            1.
+                        }
+                    })
+                    .unwrap_or_else(|| flexes[ix]);
+
                 let child_size = bounds
                     .size
-                    .apply_along(self.axis, |_| space_per_flex * flexes[ix]);
+                    .apply_along(self.axis, |_| space_per_flex * child_flex);
 
                 let child_bounds = Bounds {
                     origin,
@@ -857,20 +878,23 @@ mod element {
                 cx.with_z_index(0, |cx| {
                     child.draw(origin, child_size.into(), cx);
                 });
-                cx.with_z_index(1, |cx| {
-                    if ix < len - 1 {
-                        Self::push_handle(
-                            self.flexes.clone(),
-                            state.clone(),
-                            self.axis,
-                            ix,
-                            child_bounds,
-                            bounds,
-                            self.workspace.clone(),
-                            cx,
-                        );
-                    }
-                });
+
+                if active_pane_magnification.is_none() {
+                    cx.with_z_index(1, |cx| {
+                        if ix < len - 1 {
+                            Self::push_handle(
+                                self.flexes.clone(),
+                                state.clone(),
+                                self.axis,
+                                ix,
+                                child_bounds,
+                                bounds,
+                                self.workspace.clone(),
+                                cx,
+                            );
+                        }
+                    });
+                }
 
                 origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
             }

crates/workspace/src/shared_screen.rs 🔗

@@ -12,7 +12,7 @@ use gpui::{
     WindowContext,
 };
 use std::sync::{Arc, Weak};
-use ui::{h_stack, prelude::*, Icon, IconElement, Label};
+use ui::{h_stack, prelude::*, Icon, IconName, Label};
 
 pub enum Event {
     Close,
@@ -100,7 +100,7 @@ impl Item for SharedScreen {
     ) -> gpui::AnyElement {
         h_stack()
             .gap_1()
-            .child(IconElement::new(Icon::Screen))
+            .child(Icon::new(IconName::Screen))
             .child(
                 Label::new(format!("{}'s screen", self.user.github_login)).color(if selected {
                     Color::Default

crates/workspace/src/toolbar.rs 🔗

@@ -133,82 +133,6 @@ impl Render for Toolbar {
     }
 }
 
-// todo!()
-// impl View for Toolbar {
-//     fn ui_name() -> &'static str {
-//         "Toolbar"
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         let theme = &theme::current(cx).workspace.toolbar;
-
-//         let mut primary_left_items = Vec::new();
-//         let mut primary_right_items = Vec::new();
-//         let mut secondary_item = None;
-//         let spacing = theme.item_spacing;
-//         let mut primary_items_row_count = 1;
-
-//         for (item, position) in &self.items {
-//             match *position {
-//                 ToolbarItemLocation::Hidden => {}
-
-//                 ToolbarItemLocation::PrimaryLeft { flex } => {
-//                     primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
-//                     let left_item = ChildView::new(item.as_any(), cx).aligned();
-//                     if let Some((flex, expanded)) = flex {
-//                         primary_left_items.push(left_item.flex(flex, expanded).into_any());
-//                     } else {
-//                         primary_left_items.push(left_item.into_any());
-//                     }
-//                 }
-
-//                 ToolbarItemLocation::PrimaryRight { flex } => {
-//                     primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
-//                     let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
-//                     if let Some((flex, expanded)) = flex {
-//                         primary_right_items.push(right_item.flex(flex, expanded).into_any());
-//                     } else {
-//                         primary_right_items.push(right_item.into_any());
-//                     }
-//                 }
-
-//                 ToolbarItemLocation::Secondary => {
-//                     secondary_item = Some(
-//                         ChildView::new(item.as_any(), cx)
-//                             .constrained()
-//                             .with_height(theme.height * item.row_count(cx) as f32)
-//                             .into_any(),
-//                     );
-//                 }
-//             }
-//         }
-
-//         let container_style = theme.container;
-//         let height = theme.height * primary_items_row_count as f32;
-
-//         let mut primary_items = Flex::row().with_spacing(spacing);
-//         primary_items.extend(primary_left_items);
-//         primary_items.extend(primary_right_items);
-
-//         let mut toolbar = Flex::column();
-//         if !primary_items.is_empty() {
-//             toolbar.add_child(primary_items.constrained().with_height(height));
-//         }
-//         if let Some(secondary_item) = secondary_item {
-//             toolbar.add_child(secondary_item);
-//         }
-
-//         if toolbar.is_empty() {
-//             toolbar.into_any_named("toolbar")
-//         } else {
-//             toolbar
-//                 .contained()
-//                 .with_style(container_style)
-//                 .into_any_named("toolbar")
-//         }
-//     }
-// }
-
 impl Toolbar {
     pub fn new() -> Self {
         Self {
@@ -312,10 +236,3 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> {
         self.read(cx).row_count(cx)
     }
 }
-
-// todo!()
-// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
-//     fn from(val: &dyn ToolbarItemViewHandle) -> Self {
-//         val.as_any().clone()
-//     }
-// }

crates/workspace/src/workspace.rs 🔗

@@ -943,10 +943,8 @@ impl Workspace {
         cx: &mut ViewContext<Workspace>,
     ) -> Task<Result<()>> {
         let to_load = if let Some(pane) = pane.upgrade() {
-            // todo!("focus")
-            // cx.focus(&pane);
-
             pane.update(cx, |pane, cx| {
+                pane.focus(cx);
                 loop {
                     // Retrieve the weak item handle from the history.
                     let entry = pane.nav_history_mut().pop(mode, cx)?;
@@ -1631,8 +1629,7 @@ impl Workspace {
             });
         }
 
-        // todo!("focus")
-        // cx.focus_self();
+        cx.focus_self();
         cx.notify();
         self.serialize_workspace(cx);
     }
@@ -1713,6 +1710,7 @@ impl Workspace {
         cx.notify();
     }
 
+    // todo!()
     //     #[cfg(any(test, feature = "test-support"))]
     //     pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
     //         self.zoomed.and_then(|view| view.upgrade(cx))
@@ -2992,7 +2990,6 @@ impl Workspace {
         cx.notify();
     }
 
-    #[allow(unused)]
     fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
         self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
             cx.background_executor()
@@ -3386,6 +3383,10 @@ impl Workspace {
         div
     }
 
+    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
+        self.modal_layer.read(cx).has_active_modal()
+    }
+
     pub fn active_modal<V: ManagedView + 'static>(
         &mut self,
         cx: &ViewContext<Self>,

crates/zed/src/zed.rs 🔗

@@ -764,7 +764,6 @@ fn open_bundled_file(
     .detach_and_log_err(cx);
 }
 
-// todo!()
 #[cfg(test)]
 mod tests {
     use super::*;