Merge remote-tracking branch 'origin/main' into app-menus

Nathan Sobo created

Change summary

Cargo.lock                                       |  12 
Cargo.toml                                       |   1 
assets/keymaps/default.json                      |  40 
crates/breadcrumbs2/src/breadcrumbs.rs           |  81 +--
crates/collab_ui2/src/collab_panel.rs            | 226 ++--------
crates/diagnostics2/src/diagnostics.rs           |  47 -
crates/editor2/src/editor.rs                     |  27 
crates/editor2/src/editor_tests.rs               |   4 
crates/editor2/src/items.rs                      | 118 +++--
crates/gpui2/src/elements/canvas.rs              |  10 
crates/gpui2/src/platform/mac/display_linker.rs  |   3 
crates/gpui2/src/platform/test/platform.rs       |  29 
crates/gpui2/src/window.rs                       |   6 
crates/quick_action_bar2/Cargo.toml              |  22 +
crates/quick_action_bar2/src/quick_action_bar.rs | 288 +++++++++++++
crates/terminal_view2/src/terminal_view.rs       |   6 
crates/theme2/src/default_colors.rs              |  24 
crates/theme2/src/default_theme.rs               |  80 ++-
crates/theme2/src/registry.rs                    |   6 
crates/theme2/src/styles/syntax.rs               |  16 
crates/theme_selector2/src/theme_selector.rs     |  10 
crates/ui2/src/components.rs                     |   2 
crates/ui2/src/components/icon.rs                |  20 
crates/ui2/src/components/indicator.rs           |  60 ++
crates/ui2/src/prelude.rs                        |   1 
crates/welcome2/src/welcome.rs                   |   6 
crates/workspace2/src/item.rs                    |  48 +-
crates/workspace2/src/pane.rs                    | 376 +++++++++--------
crates/workspace2/src/shared_screen.rs           |  10 
crates/workspace2/src/toolbar.rs                 |  24 -
crates/workspace2/src/workspace2.rs              | 146 +++---
crates/zed2/Cargo.toml                           |   2 
crates/zed2/src/zed2.rs                          |  14 
33 files changed, 1,024 insertions(+), 741 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7071,6 +7071,17 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "quick_action_bar2"
+version = "0.1.0"
+dependencies = [
+ "editor2",
+ "gpui2",
+ "search2",
+ "ui2",
+ "workspace2",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.33"
@@ -11890,6 +11901,7 @@ dependencies = [
  "postage",
  "project2",
  "project_panel2",
+ "quick_action_bar2",
  "rand 0.8.5",
  "regex",
  "rope2",

Cargo.toml 🔗

@@ -89,6 +89,7 @@ members = [
     "crates/project_panel",
     "crates/project_panel2",
     "crates/project_symbols",
+    "crates/quick_action_bar2",
     "crates/recent_projects",
     "crates/rope",
     "crates/rpc",

assets/keymaps/default.json 🔗

@@ -17,18 +17,8 @@
       "cmd-enter": "menu::SecondaryConfirm",
       "escape": "menu::Cancel",
       "ctrl-c": "menu::Cancel",
-      "cmd-{": "pane::ActivatePrevItem",
-      "cmd-}": "pane::ActivateNextItem",
-      "alt-cmd-left": "pane::ActivatePrevItem",
-      "alt-cmd-right": "pane::ActivateNextItem",
-      "cmd-w": "pane::CloseActiveItem",
-      "alt-cmd-t": "pane::CloseInactiveItems",
-      "ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
-      "cmd-k u": "pane::CloseCleanItems",
-      "cmd-k cmd-w": "pane::CloseAllItems",
       "cmd-shift-w": "workspace::CloseWindow",
-      "cmd-s": "workspace::Save",
-      "cmd-shift-s": "workspace::SaveAs",
+      "cmd-o": "workspace::Open",
       "cmd-=": "zed::IncreaseBufferFontSize",
       "cmd-+": "zed::IncreaseBufferFontSize",
       "cmd--": "zed::DecreaseBufferFontSize",
@@ -38,15 +28,7 @@
       "cmd-h": "zed::Hide",
       "alt-cmd-h": "zed::HideOthers",
       "cmd-m": "zed::Minimize",
-      "ctrl-cmd-f": "zed::ToggleFullScreen",
-      "cmd-n": "workspace::NewFile",
-      "cmd-shift-n": "workspace::NewWindow",
-      "cmd-o": "workspace::Open",
-      "alt-cmd-o": "projects::OpenRecent",
-      "alt-cmd-b": "branches::OpenRecent",
-      "ctrl-~": "workspace::NewTerminal",
-      "ctrl-`": "terminal_panel::ToggleFocus",
-      "shift-escape": "workspace::ToggleZoom"
+      "ctrl-cmd-f": "zed::ToggleFullScreen"
     }
   },
   {
@@ -284,6 +266,15 @@
   {
     "context": "Pane",
     "bindings": {
+      "cmd-{": "pane::ActivatePrevItem",
+      "cmd-}": "pane::ActivateNextItem",
+      "alt-cmd-left": "pane::ActivatePrevItem",
+      "alt-cmd-right": "pane::ActivateNextItem",
+      "cmd-w": "pane::CloseActiveItem",
+      "alt-cmd-t": "pane::CloseInactiveItems",
+      "ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
+      "cmd-k u": "pane::CloseCleanItems",
+      "cmd-k cmd-w": "pane::CloseAllItems",
       "cmd-f": "project_search::ToggleFocus",
       "cmd-g": "search::SelectNextMatch",
       "cmd-shift-g": "search::SelectPrevMatch",
@@ -389,6 +380,15 @@
   {
     "context": "Workspace",
     "bindings": {
+      "alt-cmd-o": "projects::OpenRecent",
+      "alt-cmd-b": "branches::OpenRecent",
+      "ctrl-~": "workspace::NewTerminal",
+      "cmd-s": "workspace::Save",
+      "cmd-shift-s": "workspace::SaveAs",
+      "cmd-n": "workspace::NewFile",
+      "cmd-shift-n": "workspace::NewWindow",
+      "ctrl-`": "terminal_panel::ToggleFocus",
+      "shift-escape": "workspace::ToggleZoom",
       "cmd-1": ["workspace::ActivatePane", 0],
       "cmd-2": ["workspace::ActivatePane", 1],
       "cmd-3": ["workspace::ActivatePane", 2],

crates/breadcrumbs2/src/breadcrumbs.rs 🔗

@@ -1,10 +1,10 @@
 use gpui::{
-    Component, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
+    Div, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
     ViewContext, WeakView,
 };
 use itertools::Itertools;
 use theme::ActiveTheme;
-use ui::{ButtonCommon, ButtonLike, ButtonStyle, Clickable, Disableable, Label};
+use ui::{prelude::*, ButtonLike, ButtonStyle, Label};
 use workspace::{
     item::{ItemEvent, ItemHandle},
     ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
@@ -36,54 +36,51 @@ impl EventEmitter<Event> for Breadcrumbs {}
 impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
 
 impl Render for Breadcrumbs {
-    type Element = Component<ButtonLike>;
+    type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        let button = ButtonLike::new("breadcrumbs")
-            .style(ButtonStyle::Transparent)
-            .disabled(true);
+        let element = h_stack().text_ui();
+
+        let Some(active_item) = &self
+            .active_item
+            .as_ref()
+            .filter(|item| item.downcast::<editor::Editor>().is_some())
+        else {
+            return element;
+        };
 
-        let active_item = match &self.active_item {
-            Some(active_item) => active_item,
-            None => return button.into_element(),
+        let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
+            return element;
         };
-        let not_editor = active_item.downcast::<editor::Editor>().is_none();
 
-        let breadcrumbs = match active_item.breadcrumbs(cx.theme(), cx) {
-            Some(breadcrumbs) => breadcrumbs,
-            None => return button.into_element(),
-        }
-        .into_iter()
-        .map(|breadcrumb| {
-            StyledText::new(breadcrumb.text)
-                .with_highlights(&cx.text_style(), breadcrumb.highlights.unwrap_or_default())
+        let highlighted_segments = segments.into_iter().map(|segment| {
+            StyledText::new(segment.text)
+                .with_highlights(&cx.text_style(), segment.highlights.unwrap_or_default())
                 .into_any()
         });
+        let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
+            Label::new("›").into_any_element()
+        });
 
-        let button = button.children(Itertools::intersperse_with(breadcrumbs, || {
-            Label::new(" › ").into_any_element()
-        }));
-
-        if not_editor || !self.pane_focused {
-            return button.into_element();
-        }
-
-        // let this = cx.view().downgrade();
-        button
-            .style(ButtonStyle::Filled)
-            .disabled(false)
-            .on_click(move |_, _cx| {
-                todo!("outline::toggle");
-                // this.update(cx, |this, cx| {
-                //     if let Some(workspace) = this.workspace.upgrade() {
-                //         workspace.update(cx, |_workspace, _cx| {
-                //             outline::toggle(workspace, &Default::default(), cx)
-                //         })
-                //     }
-                // })
-                // .ok();
-            })
-            .into_element()
+        element.child(
+            ButtonLike::new("toggle outline view")
+                .style(ButtonStyle::Subtle)
+                .child(h_stack().gap_1().children(breadcrumbs))
+                // We disable the button when it is not focused
+                // due to ... @julia what was the reason again?
+                .disabled(!self.pane_focused)
+                .on_click(move |_, _cx| {
+                    todo!("outline::toggle");
+                    // this.update(cx, |this, cx| {
+                    //     if let Some(workspace) = this.workspace.upgrade() {
+                    //         workspace.update(cx, |_workspace, _cx| {
+                    //             outline::toggle(workspace, &Default::default(), cx)
+                    //         })
+                    //     }
+                    // })
+                    // .ok();
+                }),
+        )
     }
 }
 

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -169,7 +169,7 @@ use editor::Editor;
 use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
 use fuzzy::{match_strings, StringMatchCandidate};
 use gpui::{
-    actions, canvas, div, img, overlay, point, prelude::*, px, rems, serde_json, Action,
+    actions, canvas, div, img, overlay, point, prelude::*, px, rems, serde_json, size, Action,
     AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter,
     FocusHandle, Focusable, FocusableView, Hsla, InteractiveElement, IntoElement, Length, Model,
     MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Quad, Render, RenderOnce,
@@ -1204,14 +1204,9 @@ impl CollabPanel {
                         .detach_and_log_err(cx);
                 });
             }))
-            .left_child(IconButton::new(0, Icon::Folder))
-            .child(
-                h_stack()
-                    .w_full()
-                    .justify_between()
-                    .child(render_tree_branch(is_last, cx))
-                    .child(Label::new(project_name.clone())),
-            )
+            .left_child(render_tree_branch(is_last, cx))
+            .child(IconButton::new(0, Icon::Folder))
+            .child(Label::new(project_name.clone()))
             .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx))
 
         //         enum JoinProject {}
@@ -1299,70 +1294,20 @@ impl CollabPanel {
         is_last: bool,
         cx: &mut ViewContext<Self>,
     ) -> impl IntoElement {
-        //         enum OpenSharedScreen {}
-
-        //         let host_avatar_width = theme
-        //             .contact_avatar
-        //             .width
-        //             .or(theme.contact_avatar.height)
-        //             .unwrap_or(0.);
-        //         let tree_branch = theme.tree_branch;
-
-        //         let handler = MouseEventHandler::new::<OpenSharedScreen, _>(
-        //             peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize,
-        //             cx,
-        //             |mouse_state, cx| {
-        //                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
-        //                 let row = theme
-        //                     .project_row
-        //                     .in_state(is_selected)
-        //                     .style_for(mouse_state);
-
-        //                 Flex::row()
-        //                     .with_child(render_tree_branch(
-        //                         tree_branch,
-        //                         &row.name.text,
-        //                         is_last,
-        //                         vec2f(host_avatar_width, theme.row_height),
-        //                         cx.font_cache(),
-        //                     ))
-        //                     .with_child(
-        //                         Svg::new("icons/desktop.svg")
-        //                             .with_color(theme.channel_hash.color)
-        //                             .constrained()
-        //                             .with_width(theme.channel_hash.width)
-        //                             .aligned()
-        //                             .left(),
-        //                     )
-        //                     .with_child(
-        //                         Label::new("Screen", row.name.text.clone())
-        //                             .aligned()
-        //                             .left()
-        //                             .contained()
-        //                             .with_style(row.name.container)
-        //                             .flex(1., false),
-        //                     )
-        //                     .constrained()
-        //                     .with_height(theme.row_height)
-        //                     .contained()
-        //                     .with_style(row.container)
-        //             },
-        //         );
-        //         if peer_id.is_none() {
-        //             return handler.into_any();
-        //         }
-        //         handler
-        //             .with_cursor_style(CursorStyle::PointingHand)
-        //             .on_click(MouseButton::Left, move |_, this, cx| {
-        //                 if let Some(workspace) = this.workspace.upgrade(cx) {
-        //                     workspace.update(cx, |workspace, cx| {
-        //                         workspace.open_shared_screen(peer_id.unwrap(), cx)
-        //                     });
-        //                 }
-        //             })
-        //             .into_any()
-
-        div()
+        let id = peer_id.map_or(usize::MAX, |id| id.as_u64() as usize);
+
+        ListItem::new(("screen", id))
+            .left_child(render_tree_branch(is_last, cx))
+            .child(IconButton::new(0, Icon::Screen))
+            .child(Label::new("Screen"))
+            .when_some(peer_id, |this, _| {
+                this.on_click(cx.listener(move |this, _, cx| {
+                    this.workspace.update(cx, |workspace, cx| {
+                        workspace.open_shared_screen(peer_id.unwrap(), cx)
+                    });
+                }))
+                .tooltip(move |cx| Tooltip::text(format!("Open shared screen"), cx))
+            })
     }
 
     fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
@@ -1415,54 +1360,14 @@ impl CollabPanel {
         channel_id: ChannelId,
         cx: &mut ViewContext<Self>,
     ) -> impl IntoElement {
-        //         enum ChannelNotes {}
-        //         let host_avatar_width = theme
-        //             .contact_avatar
-        //             .width
-        //             .or(theme.contact_avatar.height)
-        //             .unwrap_or(0.);
-
-        //         MouseEventHandler::new::<ChannelNotes, _>(ix as usize, cx, |state, cx| {
-        //             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
-        //             let row = theme.project_row.in_state(is_selected).style_for(state);
-
-        //             Flex::<Self>::row()
-        //                 .with_child(render_tree_branch(
-        //                     tree_branch,
-        //                     &row.name.text,
-        //                     false,
-        //                     vec2f(host_avatar_width, theme.row_height),
-        //                     cx.font_cache(),
-        //                 ))
-        //                 .with_child(
-        //                     Svg::new("icons/file.svg")
-        //                         .with_color(theme.channel_hash.color)
-        //                         .constrained()
-        //                         .with_width(theme.channel_hash.width)
-        //                         .aligned()
-        //                         .left(),
-        //                 )
-        //                 .with_child(
-        //                     Label::new("notes", theme.channel_name.text.clone())
-        //                         .contained()
-        //                         .with_style(theme.channel_name.container)
-        //                         .aligned()
-        //                         .left()
-        //                         .flex(1., true),
-        //                 )
-        //                 .constrained()
-        //                 .with_height(theme.row_height)
-        //                 .contained()
-        //                 .with_style(*theme.channel_row.style_for(is_selected, state))
-        //                 .with_padding_left(theme.channel_row.default_style().padding.left)
-        //         })
-        //         .on_click(MouseButton::Left, move |_, this, cx| {
-        //             this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
-        //         })
-        //         .with_cursor_style(CursorStyle::PointingHand)
-        //         .into_any()
-
-        div()
+        ListItem::new("channel-notes")
+            .on_click(cx.listener(move |this, _, cx| {
+                this.open_channel_notes(channel_id, cx);
+            }))
+            .left_child(render_tree_branch(false, cx))
+            .child(IconButton::new(0, Icon::File))
+            .child(Label::new("notes"))
+            .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx))
     }
 
     fn render_channel_chat(
@@ -1470,53 +1375,14 @@ impl CollabPanel {
         channel_id: ChannelId,
         cx: &mut ViewContext<Self>,
     ) -> impl IntoElement {
-        //         enum ChannelChat {}
-        //         let host_avatar_width = theme
-        //             .contact_avatar
-        //             .width
-        //             .or(theme.contact_avatar.height)
-        //             .unwrap_or(0.);
-
-        //         MouseEventHandler::new::<ChannelChat, _>(ix as usize, cx, |state, cx| {
-        //             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
-        //             let row = theme.project_row.in_state(is_selected).style_for(state);
-
-        //             Flex::<Self>::row()
-        //                 .with_child(render_tree_branch(
-        //                     tree_branch,
-        //                     &row.name.text,
-        //                     true,
-        //                     vec2f(host_avatar_width, theme.row_height),
-        //                     cx.font_cache(),
-        //                 ))
-        //                 .with_child(
-        //                     Svg::new("icons/conversations.svg")
-        //                         .with_color(theme.channel_hash.color)
-        //                         .constrained()
-        //                         .with_width(theme.channel_hash.width)
-        //                         .aligned()
-        //                         .left(),
-        //                 )
-        //                 .with_child(
-        //                     Label::new("chat", theme.channel_name.text.clone())
-        //                         .contained()
-        //                         .with_style(theme.channel_name.container)
-        //                         .aligned()
-        //                         .left()
-        //                         .flex(1., true),
-        //                 )
-        //                 .constrained()
-        //                 .with_height(theme.row_height)
-        //                 .contained()
-        //                 .with_style(*theme.channel_row.style_for(is_selected, state))
-        //                 .with_padding_left(theme.channel_row.default_style().padding.left)
-        //         })
-        //         .on_click(MouseButton::Left, move |_, this, cx| {
-        //             this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
-        //         })
-        //         .with_cursor_style(CursorStyle::PointingHand)
-        //         .into_any()
-        div()
+        ListItem::new("channel-chat")
+            .on_click(cx.listener(move |this, _, cx| {
+                this.join_channel_chat(channel_id, cx);
+            }))
+            .left_child(render_tree_branch(true, cx))
+            .child(IconButton::new(0, Icon::MessageBubbles))
+            .child(Label::new("chat"))
+            .tooltip(move |cx| Tooltip::text("Open Chat", cx))
     }
 
     //     fn render_channel_invite(
@@ -3119,30 +2985,24 @@ impl CollabPanel {
 }
 
 fn render_tree_branch(is_last: bool, cx: &mut WindowContext) -> impl IntoElement {
-    let text_style = cx.text_style();
     let rem_size = cx.rem_size();
-    let text_system = cx.text_system();
-    let font_id = text_system.font_id(&text_style.font()).unwrap();
-    let font_size = text_style.font_size.to_pixels(rem_size);
-    let line_height = text_style.line_height_in_pixels(rem_size);
-    let cap_height = text_system.cap_height(font_id, font_size);
-    let baseline_offset = text_system.baseline_offset(font_id, font_size, line_height);
-    let width = cx.rem_size() * 2.5;
+    let line_height = cx.text_style().line_height_in_pixels(rem_size);
+    let width = rem_size * 1.5;
     let thickness = px(2.);
     let color = cx.theme().colors().text;
 
     canvas(move |bounds, cx| {
-        let start_x = bounds.left() + (bounds.size.width / 2.) - (width / 2.);
-        let end_x = bounds.right();
-        let start_y = bounds.top();
-        let end_y = bounds.top() + baseline_offset - (cap_height / 2.);
+        let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
+        let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
+        let right = bounds.right();
+        let top = bounds.top();
 
         cx.paint_quad(
             Bounds::from_corners(
-                point(start_x, start_y),
+                point(start_x, top),
                 point(
                     start_x + thickness,
-                    if is_last { end_y } else { bounds.bottom() },
+                    if is_last { start_y } else { bounds.bottom() },
                 ),
             ),
             Default::default(),
@@ -3151,7 +3011,7 @@ fn render_tree_branch(is_last: bool, cx: &mut WindowContext) -> impl IntoElement
             Hsla::transparent_black(),
         );
         cx.paint_quad(
-            Bounds::from_corners(point(start_x, end_y), point(end_x, end_y + thickness)),
+            Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
             Default::default(),
             color,
             Default::default(),

crates/diagnostics2/src/diagnostics.rs 🔗

@@ -88,7 +88,7 @@ struct DiagnosticGroupState {
     block_count: usize,
 }
 
-impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}
+impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
 
 impl Render for ProjectDiagnosticsEditor {
     type Element = Focusable<Div>;
@@ -158,7 +158,7 @@ impl ProjectDiagnosticsEditor {
         });
         let editor_event_subscription =
             cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
-                Self::emit_item_event_for_editor_event(event, cx);
+                cx.emit(event.clone());
                 if event == &EditorEvent::Focused && this.path_states.is_empty() {
                     cx.focus(&this.focus_handle);
                 }
@@ -183,40 +183,6 @@ impl ProjectDiagnosticsEditor {
         this
     }
 
-    fn emit_item_event_for_editor_event(event: &EditorEvent, cx: &mut ViewContext<Self>) {
-        match event {
-            EditorEvent::Closed => cx.emit(ItemEvent::CloseItem),
-
-            EditorEvent::Saved | EditorEvent::TitleChanged => {
-                cx.emit(ItemEvent::UpdateTab);
-                cx.emit(ItemEvent::UpdateBreadcrumbs);
-            }
-
-            EditorEvent::Reparsed => {
-                cx.emit(ItemEvent::UpdateBreadcrumbs);
-            }
-
-            EditorEvent::SelectionsChanged { local } if *local => {
-                cx.emit(ItemEvent::UpdateBreadcrumbs);
-            }
-
-            EditorEvent::DirtyChanged => {
-                cx.emit(ItemEvent::UpdateTab);
-            }
-
-            EditorEvent::BufferEdited => {
-                cx.emit(ItemEvent::Edit);
-                cx.emit(ItemEvent::UpdateBreadcrumbs);
-            }
-
-            EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
-                cx.emit(ItemEvent::Edit);
-            }
-
-            _ => {}
-        }
-    }
-
     fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
         if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
             workspace.activate_item(&existing, cx);
@@ -333,8 +299,7 @@ impl ProjectDiagnosticsEditor {
 
                 this.update(&mut cx, |this, cx| {
                     this.summary = this.project.read(cx).diagnostic_summary(false, cx);
-                    cx.emit(ItemEvent::UpdateTab);
-                    cx.emit(ItemEvent::UpdateBreadcrumbs);
+                    cx.emit(EditorEvent::TitleChanged);
                 })?;
                 anyhow::Ok(())
             }
@@ -649,6 +614,12 @@ impl FocusableView for ProjectDiagnosticsEditor {
 }
 
 impl Item for ProjectDiagnosticsEditor {
+    type Event = EditorEvent;
+
+    fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
+        Editor::to_item_events(event, f)
+    }
+
     fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
         self.editor.update(cx, |editor, cx| editor.deactivated(cx));
     }

crates/editor2/src/editor.rs 🔗

@@ -1675,8 +1675,7 @@ impl Editor {
             if let Some(project) = project.as_ref() {
                 if buffer.read(cx).is_singleton() {
                     project_subscriptions.push(cx.observe(project, |_, _, cx| {
-                        cx.emit(ItemEvent::UpdateTab);
-                        cx.emit(ItemEvent::UpdateBreadcrumbs);
+                        cx.emit(EditorEvent::TitleChanged);
                     }));
                 }
                 project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
@@ -2141,10 +2140,6 @@ impl Editor {
             cx.emit(SearchEvent::ActiveMatchChanged)
         }
 
-        if local {
-            cx.emit(ItemEvent::UpdateBreadcrumbs);
-        }
-
         cx.notify();
     }
 
@@ -8573,8 +8568,6 @@ impl Editor {
                     self.update_visible_copilot_suggestion(cx);
                 }
                 cx.emit(EditorEvent::BufferEdited);
-                cx.emit(ItemEvent::Edit);
-                cx.emit(ItemEvent::UpdateBreadcrumbs);
                 cx.emit(SearchEvent::MatchesInvalidated);
 
                 if *sigleton_buffer_edited {
@@ -8622,20 +8615,14 @@ impl Editor {
                 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
                 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
             }
-            multi_buffer::Event::Reparsed => {
-                cx.emit(ItemEvent::UpdateBreadcrumbs);
-            }
-            multi_buffer::Event::DirtyChanged => {
-                cx.emit(ItemEvent::UpdateTab);
-            }
-            multi_buffer::Event::Saved
-            | multi_buffer::Event::FileHandleChanged
-            | multi_buffer::Event::Reloaded => {
-                cx.emit(ItemEvent::UpdateTab);
-                cx.emit(ItemEvent::UpdateBreadcrumbs);
+            multi_buffer::Event::Reparsed => cx.emit(EditorEvent::Reparsed),
+            multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
+            multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
+            multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
+                cx.emit(EditorEvent::TitleChanged)
             }
             multi_buffer::Event::DiffBaseChanged => cx.emit(EditorEvent::DiffBaseChanged),
-            multi_buffer::Event::Closed => cx.emit(ItemEvent::CloseItem),
+            multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
             multi_buffer::Event::DiagnosticsUpdated => {
                 self.refresh_active_diagnostics(cx);
             }

crates/editor2/src/editor_tests.rs 🔗

@@ -32,7 +32,7 @@ use util::{
     test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
 };
 use workspace::{
-    item::{FollowEvent, FollowableEvents, FollowableItem, Item, ItemHandle},
+    item::{FollowEvent, FollowableItem, Item, ItemHandle},
     NavigationEntry, ViewId,
 };
 
@@ -6476,7 +6476,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
             cx.subscribe(
                 &follower.root_view(cx).unwrap(),
                 move |_, _, event: &EditorEvent, cx| {
-                    if matches!(event.to_follow_event(), Some(FollowEvent::Unfollow)) {
+                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
                         *is_still_following.borrow_mut() = false;
                     }
 

crates/editor2/src/items.rs 🔗

@@ -32,10 +32,10 @@ use std::{
 };
 use text::Selection;
 use theme::{ActiveTheme, Theme};
-use ui::{Color, Label};
+use ui::{h_stack, Color, Label};
 use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
 use workspace::{
-    item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle},
+    item::{BreadcrumbText, FollowEvent, FollowableItemHandle},
     StatusItemView,
 };
 use workspace::{
@@ -46,27 +46,7 @@ use workspace::{
 
 pub const MAX_TAB_TITLE_LEN: usize = 24;
 
-impl FollowableEvents for EditorEvent {
-    fn to_follow_event(&self) -> Option<workspace::item::FollowEvent> {
-        match self {
-            EditorEvent::Edited => Some(FollowEvent::Unfollow),
-            EditorEvent::SelectionsChanged { local }
-            | EditorEvent::ScrollPositionChanged { local, .. } => {
-                if *local {
-                    Some(FollowEvent::Unfollow)
-                } else {
-                    None
-                }
-            }
-            _ => None,
-        }
-    }
-}
-
-impl EventEmitter<ItemEvent> for Editor {}
-
 impl FollowableItem for Editor {
-    type FollowableEvent = EditorEvent;
     fn remote_id(&self) -> Option<ViewId> {
         self.remote_id
     }
@@ -241,9 +221,24 @@ impl FollowableItem for Editor {
         }))
     }
 
+    fn to_follow_event(event: &EditorEvent) -> Option<workspace::item::FollowEvent> {
+        match event {
+            EditorEvent::Edited => Some(FollowEvent::Unfollow),
+            EditorEvent::SelectionsChanged { local }
+            | EditorEvent::ScrollPositionChanged { local, .. } => {
+                if *local {
+                    Some(FollowEvent::Unfollow)
+                } else {
+                    None
+                }
+            }
+            _ => None,
+        }
+    }
+
     fn add_event_to_update_proto(
         &self,
-        event: &Self::FollowableEvent,
+        event: &EditorEvent,
         update: &mut Option<proto::update_view::Variant>,
         cx: &WindowContext,
     ) -> bool {
@@ -528,6 +523,8 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
 }
 
 impl Item for Editor {
+    type Event = EditorEvent;
+
     fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
         if let Ok(data) = data.downcast::<NavigationData>() {
             let newest_selection = self.selections.newest::<Point>(cx);
@@ -586,28 +583,25 @@ impl Item for Editor {
     fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
         let theme = cx.theme();
 
-        AnyElement::new(
-            div()
-                .flex()
-                .flex_row()
-                .items_center()
-                .gap_2()
-                .child(Label::new(self.title(cx).to_string()))
-                .children(detail.and_then(|detail| {
-                    let path = path_for_buffer(&self.buffer, detail, false, cx)?;
-                    let description = path.to_string_lossy();
-
-                    Some(
-                        div().child(
-                            Label::new(util::truncate_and_trailoff(
-                                &description,
-                                MAX_TAB_TITLE_LEN,
-                            ))
-                            .color(Color::Muted),
-                        ),
-                    )
-                })),
-        )
+        let description = detail.and_then(|detail| {
+            let path = path_for_buffer(&self.buffer, detail, false, cx)?;
+            let description = path.to_string_lossy();
+            let description = description.trim();
+
+            if description.is_empty() {
+                return None;
+            }
+
+            Some(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN))
+        });
+
+        h_stack()
+            .gap_2()
+            .child(Label::new(self.title(cx).to_string()))
+            .when_some(description, |this, description| {
+                this.child(Label::new(description).color(Color::Muted))
+            })
+            .into_any_element()
     }
 
     fn for_each_project_item(
@@ -841,6 +835,40 @@ impl Item for Editor {
         Some("Editor")
     }
 
+    fn to_item_events(event: &EditorEvent, mut f: impl FnMut(ItemEvent)) {
+        match event {
+            EditorEvent::Closed => f(ItemEvent::CloseItem),
+
+            EditorEvent::Saved | EditorEvent::TitleChanged => {
+                f(ItemEvent::UpdateTab);
+                f(ItemEvent::UpdateBreadcrumbs);
+            }
+
+            EditorEvent::Reparsed => {
+                f(ItemEvent::UpdateBreadcrumbs);
+            }
+
+            EditorEvent::SelectionsChanged { local } if *local => {
+                f(ItemEvent::UpdateBreadcrumbs);
+            }
+
+            EditorEvent::DirtyChanged => {
+                f(ItemEvent::UpdateTab);
+            }
+
+            EditorEvent::BufferEdited => {
+                f(ItemEvent::Edit);
+                f(ItemEvent::UpdateBreadcrumbs);
+            }
+
+            EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
+                f(ItemEvent::Edit);
+            }
+
+            _ => {}
+        }
+    }
+
     fn deserialize(
         project: Model<Project>,
         _workspace: WeakView<Workspace>,

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

@@ -1,9 +1,11 @@
-use crate::{Bounds, Element, IntoElement, Pixels, StyleRefinement, Styled, WindowContext};
+use refineable::Refineable as _;
+
+use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext};
 
 pub fn canvas(callback: impl 'static + FnOnce(Bounds<Pixels>, &mut WindowContext)) -> Canvas {
     Canvas {
         paint_callback: Box::new(callback),
-        style: Default::default(),
+        style: StyleRefinement::default(),
     }
 }
 
@@ -32,7 +34,9 @@ impl Element for Canvas {
         _: Option<Self::State>,
         cx: &mut WindowContext,
     ) -> (crate::LayoutId, Self::State) {
-        let layout_id = cx.request_layout(&self.style.clone().into(), []);
+        let mut style = Style::default();
+        style.refine(&self.style);
+        let layout_id = cx.request_layout(&style, []);
         (layout_id, ())
     }
 

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

@@ -7,6 +7,7 @@ use std::{
 use crate::DisplayId;
 use collections::HashMap;
 use parking_lot::Mutex;
+pub use sys::CVSMPTETime as SmtpeTime;
 pub use sys::CVTimeStamp as VideoTimestamp;
 
 pub(crate) struct MacDisplayLinker {
@@ -153,7 +154,7 @@ mod sys {
         kCVTimeStampTopField | kCVTimeStampBottomField;
 
     #[repr(C)]
-    #[derive(Clone, Copy)]
+    #[derive(Clone, Copy, Default)]
     pub struct CVSMPTETime {
         pub subframes: i16,
         pub subframe_divisor: i16,

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

@@ -148,18 +148,25 @@ impl Platform for TestPlatform {
     fn set_display_link_output_callback(
         &self,
         _display_id: DisplayId,
-        _callback: Box<dyn FnMut(&crate::VideoTimestamp, &crate::VideoTimestamp) + Send>,
+        mut callback: Box<dyn FnMut(&crate::VideoTimestamp, &crate::VideoTimestamp) + Send>,
     ) {
-        unimplemented!()
-    }
-
-    fn start_display_link(&self, _display_id: DisplayId) {
-        unimplemented!()
-    }
-
-    fn stop_display_link(&self, _display_id: DisplayId) {
-        unimplemented!()
-    }
+        let timestamp = crate::VideoTimestamp {
+            version: 0,
+            video_time_scale: 0,
+            video_time: 0,
+            host_time: 0,
+            rate_scalar: 0.0,
+            video_refresh_period: 0,
+            smpte_time: crate::SmtpeTime::default(),
+            flags: 0,
+            reserved: 0,
+        };
+        callback(&timestamp, &timestamp)
+    }
+
+    fn start_display_link(&self, _display_id: DisplayId) {}
+
+    fn stop_display_link(&self, _display_id: DisplayId) {}
 
     fn open_url(&self, _url: &str) {
         unimplemented!()

crates/gpui2/src/window.rs 🔗

@@ -2816,3 +2816,9 @@ impl From<(&'static str, EntityId)> for ElementId {
         ElementId::NamedInteger(name.into(), id.as_u64() as usize)
     }
 }
+
+impl From<(&'static str, usize)> for ElementId {
+    fn from((name, id): (&'static str, usize)) -> Self {
+        ElementId::NamedInteger(name.into(), id)
+    }
+}

crates/quick_action_bar2/Cargo.toml 🔗

@@ -0,0 +1,22 @@
+[package]
+name = "quick_action_bar2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/quick_action_bar.rs"
+doctest = false
+
+[dependencies]
+#assistant = { path = "../assistant" }
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+search = { package = "search2", path = "../search2" }
+workspace = { package = "workspace2", path = "../workspace2" }
+ui = { package = "ui2", path = "../ui2" }
+
+[dev-dependencies]
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }

crates/quick_action_bar2/src/quick_action_bar.rs 🔗

@@ -0,0 +1,288 @@
+// use assistant::{assistant_panel::InlineAssist, AssistantPanel};
+use editor::Editor;
+
+use gpui::{
+    Action, Div, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Stateful,
+    Styled, Subscription, View, ViewContext, WeakView,
+};
+use search::BufferSearchBar;
+use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip};
+use workspace::{
+    item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
+};
+
+pub struct QuickActionBar {
+    buffer_search_bar: View<BufferSearchBar>,
+    active_item: Option<Box<dyn ItemHandle>>,
+    _inlay_hints_enabled_subscription: Option<Subscription>,
+    #[allow(unused)]
+    workspace: WeakView<Workspace>,
+}
+
+impl QuickActionBar {
+    pub fn new(buffer_search_bar: View<BufferSearchBar>, workspace: &Workspace) -> Self {
+        Self {
+            buffer_search_bar,
+            active_item: None,
+            _inlay_hints_enabled_subscription: None,
+            workspace: workspace.weak_handle(),
+        }
+    }
+
+    #[allow(dead_code)]
+    fn active_editor(&self) -> Option<View<Editor>> {
+        self.active_item
+            .as_ref()
+            .and_then(|item| item.downcast::<Editor>())
+    }
+}
+
+impl Render for QuickActionBar {
+    type Element = Stateful<Div>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let search_button = QuickActionBarButton::new(
+            "toggle buffer search",
+            Icon::MagnifyingGlass,
+            !self.buffer_search_bar.read(cx).is_dismissed(),
+            Box::new(search::buffer_search::Deploy { focus: false }),
+            "Buffer Search",
+        );
+        let assistant_button = QuickActionBarButton::new(
+            "toggle inline assitant",
+            Icon::MagicWand,
+            false,
+            Box::new(gpui::NoAction),
+            "Inline assistant",
+        );
+        h_stack()
+            .id("quick action bar")
+            .p_1()
+            .gap_2()
+            .child(search_button)
+            .child(
+                div()
+                    .border()
+                    .border_color(gpui::red())
+                    .child(assistant_button),
+            )
+    }
+}
+
+impl EventEmitter<ToolbarItemEvent> for QuickActionBar {}
+
+// impl View for QuickActionBar {
+//     fn ui_name() -> &'static str {
+//         "QuickActionsBar"
+//     }
+
+//     fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
+//         let Some(editor) = self.active_editor() else {
+//             return div();
+//         };
+
+//         let mut bar = Flex::row();
+//         if editor.read(cx).supports_inlay_hints(cx) {
+//             bar = bar.with_child(render_quick_action_bar_button(
+//                 0,
+//                 "icons/inlay_hint.svg",
+//                 editor.read(cx).inlay_hints_enabled(),
+//                 (
+//                     "Toggle Inlay Hints".to_string(),
+//                     Some(Box::new(editor::ToggleInlayHints)),
+//                 ),
+//                 cx,
+//                 |this, cx| {
+//                     if let Some(editor) = this.active_editor() {
+//                         editor.update(cx, |editor, cx| {
+//                             editor.toggle_inlay_hints(&editor::ToggleInlayHints, cx);
+//                         });
+//                     }
+//                 },
+//             ));
+//         }
+
+//         if editor.read(cx).buffer().read(cx).is_singleton() {
+//             let search_bar_shown = !self.buffer_search_bar.read(cx).is_dismissed();
+//             let search_action = buffer_search::Deploy { focus: true };
+
+//             bar = bar.with_child(render_quick_action_bar_button(
+//                 1,
+//                 "icons/magnifying_glass.svg",
+//                 search_bar_shown,
+//                 (
+//                     "Buffer Search".to_string(),
+//                     Some(Box::new(search_action.clone())),
+//                 ),
+//                 cx,
+//                 move |this, cx| {
+//                     this.buffer_search_bar.update(cx, |buffer_search_bar, cx| {
+//                         if search_bar_shown {
+//                             buffer_search_bar.dismiss(&buffer_search::Dismiss, cx);
+//                         } else {
+//                             buffer_search_bar.deploy(&search_action, cx);
+//                         }
+//                     });
+//                 },
+//             ));
+//         }
+
+//         bar.add_child(render_quick_action_bar_button(
+//             2,
+//             "icons/magic-wand.svg",
+//             false,
+//             ("Inline Assist".into(), Some(Box::new(InlineAssist))),
+//             cx,
+//             move |this, cx| {
+//                 if let Some(workspace) = this.workspace.upgrade(cx) {
+//                     workspace.update(cx, |workspace, cx| {
+//                         AssistantPanel::inline_assist(workspace, &Default::default(), cx);
+//                     });
+//                 }
+//             },
+//         ));
+
+//         bar.into_any()
+//     }
+// }
+
+#[derive(IntoElement)]
+struct QuickActionBarButton {
+    id: ElementId,
+    icon: Icon,
+    toggled: bool,
+    action: Box<dyn Action>,
+    tooltip: SharedString,
+    tooltip_meta: Option<SharedString>,
+}
+
+impl QuickActionBarButton {
+    fn new(
+        id: impl Into<ElementId>,
+        icon: Icon,
+        toggled: bool,
+        action: Box<dyn Action>,
+        tooltip: impl Into<SharedString>,
+    ) -> Self {
+        Self {
+            id: id.into(),
+            icon,
+            toggled,
+            action,
+            tooltip: tooltip.into(),
+            tooltip_meta: None,
+        }
+    }
+
+    #[allow(dead_code)]
+    pub fn meta(mut self, meta: Option<impl Into<SharedString>>) -> Self {
+        self.tooltip_meta = meta.map(|meta| meta.into());
+        self
+    }
+}
+
+impl RenderOnce for QuickActionBarButton {
+    type Rendered = IconButton;
+
+    fn render(self, _: &mut WindowContext) -> Self::Rendered {
+        let tooltip = self.tooltip.clone();
+        let action = self.action.boxed_clone();
+        let tooltip_meta = self.tooltip_meta.clone();
+
+        IconButton::new(self.id.clone(), self.icon)
+            .size(ButtonSize::Compact)
+            .icon_size(IconSize::Small)
+            .style(ButtonStyle::Subtle)
+            .selected(self.toggled)
+            .tooltip(move |cx| {
+                if let Some(meta) = &tooltip_meta {
+                    Tooltip::with_meta(tooltip.clone(), Some(&*action), meta.clone(), cx)
+                } else {
+                    Tooltip::for_action(tooltip.clone(), &*action, cx)
+                }
+            })
+            .on_click({
+                let action = self.action.boxed_clone();
+                move |_, cx| cx.dispatch_action(action.boxed_clone())
+            })
+    }
+}
+
+// fn render_quick_action_bar_button<
+//     F: 'static + Fn(&mut QuickActionBar, &mut ViewContext<QuickActionBar>),
+// >(
+//     index: usize,
+//     icon: &'static str,
+//     toggled: bool,
+//     tooltip: (String, Option<Box<dyn Action>>),
+//     cx: &mut ViewContext<QuickActionBar>,
+//     on_click: F,
+// ) -> AnyElement<QuickActionBar> {
+//     enum QuickActionBarButton {}
+
+//     let theme = theme::current(cx);
+//     let (tooltip_text, action) = tooltip;
+
+//     MouseEventHandler::new::<QuickActionBarButton, _>(index, cx, |mouse_state, _| {
+//         let style = theme
+//             .workspace
+//             .toolbar
+//             .toggleable_tool
+//             .in_state(toggled)
+//             .style_for(mouse_state);
+//         Svg::new(icon)
+//             .with_color(style.color)
+//             .constrained()
+//             .with_width(style.icon_width)
+//             .aligned()
+//             .constrained()
+//             .with_width(style.button_width)
+//             .with_height(style.button_width)
+//             .contained()
+//             .with_style(style.container)
+//     })
+//     .with_cursor_style(CursorStyle::PointingHand)
+//     .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
+//     .with_tooltip::<QuickActionBarButton>(index, tooltip_text, action, theme.tooltip.clone(), cx)
+//     .into_any_named("quick action bar button")
+// }
+
+impl ToolbarItemView for QuickActionBar {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        cx: &mut ViewContext<Self>,
+    ) -> ToolbarItemLocation {
+        match active_pane_item {
+            Some(active_item) => {
+                self.active_item = Some(active_item.boxed_clone());
+                self._inlay_hints_enabled_subscription.take();
+
+                if let Some(editor) = active_item.downcast::<Editor>() {
+                    let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
+                    let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
+                    self._inlay_hints_enabled_subscription =
+                        Some(cx.observe(&editor, move |_, editor, cx| {
+                            let editor = editor.read(cx);
+                            let new_inlay_hints_enabled = editor.inlay_hints_enabled();
+                            let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
+                            let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
+                                || supports_inlay_hints != new_supports_inlay_hints;
+                            inlay_hints_enabled = new_inlay_hints_enabled;
+                            supports_inlay_hints = new_supports_inlay_hints;
+                            if should_notify {
+                                cx.notify()
+                            }
+                        }));
+                    ToolbarItemLocation::PrimaryRight
+                } else {
+                    ToolbarItemLocation::Hidden
+                }
+            }
+            None => {
+                self.active_item = None;
+                ToolbarItemLocation::Hidden
+            }
+        }
+    }
+}

crates/terminal_view2/src/terminal_view.rs 🔗

@@ -736,6 +736,8 @@ impl InputHandler for TerminalView {
 }
 
 impl Item for TerminalView {
+    type Event = ItemEvent;
+
     fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
         Some(self.terminal().read(cx).title().into())
     }
@@ -843,6 +845,10 @@ impl Item for TerminalView {
         //     .detach();
         self.workspace_id = workspace.database_id();
     }
+
+    fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
+        f(*event)
+    }
 }
 
 impl SearchableItem for TerminalView {

crates/theme2/src/default_colors.rs 🔗

@@ -5,7 +5,7 @@ use crate::ColorScale;
 use crate::{SystemColors, ThemeColors};
 
 pub(crate) fn neutral() -> ColorScaleSet {
-    slate()
+    sand()
 }
 
 impl ThemeColors {
@@ -29,12 +29,12 @@ impl ThemeColors {
             element_disabled: neutral().light_alpha().step_3(),
             drop_target_background: blue().light_alpha().step_2(),
             ghost_element_background: system.transparent,
-            ghost_element_hover: neutral().light_alpha().step_4(),
-            ghost_element_active: neutral().light_alpha().step_5(),
+            ghost_element_hover: neutral().light_alpha().step_3(),
+            ghost_element_active: neutral().light_alpha().step_4(),
             ghost_element_selected: neutral().light_alpha().step_5(),
             ghost_element_disabled: neutral().light_alpha().step_3(),
-            text: yellow().light().step_9(),
-            text_muted: neutral().light().step_11(),
+            text: neutral().light().step_12(),
+            text_muted: neutral().light().step_10(),
             text_placeholder: neutral().light().step_10(),
             text_disabled: neutral().light().step_9(),
             text_accent: blue().light().step_11(),
@@ -53,13 +53,13 @@ impl ThemeColors {
             editor_gutter_background: neutral().light().step_1(), // todo!("pick the right colors")
             editor_subheader_background: neutral().light().step_2(),
             editor_active_line_background: neutral().light_alpha().step_3(),
-            editor_line_number: neutral().light_alpha().step_3(), // todo!("pick the right colors")
-            editor_active_line_number: neutral().light_alpha().step_3(), // todo!("pick the right colors")
-            editor_highlighted_line_background: neutral().light_alpha().step_4(), // todo!("pick the right colors")
-            editor_invisible: neutral().light_alpha().step_4(), // todo!("pick the right colors")
-            editor_wrap_guide: neutral().light_alpha().step_4(), // todo!("pick the right colors")
-            editor_active_wrap_guide: neutral().light_alpha().step_4(), // todo!("pick the right colors")
-            editor_document_highlight_read_background: neutral().light_alpha().step_4(), // todo!("pick the right colors")
+            editor_line_number: neutral().light().step_10(),
+            editor_active_line_number: neutral().light().step_11(),
+            editor_highlighted_line_background: neutral().light_alpha().step_3(),
+            editor_invisible: neutral().light().step_10(),
+            editor_wrap_guide: neutral().light_alpha().step_7(),
+            editor_active_wrap_guide: neutral().light_alpha().step_8(), // todo!("pick the right colors")
+            editor_document_highlight_read_background: neutral().light_alpha().step_3(), // todo!("pick the right colors")
             editor_document_highlight_write_background: neutral().light_alpha().step_4(), // todo!("pick the right colors")
             terminal_background: neutral().light().step_1(),
             terminal_ansi_black: black().light().step_12(),

crates/theme2/src/default_theme.rs 🔗

@@ -1,47 +1,51 @@
+use std::sync::Arc;
+
 use crate::{
+    default_color_scales,
     one_themes::{one_dark, one_family},
-    Theme, ThemeFamily,
+    Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme, ThemeColors,
+    ThemeFamily, ThemeStyles,
 };
 
-// fn zed_pro_daylight() -> Theme {
-//     Theme {
-//         id: "zed_pro_daylight".to_string(),
-//         name: "Zed Pro Daylight".into(),
-//         appearance: Appearance::Light,
-//         styles: ThemeStyles {
-//             system: SystemColors::default(),
-//             colors: ThemeColors::light(),
-//             status: StatusColors::light(),
-//             player: PlayerColors::light(),
-//             syntax: Arc::new(SyntaxTheme::light()),
-//         },
-//     }
-// }
+fn zed_pro_daylight() -> Theme {
+    Theme {
+        id: "zed_pro_daylight".to_string(),
+        name: "Zed Pro Daylight".into(),
+        appearance: Appearance::Light,
+        styles: ThemeStyles {
+            system: SystemColors::default(),
+            colors: ThemeColors::light(),
+            status: StatusColors::light(),
+            player: PlayerColors::light(),
+            syntax: Arc::new(SyntaxTheme::light()),
+        },
+    }
+}
 
-// pub(crate) fn zed_pro_moonlight() -> Theme {
-//     Theme {
-//         id: "zed_pro_moonlight".to_string(),
-//         name: "Zed Pro Moonlight".into(),
-//         appearance: Appearance::Dark,
-//         styles: ThemeStyles {
-//             system: SystemColors::default(),
-//             colors: ThemeColors::dark(),
-//             status: StatusColors::dark(),
-//             player: PlayerColors::dark(),
-//             syntax: Arc::new(SyntaxTheme::dark()),
-//         },
-//     }
-// }
+pub(crate) fn zed_pro_moonlight() -> Theme {
+    Theme {
+        id: "zed_pro_moonlight".to_string(),
+        name: "Zed Pro Moonlight".into(),
+        appearance: Appearance::Dark,
+        styles: ThemeStyles {
+            system: SystemColors::default(),
+            colors: ThemeColors::dark(),
+            status: StatusColors::dark(),
+            player: PlayerColors::dark(),
+            syntax: Arc::new(SyntaxTheme::dark()),
+        },
+    }
+}
 
-// pub fn zed_pro_family() -> ThemeFamily {
-//     ThemeFamily {
-//         id: "zed_pro".to_string(),
-//         name: "Zed Pro".into(),
-//         author: "Zed Team".into(),
-//         themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
-//         scales: default_color_scales(),
-//     }
-// }
+pub fn zed_pro_family() -> ThemeFamily {
+    ThemeFamily {
+        id: "zed_pro".to_string(),
+        name: "Zed Pro".into(),
+        author: "Zed Team".into(),
+        themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
+        scales: default_color_scales(),
+    }
+}
 
 impl Default for ThemeFamily {
     fn default() -> Self {

crates/theme2/src/registry.rs 🔗

@@ -6,8 +6,8 @@ use gpui::{HighlightStyle, SharedString};
 use refineable::Refineable;
 
 use crate::{
-    one_themes::one_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors,
-    Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
+    one_themes::one_family, zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme,
+    SystemColors, Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
 };
 
 pub struct ThemeRegistry {
@@ -117,7 +117,7 @@ impl Default for ThemeRegistry {
             themes: HashMap::default(),
         };
 
-        this.insert_theme_families([one_family()]);
+        this.insert_theme_families([zed_pro_family(), one_family()]);
 
         this
     }

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

@@ -22,8 +22,8 @@ impl SyntaxTheme {
             highlights: vec![
                 ("attribute".into(), cyan().light().step_11().into()),
                 ("boolean".into(), tomato().light().step_11().into()),
-                ("comment".into(), neutral().light().step_11().into()),
-                ("comment.doc".into(), iris().light().step_12().into()),
+                ("comment".into(), neutral().light().step_10().into()),
+                ("comment.doc".into(), iris().light().step_11().into()),
                 ("constant".into(), red().light().step_9().into()),
                 ("constructor".into(), red().light().step_9().into()),
                 ("embedded".into(), red().light().step_9().into()),
@@ -32,11 +32,11 @@ impl SyntaxTheme {
                 ("enum".into(), red().light().step_9().into()),
                 ("function".into(), red().light().step_9().into()),
                 ("hint".into(), red().light().step_9().into()),
-                ("keyword".into(), orange().light().step_11().into()),
+                ("keyword".into(), orange().light().step_9().into()),
                 ("label".into(), red().light().step_9().into()),
                 ("link_text".into(), red().light().step_9().into()),
                 ("link_uri".into(), red().light().step_9().into()),
-                ("number".into(), red().light().step_9().into()),
+                ("number".into(), purple().light().step_10().into()),
                 ("operator".into(), red().light().step_9().into()),
                 ("predictive".into(), red().light().step_9().into()),
                 ("preproc".into(), red().light().step_9().into()),
@@ -49,16 +49,16 @@ impl SyntaxTheme {
                 ),
                 (
                     "punctuation.delimiter".into(),
-                    neutral().light().step_11().into(),
+                    neutral().light().step_10().into(),
                 ),
                 (
                     "punctuation.list_marker".into(),
                     blue().light().step_11().into(),
                 ),
                 ("punctuation.special".into(), red().light().step_9().into()),
-                ("string".into(), jade().light().step_11().into()),
+                ("string".into(), jade().light().step_9().into()),
                 ("string.escape".into(), red().light().step_9().into()),
-                ("string.regex".into(), tomato().light().step_11().into()),
+                ("string.regex".into(), tomato().light().step_9().into()),
                 ("string.special".into(), red().light().step_9().into()),
                 (
                     "string.special.symbol".into(),
@@ -67,7 +67,7 @@ impl SyntaxTheme {
                 ("tag".into(), red().light().step_9().into()),
                 ("text.literal".into(), red().light().step_9().into()),
                 ("title".into(), red().light().step_9().into()),
-                ("type".into(), red().light().step_9().into()),
+                ("type".into(), cyan().light().step_9().into()),
                 ("variable".into(), red().light().step_9().into()),
                 ("variable.special".into(), red().light().step_9().into()),
                 ("variant".into(), red().light().step_9().into()),

crates/theme_selector2/src/theme_selector.rs 🔗

@@ -2,14 +2,14 @@ use feature_flags::FeatureFlagAppExt;
 use fs::Fs;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, SharedString, View,
-    ViewContext, VisualContext, WeakView,
+    actions, AppContext, DismissEvent, Div, EventEmitter, FocusableView, Render, SharedString,
+    View, ViewContext, VisualContext, WeakView,
 };
 use picker::{Picker, PickerDelegate};
 use settings::{update_settings_file, SettingsStore};
 use std::sync::Arc;
 use theme::{Theme, ThemeRegistry, ThemeSettings};
-use ui::{prelude::*, ListItem};
+use ui::{prelude::*, v_stack, ListItem};
 use util::ResultExt;
 use workspace::{ui::HighlightedLabel, Workspace};
 
@@ -65,10 +65,10 @@ impl FocusableView for ThemeSelector {
 }
 
 impl Render for ThemeSelector {
-    type Element = View<Picker<ThemeSelectorDelegate>>;
+    type Element = Div;
 
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
-        self.picker.clone()
+        v_stack().min_w_96().child(self.picker.clone())
     }
 }
 

crates/ui2/src/components.rs 🔗

@@ -5,6 +5,7 @@ mod context_menu;
 mod disclosure;
 mod divider;
 mod icon;
+mod indicator;
 mod keybinding;
 mod label;
 mod list;
@@ -24,6 +25,7 @@ pub use context_menu::*;
 pub use disclosure::*;
 pub use divider::*;
 pub use icon::*;
+pub use indicator::*;
 pub use keybinding::*;
 pub use label::*;
 pub use list::*;

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

@@ -1,15 +1,26 @@
-use gpui::{rems, svg, IntoElement, Svg};
+use gpui::{rems, svg, IntoElement, Rems, Svg};
 use strum::EnumIter;
 
 use crate::prelude::*;
 
 #[derive(Default, PartialEq, Copy, Clone)]
 pub enum IconSize {
+    XSmall,
     Small,
     #[default]
     Medium,
 }
 
+impl IconSize {
+    pub fn rems(self) -> Rems {
+        match self {
+            IconSize::XSmall => rems(12. / 16.),
+            IconSize::Small => rems(14. / 16.),
+            IconSize::Medium => rems(16. / 16.),
+        }
+    }
+}
+
 #[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
 pub enum Icon {
     Ai,
@@ -170,13 +181,8 @@ impl RenderOnce for IconElement {
     type Rendered = Svg;
 
     fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        let svg_size = match self.size {
-            IconSize::Small => rems(14. / 16.),
-            IconSize::Medium => rems(16. / 16.),
-        };
-
         svg()
-            .size(svg_size)
+            .size(self.size.rems())
             .flex_none()
             .path(self.path)
             .text_color(self.color.color(cx))

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

@@ -0,0 +1,60 @@
+use gpui::{Div, Position};
+
+use crate::prelude::*;
+
+#[derive(Default)]
+pub enum IndicatorStyle {
+    #[default]
+    Dot,
+    Bar,
+}
+
+#[derive(IntoElement)]
+pub struct Indicator {
+    position: Position,
+    style: IndicatorStyle,
+    color: Color,
+}
+
+impl Indicator {
+    pub fn dot() -> Self {
+        Self {
+            position: Position::Relative,
+            style: IndicatorStyle::Dot,
+            color: Color::Default,
+        }
+    }
+
+    pub fn bar() -> Self {
+        Self {
+            position: Position::Relative,
+            style: IndicatorStyle::Dot,
+            color: Color::Default,
+        }
+    }
+
+    pub fn color(mut self, color: Color) -> Self {
+        self.color = color;
+        self
+    }
+
+    pub fn absolute(mut self) -> Self {
+        self.position = Position::Absolute;
+        self
+    }
+}
+
+impl RenderOnce for Indicator {
+    type Rendered = Div;
+
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+        div()
+            .flex_none()
+            .map(|this| match self.style {
+                IndicatorStyle::Dot => this.w_1p5().h_1p5().rounded_full(),
+                IndicatorStyle::Bar => this.w_full().h_1p5().rounded_t_md(),
+            })
+            .when(self.position == Position::Absolute, |this| this.absolute())
+            .bg(self.color.color(cx))
+    }
+}

crates/ui2/src/prelude.rs 🔗

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

crates/welcome2/src/welcome.rs 🔗

@@ -259,6 +259,8 @@ impl FocusableView for WelcomePage {
 }
 
 impl Item for WelcomePage {
+    type Event = ItemEvent;
+
     fn tab_content(&self, _: Option<usize>, _: &WindowContext) -> AnyElement {
         "Welcome to Zed!".into_any()
     }
@@ -278,4 +280,8 @@ impl Item for WelcomePage {
             _settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
         }))
     }
+
+    fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
+        f(*event)
+    }
 }

crates/workspace2/src/item.rs 🔗

@@ -78,7 +78,7 @@ impl Settings for ItemSettings {
     }
 }
 
-#[derive(Eq, PartialEq, Hash, Debug)]
+#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
 pub enum ItemEvent {
     CloseItem,
     UpdateTab,
@@ -92,7 +92,9 @@ pub struct BreadcrumbText {
     pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
 }
 
-pub trait Item: FocusableView + EventEmitter<ItemEvent> {
+pub trait Item: FocusableView + EventEmitter<Self::Event> {
+    type Event;
+
     fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
     fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
     fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
@@ -155,6 +157,8 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
         unimplemented!("reload() must be implemented if can_save() returns true")
     }
 
+    fn to_item_events(event: &Self::Event, f: impl FnMut(ItemEvent));
+
     fn act_as_type<'a>(
         &'a self,
         type_id: TypeId,
@@ -206,12 +210,12 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
 }
 
 pub trait ItemHandle: 'static + Send {
-    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
     fn subscribe_to_item_events(
         &self,
         cx: &mut WindowContext,
-        handler: Box<dyn Fn(&ItemEvent, &mut WindowContext) + Send>,
+        handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
     ) -> gpui::Subscription;
+    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
     fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
     fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
     fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement;
@@ -285,20 +289,20 @@ impl dyn ItemHandle {
 }
 
 impl<T: Item> ItemHandle for View<T> {
-    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
-        self.focus_handle(cx)
-    }
-
     fn subscribe_to_item_events(
         &self,
         cx: &mut WindowContext,
-        handler: Box<dyn Fn(&ItemEvent, &mut WindowContext) + Send>,
+        handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
     ) -> gpui::Subscription {
         cx.subscribe(self, move |_, event, cx| {
-            handler(event, cx);
+            T::to_item_events(event, |item_event| handler(item_event, cx));
         })
     }
 
+    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
+        self.focus_handle(cx)
+    }
+
     fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
         self.read(cx).tab_tooltip_text(cx)
     }
@@ -461,7 +465,7 @@ impl<T: Item> ItemHandle for View<T> {
                         }
                     }
 
-                    match event {
+                    T::to_item_events(event, |event| match event {
                         ItemEvent::CloseItem => {
                             pane.update(cx, |pane, cx| {
                                 pane.close_item_by_id(item.item_id(), crate::SaveIntent::Close, cx)
@@ -489,7 +493,7 @@ impl<T: Item> ItemHandle for View<T> {
                         }
 
                         _ => {}
-                    }
+                    });
                 }));
 
             cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
@@ -655,12 +659,7 @@ pub enum FollowEvent {
     Unfollow,
 }
 
-pub trait FollowableEvents {
-    fn to_follow_event(&self) -> Option<FollowEvent>;
-}
-
 pub trait FollowableItem: Item {
-    type FollowableEvent: FollowableEvents;
     fn remote_id(&self) -> Option<ViewId>;
     fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
     fn from_state_proto(
@@ -670,9 +669,10 @@ pub trait FollowableItem: Item {
         state: &mut Option<proto::view::Variant>,
         cx: &mut WindowContext,
     ) -> Option<Task<Result<View<Self>>>>;
+    fn to_follow_event(event: &Self::Event) -> Option<FollowEvent>;
     fn add_event_to_update_proto(
         &self,
-        event: &Self::FollowableEvent,
+        event: &Self::Event,
         update: &mut Option<proto::update_view::Variant>,
         cx: &WindowContext,
     ) -> bool;
@@ -683,7 +683,6 @@ pub trait FollowableItem: Item {
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>>;
     fn is_project_item(&self, cx: &WindowContext) -> bool;
-
     fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
 }
 
@@ -739,10 +738,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
     }
 
     fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent> {
-        event
-            .downcast_ref()
-            .map(T::FollowableEvent::to_follow_event)
-            .flatten()
+        T::to_follow_event(event.downcast_ref()?)
     }
 
     fn apply_update_proto(
@@ -929,6 +925,12 @@ pub mod test {
     }
 
     impl Item for TestItem {
+        type Event = ItemEvent;
+
+        fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
+            f(*event)
+        }
+
         fn tab_description(&self, detail: usize, _: &AppContext) -> Option<SharedString> {
             self.tab_descriptions.as_ref().and_then(|descriptions| {
                 let description = *descriptions.get(detail).or_else(|| descriptions.last())?;

crates/workspace2/src/pane.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{
-    item::{Item, ItemHandle, ItemSettings, WeakItemHandle},
+    item::{ClosePosition, Item, ItemHandle, ItemSettings, WeakItemHandle},
     toolbar::Toolbar,
     workspace_settings::{AutosaveSetting, WorkspaceSettings},
     NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
@@ -27,7 +27,8 @@ use std::{
 };
 
 use ui::{
-    h_stack, prelude::*, right_click_menu, Color, Icon, IconButton, IconElement, Label, Tooltip,
+    h_stack, prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize,
+    Indicator, Label, Tooltip,
 };
 use ui::{v_stack, ContextMenu};
 use util::truncate_and_remove_front;
@@ -1418,22 +1419,7 @@ impl Pane {
         cx: &mut ViewContext<'_, Pane>,
     ) -> impl IntoElement {
         let label = item.tab_content(Some(detail), cx);
-        let close_icon = || {
-            let id = item.item_id();
-
-            div()
-                .id(ix)
-                .invisible()
-                .group_hover("", |style| style.visible())
-                .child(
-                    IconButton::new("close_tab", Icon::Close).on_click(cx.listener(
-                        move |pane, _, cx| {
-                            pane.close_item_by_id(id, SaveIntent::Close, cx)
-                                .detach_and_log_err(cx);
-                        },
-                    )),
-                )
-        };
+        let close_side = &ItemSettings::get_global(cx).close_position;
 
         let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
             false => (
@@ -1450,102 +1436,129 @@ impl Pane {
             ),
         };
 
-        let close_right = ItemSettings::get_global(cx).close_position.right();
         let is_active = ix == self.active_item_index;
 
+        let indicator = {
+            let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
+                (true, _) => Some(Color::Warning),
+                (_, true) => Some(Color::Accent),
+                (false, false) => None,
+            };
+
+            h_stack()
+                .w_3()
+                .h_3()
+                .justify_center()
+                .absolute()
+                .map(|this| match close_side {
+                    ClosePosition::Left => this.right_1(),
+                    ClosePosition::Right => this.left_1(),
+                })
+                .when_some(indicator_color, |this, indicator_color| {
+                    this.child(Indicator::dot().color(indicator_color))
+                })
+        };
+
+        let close_button = {
+            let id = item.item_id();
+
+            h_stack()
+                .invisible()
+                .w_3()
+                .h_3()
+                .justify_center()
+                .absolute()
+                .map(|this| match close_side {
+                    ClosePosition::Left => this.left_1(),
+                    ClosePosition::Right => this.right_1(),
+                })
+                .group_hover("", |style| style.visible())
+                .child(
+                    // TODO: Fix button size
+                    IconButton::new("close tab", Icon::Close)
+                        .icon_color(Color::Muted)
+                        .size(ButtonSize::None)
+                        .icon_size(IconSize::XSmall)
+                        .on_click(cx.listener(move |pane, _, cx| {
+                            pane.close_item_by_id(id, SaveIntent::Close, cx)
+                                .detach_and_log_err(cx);
+                        })),
+                )
+        };
+
         let tab = div()
-            .group("")
-            .id(ix)
-            .cursor_pointer()
-            .when_some(item.tab_tooltip_text(cx), |div, text| {
-                div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
-            })
-            .on_click(cx.listener(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx)))
-            // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
-            // .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
-            // .on_drop(|_view, state: View<DraggedTab>, cx| {
-            //     eprintln!("{:?}", state.read(cx));
-            // })
-            .flex()
-            .items_center()
-            .justify_center()
-            // todo!("Nate - I need to do some work to balance all the items in the tab once things stablize")
-            .map(|this| {
-                if close_right {
-                    this.pl_3().pr_1()
-                } else {
-                    this.pr_1().pr_3()
-                }
-            })
-            .py_1()
-            .bg(tab_bg)
             .border_color(cx.theme().colors().border)
-            .text_color(if is_active {
-                cx.theme().colors().text
-            } else {
-                cx.theme().colors().text_muted
-            })
+            .bg(tab_bg)
+            // 30px @ 16px/rem
+            .h(rems(1.875))
             .map(|this| {
+                let is_first_item = ix == 0;
                 let is_last_item = ix == self.items.len() - 1;
                 match ix.cmp(&self.active_item_index) {
-                    cmp::Ordering::Less => this.border_l().mr_px(),
+                    cmp::Ordering::Less => {
+                        if is_first_item {
+                            this.pl_px().pr_px().border_b()
+                        } else {
+                            this.border_l().pr_px().border_b()
+                        }
+                    }
                     cmp::Ordering::Greater => {
                         if is_last_item {
-                            this.mr_px().ml_px()
+                            this.pr_px().pl_px().border_b()
                         } else {
-                            this.border_r().ml_px()
+                            this.border_r().pl_px().border_b()
+                        }
+                    }
+                    cmp::Ordering::Equal => {
+                        if is_first_item {
+                            this.pl_px().border_r().pb_px()
+                        } else {
+                            this.border_l().border_r().pb_px()
                         }
                     }
-                    cmp::Ordering::Equal => this.border_l().border_r(),
                 }
             })
-            // .hover(|h| h.bg(tab_hover_bg))
-            // .active(|a| a.bg(tab_active_bg))
             .child(
-                div()
-                    .flex()
-                    .items_center()
+                h_stack()
+                    .group("")
+                    .id(ix)
+                    .relative()
+                    .h_full()
+                    .cursor_pointer()
+                    .when_some(item.tab_tooltip_text(cx), |div, text| {
+                        div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
+                    })
+                    .on_click(
+                        cx.listener(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx)),
+                    )
+                    // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
+                    // .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
+                    // .on_drop(|_view, state: View<DraggedTab>, cx| {
+                    //     eprintln!("{:?}", state.read(cx));
+                    // })
+                    .px_5()
+                    // .hover(|h| h.bg(tab_hover_bg))
+                    // .active(|a| a.bg(tab_active_bg))
                     .gap_1()
                     .text_color(text_color)
-                    .children(
-                        item.has_conflict(cx)
-                            .then(|| {
-                                div().border().border_color(gpui::red()).child(
-                                    IconElement::new(Icon::ExclamationTriangle)
-                                        .size(ui::IconSize::Small)
-                                        .color(Color::Warning),
-                                )
-                            })
-                            .or(item.is_dirty(cx).then(|| {
-                                div().border().border_color(gpui::red()).child(
-                                    IconElement::new(Icon::ExclamationTriangle)
-                                        .size(ui::IconSize::Small)
-                                        .color(Color::Info),
-                                )
-                            })),
-                    )
-                    .children((!close_right).then(|| close_icon()))
-                    .child(label)
-                    .children(close_right.then(|| close_icon())),
+                    .child(indicator)
+                    .child(close_button)
+                    .child(label),
             );
 
         right_click_menu(ix).trigger(tab).menu(|cx| {
             ContextMenu::build(cx, |menu, cx| {
-                menu.action(
-                    "Close Active Item",
-                    CloseActiveItem { save_intent: None }.boxed_clone(),
-                )
-                .action("Close Inactive Items", CloseInactiveItems.boxed_clone())
-                .action("Close Clean Items", CloseCleanItems.boxed_clone())
-                .action("Close Items To The Left", CloseItemsToTheLeft.boxed_clone())
-                .action(
-                    "Close Items To The Right",
-                    CloseItemsToTheRight.boxed_clone(),
-                )
-                .action(
-                    "Close All Items",
-                    CloseAllItems { save_intent: None }.boxed_clone(),
-                )
+                menu.action("Close", CloseActiveItem { save_intent: None }.boxed_clone())
+                    .action("Close Others", CloseInactiveItems.boxed_clone())
+                    .separator()
+                    .action("Close Left", CloseItemsToTheLeft.boxed_clone())
+                    .action("Close Right", CloseItemsToTheRight.boxed_clone())
+                    .separator()
+                    .action("Close Clean", CloseCleanItems.boxed_clone())
+                    .action(
+                        "Close All",
+                        CloseAllItems { save_intent: None }.boxed_clone(),
+                    )
             })
         })
     }
@@ -1565,116 +1578,118 @@ impl Pane {
             // Left Side
             .child(
                 h_stack()
-                    .px_2()
                     .flex()
                     .flex_none()
                     .gap_1()
+                    .px_1()
+                    .border_b()
+                    .border_r()
+                    .border_color(cx.theme().colors().border)
                     // Nav Buttons
                     .child(
-                        div().border().border_color(gpui::red()).child(
-                            IconButton::new("navigate_backward", Icon::ArrowLeft)
-                                .on_click({
-                                    let view = cx.view().clone();
-                                    move |_, cx| view.update(cx, Self::navigate_backward)
-                                })
-                                .disabled(!self.can_navigate_backward()),
-                        ),
+                        IconButton::new("navigate_backward", Icon::ArrowLeft)
+                            .icon_size(IconSize::Small)
+                            .on_click({
+                                let view = cx.view().clone();
+                                move |_, cx| view.update(cx, Self::navigate_backward)
+                            })
+                            .disabled(!self.can_navigate_backward()),
                     )
                     .child(
-                        div().border().border_color(gpui::red()).child(
-                            IconButton::new("navigate_forward", Icon::ArrowRight)
-                                .on_click({
-                                    let view = cx.view().clone();
-                                    move |_, cx| view.update(cx, Self::navigate_backward)
-                                })
-                                .disabled(!self.can_navigate_forward()),
-                        ),
+                        IconButton::new("navigate_forward", Icon::ArrowRight)
+                            .icon_size(IconSize::Small)
+                            .on_click({
+                                let view = cx.view().clone();
+                                move |_, cx| view.update(cx, Self::navigate_backward)
+                            })
+                            .disabled(!self.can_navigate_forward()),
                     ),
             )
             .child(
-                div().flex_1().h_full().child(
-                    div().id("tabs").flex().overflow_x_scroll().children(
-                        self.items
-                            .iter()
-                            .enumerate()
-                            .zip(self.tab_details(cx))
-                            .map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
+                div()
+                    .relative()
+                    .flex_1()
+                    .h_full()
+                    .overflow_hidden_x()
+                    .child(
+                        div()
+                            .absolute()
+                            .top_0()
+                            .left_0()
+                            .z_index(1)
+                            .size_full()
+                            .border_b()
+                            .border_color(cx.theme().colors().border),
+                    )
+                    .child(
+                        h_stack().id("tabs").z_index(2).children(
+                            self.items
+                                .iter()
+                                .enumerate()
+                                .zip(self.tab_details(cx))
+                                .map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
+                        ),
                     ),
-                ),
             )
             // Right Side
             .child(
-                div()
-                    .px_1()
+                h_stack()
                     .flex()
                     .flex_none()
-                    .gap_2()
-                    // Nav Buttons
+                    .gap_1()
+                    .px_1()
+                    .border_b()
+                    .border_l()
+                    .border_color(cx.theme().colors().border)
                     .child(
                         div()
                             .flex()
                             .items_center()
                             .gap_px()
                             .child(
-                                div()
-                                    .bg(gpui::blue())
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(IconButton::new("plus", Icon::Plus).on_click(
-                                        cx.listener(|this, _, cx| {
-                                            let menu = ContextMenu::build(cx, |menu, cx| {
-                                                menu.action("New File", NewFile.boxed_clone())
-                                                    .action(
-                                                        "New Terminal",
-                                                        NewCenterTerminal.boxed_clone(),
-                                                    )
-                                                    .action("New Search", NewSearch.boxed_clone())
-                                            });
-                                            cx.subscribe(
-                                                &menu,
-                                                |this, _, event: &DismissEvent, cx| {
-                                                    this.focus(cx);
-                                                    this.new_item_menu = None;
-                                                },
-                                            )
-                                            .detach();
-                                            this.new_item_menu = Some(menu);
-                                        }),
-                                    ))
-                                    .when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
-                                        el.child(Self::render_menu_overlay(new_item_menu))
-                                    }),
+                                IconButton::new("plus", Icon::Plus)
+                                    .icon_size(IconSize::Small)
+                                    .on_click(cx.listener(|this, _, cx| {
+                                        let menu = ContextMenu::build(cx, |menu, cx| {
+                                            menu.action("New File", NewFile.boxed_clone())
+                                                .action(
+                                                    "New Terminal",
+                                                    NewCenterTerminal.boxed_clone(),
+                                                )
+                                                .action("New Search", NewSearch.boxed_clone())
+                                        });
+                                        cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| {
+                                            this.focus(cx);
+                                            this.new_item_menu = None;
+                                        })
+                                        .detach();
+                                        this.new_item_menu = Some(menu);
+                                    })),
                             )
+                            .when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
+                                el.child(Self::render_menu_overlay(new_item_menu))
+                            })
                             .child(
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(IconButton::new("split", Icon::Split).on_click(
-                                        cx.listener(|this, _, cx| {
-                                            let menu = ContextMenu::build(cx, |menu, cx| {
-                                                menu.action("Split Right", SplitRight.boxed_clone())
-                                                    .action("Split Left", SplitLeft.boxed_clone())
-                                                    .action("Split Up", SplitUp.boxed_clone())
-                                                    .action("Split Down", SplitDown.boxed_clone())
-                                            });
-                                            cx.subscribe(
-                                                &menu,
-                                                |this, _, event: &DismissEvent, cx| {
-                                                    this.focus(cx);
-                                                    this.split_item_menu = None;
-                                                },
-                                            )
-                                            .detach();
-                                            this.split_item_menu = Some(menu);
-                                        }),
-                                    ))
-                                    .when_some(
-                                        self.split_item_menu.as_ref(),
-                                        |el, split_item_menu| {
-                                            el.child(Self::render_menu_overlay(split_item_menu))
-                                        },
-                                    ),
-                            ),
+                                IconButton::new("split", Icon::Split)
+                                    .icon_size(IconSize::Small)
+                                    .on_click(cx.listener(|this, _, cx| {
+                                        let menu = ContextMenu::build(cx, |menu, cx| {
+                                            menu.action("Split Right", SplitRight.boxed_clone())
+                                                .action("Split Left", SplitLeft.boxed_clone())
+                                                .action("Split Up", SplitUp.boxed_clone())
+                                                .action("Split Down", SplitDown.boxed_clone())
+                                        });
+                                        cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| {
+                                            this.focus(cx);
+                                            this.split_item_menu = None;
+                                        })
+                                        .detach();
+                                        this.split_item_menu = Some(menu);
+                                    })),
+                            )
+                            .when_some(self.split_item_menu.as_ref(), |el, split_item_menu| {
+                                el.child(Self::render_menu_overlay(split_item_menu))
+                            }),
                     ),
             )
     }
@@ -2092,6 +2107,8 @@ impl Render for Pane {
         v_stack()
             .key_context("Pane")
             .track_focus(&self.focus_handle)
+            .size_full()
+            .overflow_hidden()
             .on_focus_in({
                 let this = this.clone();
                 move |event, cx| {
@@ -2159,7 +2176,6 @@ impl Render for Pane {
                 pane.close_all_items(action, cx)
                     .map(|task| task.detach_and_log_err(cx));
             }))
-            .size_full()
             .on_action(
                 cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
                     pane.close_active_item(action, cx)

crates/workspace2/src/shared_screen.rs 🔗

@@ -59,7 +59,6 @@ impl SharedScreen {
 }
 
 impl EventEmitter<Event> for SharedScreen {}
-impl EventEmitter<ItemEvent> for SharedScreen {}
 
 impl FocusableView for SharedScreen {
     fn focus_handle(&self, _: &AppContext) -> FocusHandle {
@@ -79,9 +78,12 @@ impl Render for SharedScreen {
 }
 
 impl Item for SharedScreen {
+    type Event = Event;
+
     fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
         Some(format!("{}'s screen", self.user.github_login).into())
     }
+
     fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
         if let Some(nav_history) = self.nav_history.as_mut() {
             nav_history.push::<()>(None, cx);
@@ -111,4 +113,10 @@ impl Item for SharedScreen {
         let track = self.track.upgrade()?;
         Some(cx.build_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx)))
     }
+
+    fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
+        match event {
+            Event::Close => f(ItemEvent::CloseItem),
+        }
+    }
 }

crates/workspace2/src/toolbar.rs 🔗

@@ -1,10 +1,10 @@
 use crate::ItemHandle;
 use gpui::{
-    div, AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View,
+    AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View,
     ViewContext, WindowContext,
 };
 use ui::prelude::*;
-use ui::{h_stack, v_stack, Icon, IconButton};
+use ui::{h_stack, v_stack};
 
 pub enum ToolbarItemEvent {
     ChangeLocation(ToolbarItemLocation),
@@ -87,25 +87,7 @@ impl Render for Toolbar {
             .child(
                 h_stack()
                     .justify_between()
-                    // Toolbar left side
-                    .children(self.items.iter().map(|(child, _)| child.to_any()))
-                    // Toolbar right side
-                    .child(
-                        h_stack()
-                            .p_1()
-                            .child(
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(IconButton::new("buffer-search", Icon::MagnifyingGlass)),
-                            )
-                            .child(
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(IconButton::new("inline-assist", Icon::MagicWand)),
-                            ),
-                    ),
+                    .children(self.items.iter().map(|(child, _)| child.to_any())),
             )
     }
 }

crates/workspace2/src/workspace2.rs 🔗

@@ -212,27 +212,31 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
     init_settings(cx);
     notifications::init(cx);
 
-    //     cx.add_global_action({
-    //         let app_state = Arc::downgrade(&app_state);
-    //         move |_: &Open, cx: &mut AppContext| {
-    //             let mut paths = cx.prompt_for_paths(PathPromptOptions {
-    //                 files: true,
-    //                 directories: true,
-    //                 multiple: true,
-    //             });
+    cx.on_action(Workspace::close_global);
+    cx.on_action(restart);
+
+    cx.on_action({
+        let app_state = Arc::downgrade(&app_state);
+        move |_: &Open, cx: &mut AppContext| {
+            let mut paths = cx.prompt_for_paths(PathPromptOptions {
+                files: true,
+                directories: true,
+                multiple: true,
+            });
 
-    //             if let Some(app_state) = app_state.upgrade() {
-    //                 cx.spawn(move |mut cx| async move {
-    //                     if let Some(paths) = paths.recv().await.flatten() {
-    //                         cx.update(|cx| {
-    //                             open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
-    //                         });
-    //                     }
-    //                 })
-    //                 .detach();
-    //             }
-    //         }
-    //     });
+            if let Some(app_state) = app_state.upgrade() {
+                cx.spawn(move |mut cx| async move {
+                    if let Some(paths) = paths.await.log_err().flatten() {
+                        cx.update(|cx| {
+                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
+                        })
+                        .ok();
+                    }
+                })
+                .detach();
+            }
+        }
+    });
 }
 
 type ProjectItemBuilders =
@@ -1076,7 +1080,6 @@ impl Workspace {
         }
     }
 
-    // todo!(Non-window-actions)
     pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
         cx.windows().iter().find(|window| {
             window
@@ -1094,21 +1097,18 @@ impl Workspace {
         });
     }
 
-    pub fn close(
-        &mut self,
-        _: &CloseWindow,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
+    pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
         let window = cx.window_handle();
         let prepare = self.prepare_to_close(false, cx);
-        Some(cx.spawn(|_, mut cx| async move {
+        cx.spawn(|_, mut cx| async move {
             if prepare.await? {
                 window.update(&mut cx, |_, cx| {
                     cx.remove_window();
                 })?;
             }
-            Ok(())
-        }))
+            anyhow::Ok(())
+        })
+        .detach_and_log_err(cx)
     }
 
     pub fn prepare_to_close(
@@ -2325,42 +2325,44 @@ impl Workspace {
         }))
     }
 
-    //     pub fn follow_next_collaborator(
-    //         &mut self,
-    //         _: &FollowNextCollaborator,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Option<Task<Result<()>>> {
-    //         let collaborators = self.project.read(cx).collaborators();
-    //         let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
-    //             let mut collaborators = collaborators.keys().copied();
-    //             for peer_id in collaborators.by_ref() {
-    //                 if peer_id == leader_id {
-    //                     break;
-    //                 }
-    //             }
-    //             collaborators.next()
-    //         } else if let Some(last_leader_id) =
-    //             self.last_leaders_by_pane.get(&self.active_pane.downgrade())
-    //         {
-    //             if collaborators.contains_key(last_leader_id) {
-    //                 Some(*last_leader_id)
-    //             } else {
-    //                 None
+    // pub fn follow_next_collaborator(
+    //     &mut self,
+    //     _: &FollowNextCollaborator,
+    //     cx: &mut ViewContext<Self>,
+    // ) {
+    //     let collaborators = self.project.read(cx).collaborators();
+    //     let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
+    //         let mut collaborators = collaborators.keys().copied();
+    //         for peer_id in collaborators.by_ref() {
+    //             if peer_id == leader_id {
+    //                 break;
     //             }
+    //         }
+    //         collaborators.next()
+    //     } else if let Some(last_leader_id) =
+    //         self.last_leaders_by_pane.get(&self.active_pane.downgrade())
+    //     {
+    //         if collaborators.contains_key(last_leader_id) {
+    //             Some(*last_leader_id)
     //         } else {
     //             None
-    //         };
-
-    //         let pane = self.active_pane.clone();
-    //         let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
-    //         else {
-    //             return None;
-    //         };
-    //         if Some(leader_id) == self.unfollow(&pane, cx) {
-    //             return None;
     //         }
-    //         self.follow(leader_id, cx)
+    //     } else {
+    //         None
+    //     };
+
+    //     let pane = self.active_pane.clone();
+    //     let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
+    //     else {
+    //         return;
+    //     };
+    //     if Some(leader_id) == self.unfollow(&pane, cx) {
+    //         return;
     //     }
+    //     if let Some(task) = self.follow(leader_id, cx) {
+    //         task.detach();
+    //     }
+    // }
 
     pub fn follow(
         &mut self,
@@ -2409,6 +2411,18 @@ impl Workspace {
         self.start_following(leader_id, cx)
     }
 
+    //     // if you're already following, find the right pane and focus it.
+    //     for (pane, state) in &self.follower_states {
+    //         if leader_id == state.leader_id {
+    //             cx.focus(pane);
+    //             return None;
+    //         }
+    //     }
+
+    //     // Otherwise, follow.
+    //     self.start_following(leader_id, cx)
+    // }
+
     pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
         let state = self.follower_states.remove(pane)?;
         let leader_id = state.leader_id;
@@ -2625,8 +2639,6 @@ impl Workspace {
         update: proto::UpdateFollowers,
         cx: &mut AsyncWindowContext,
     ) -> Result<()> {
-        dbg!("process_leader_update", &update);
-
         match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
             proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
                 this.update(cx, |this, _| {
@@ -3221,13 +3233,8 @@ impl Workspace {
 
     fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
         self.add_workspace_actions_listeners(div, cx)
-            //     cx.add_async_action(Workspace::open);
-            //     cx.add_async_action(Workspace::follow_next_collaborator);
-            //     cx.add_async_action(Workspace::close);
             .on_action(cx.listener(Self::close_inactive_items_and_panes))
             .on_action(cx.listener(Self::close_all_items_and_panes))
-            //     cx.add_global_action(Workspace::close_global);
-            //     cx.add_global_action(restart);
             .on_action(cx.listener(Self::save_all))
             .on_action(cx.listener(Self::add_folder_to_project))
             .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
@@ -3276,6 +3283,9 @@ impl Workspace {
                     workspace.close_all_docks(cx);
                 }),
             )
+            .on_action(cx.listener(Workspace::open))
+            .on_action(cx.listener(Workspace::close_window))
+
         //     cx.add_action(Workspace::activate_pane_at_index);
         //     cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
         //         workspace.reopen_closed_item(cx).detach();
@@ -3880,8 +3890,6 @@ impl WorkspaceStore {
         let leader_id = envelope.original_sender_id()?;
         let update = envelope.payload;
 
-        dbg!("handle_upate_followers");
-
         this.update(&mut cx, |this, cx| {
             for workspace in &this.workspaces {
                 workspace.update(cx, |workspace, cx| {

crates/zed2/Cargo.toml 🔗

@@ -55,7 +55,7 @@ outline = { package = "outline2", path = "../outline2" }
 project = { package = "project2", path = "../project2" }
 project_panel = { package = "project_panel2", path = "../project_panel2" }
 # project_symbols = { path = "../project_symbols" }
-# quick_action_bar = { path = "../quick_action_bar" }
+quick_action_bar = { package = "quick_action_bar2", path = "../quick_action_bar2" }
 # recent_projects = { path = "../recent_projects" }
 rope = { package = "rope2", path = "../rope2"}
 rpc = { package = "rpc2", path = "../rpc2" }

crates/zed2/src/zed2.rs 🔗

@@ -22,6 +22,7 @@ pub use open_listener::*;
 use anyhow::{anyhow, Context as _};
 use futures::{channel::mpsc, StreamExt};
 use project_panel::ProjectPanel;
+use quick_action_bar::QuickActionBar;
 use settings::{initial_local_settings_content, load_default_keymap, KeymapFile, Settings};
 use std::{borrow::Cow, ops::Deref, sync::Arc};
 use terminal_view::terminal_panel::TerminalPanel;
@@ -103,11 +104,10 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
                             toolbar.add_item(breadcrumbs, cx);
                             let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
                             toolbar.add_item(buffer_search_bar.clone(), cx);
-                            // todo!()
-                            //     let quick_action_bar = cx.add_view(|_| {
-                            //         QuickActionBar::new(buffer_search_bar, workspace)
-                            //     });
-                            //     toolbar.add_item(quick_action_bar, cx);
+
+                            let quick_action_bar = cx
+                                .build_view(|_| QuickActionBar::new(buffer_search_bar, workspace));
+                            toolbar.add_item(quick_action_bar, cx);
                             let diagnostic_editor_controls =
                                 cx.build_view(|_| diagnostics::ToolbarControls::new());
                             //     toolbar.add_item(diagnostic_editor_controls, cx);
@@ -171,9 +171,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
         cx.on_window_should_close(move |cx| {
             handle
                 .update(cx, |workspace, cx| {
-                    if let Some(task) = workspace.close(&Default::default(), cx) {
-                        task.detach_and_log_err(cx);
-                    }
+                    workspace.close_window(&Default::default(), cx);
                     false
                 })
                 .unwrap_or(true)