Merge branch 'main' into completion-menu-detail-layout

Marshall Bowers created

Change summary

crates/collab_ui2/src/collab_panel.rs | 141 +++++++++++++++++-----------
crates/search2/src/buffer_search.rs   |  11 ++
crates/workspace2/src/pane.rs         |   2 
3 files changed, 95 insertions(+), 59 deletions(-)

Detailed changes

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -2110,6 +2110,47 @@ impl CollabPanel {
             None
         };
 
+        let button_container = |cx: &mut ViewContext<Self>| {
+            h_stack()
+                .absolute()
+                // We're using a negative coordinate for the right anchor to
+                // counteract the padding of the `ListItem`.
+                //
+                // This prevents a gap from showing up between the background
+                // of this element and the edge of the collab panel.
+                .right(rems(-0.5))
+                // HACK: Without this the channel name clips on top of the icons, but I'm not sure why.
+                .z_index(10)
+                .bg(cx.theme().colors().panel_background)
+                .when(is_selected || is_active, |this| {
+                    this.bg(cx.theme().colors().ghost_element_selected)
+                })
+        };
+
+        let messages_button = |cx: &mut ViewContext<Self>| {
+            IconButton::new("channel_chat", Icon::MessageBubbles)
+                .icon_size(IconSize::Small)
+                .icon_color(if has_messages_notification {
+                    Color::Default
+                } else {
+                    Color::Muted
+                })
+                .on_click(cx.listener(move |this, _, cx| this.join_channel_chat(channel_id, cx)))
+                .tooltip(|cx| Tooltip::text("Open channel chat", cx))
+        };
+
+        let notes_button = |cx: &mut ViewContext<Self>| {
+            IconButton::new("channel_notes", Icon::File)
+                .icon_size(IconSize::Small)
+                .icon_color(if has_notes_notification {
+                    Color::Default
+                } else {
+                    Color::Muted
+                })
+                .on_click(cx.listener(move |this, _, cx| this.open_channel_notes(channel_id, cx)))
+                .tooltip(|cx| Tooltip::text("Open channel notes", cx))
+        };
+
         let width = self.width.unwrap_or(px(240.));
 
         div()
@@ -2172,63 +2213,49 @@ impl CollabPanel {
                             .child(Label::new(channel.name.clone()))
                             .children(face_pile.map(|face_pile| face_pile.render(cx))),
                     )
-                    .end_slot(
-                        h_stack()
-                            .absolute()
-                            // We're using a negative coordinate for the right anchor to
-                            // counteract the padding of the `ListItem`.
-                            //
-                            // This prevents a gap from showing up between the background
-                            // of this element and the edge of the collab panel.
-                            .right(rems(-0.5))
-                            // HACK: Without this the channel name clips on top of the icons, but I'm not sure why.
-                            .z_index(10)
-                            .bg(cx.theme().colors().panel_background)
-                            .when(is_selected || is_active, |this| {
-                                this.bg(cx.theme().colors().ghost_element_selected)
-                            })
-                            .child(
-                                h_stack()
-                                    .px_1()
-                                    // The element hover background has a slight transparency to it, so we
-                                    // need to apply it to the inner element so that it blends with the solid
-                                    // background color of the absolutely-positioned element.
-                                    .group_hover("", |style| {
-                                        style.bg(cx.theme().colors().ghost_element_hover)
-                                    })
-                                    .child(
-                                        IconButton::new("channel_chat", Icon::MessageBubbles)
-                                            .icon_size(IconSize::Small)
-                                            .icon_color(if has_messages_notification {
-                                                Color::Default
-                                            } else {
-                                                Color::Muted
-                                            })
-                                            .when(!has_messages_notification, |this| {
-                                                this.visible_on_hover("")
-                                            })
-                                            .on_click(cx.listener(move |this, _, cx| {
-                                                this.join_channel_chat(channel_id, cx)
-                                            }))
-                                            .tooltip(|cx| Tooltip::text("Open channel chat", cx)),
-                                    )
-                                    .child(
-                                        IconButton::new("channel_notes", Icon::File)
-                                            .icon_size(IconSize::Small)
-                                            .icon_color(if has_notes_notification {
-                                                Color::Default
-                                            } else {
-                                                Color::Muted
-                                            })
-                                            .when(!has_notes_notification, |this| {
-                                                this.visible_on_hover("")
-                                            })
-                                            .on_click(cx.listener(move |this, _, cx| {
-                                                this.open_channel_notes(channel_id, cx)
-                                            }))
-                                            .tooltip(|cx| Tooltip::text("Open channel notes", cx)),
-                                    ),
-                            ),
+                    .end_slot::<Div>(
+                        // If we have a notification for either button, we want to show the corresponding
+                        // button(s) as indicators.
+                        if has_messages_notification || has_notes_notification {
+                            Some(
+                                button_container(cx).child(
+                                    h_stack()
+                                        .px_1()
+                                        .children(
+                                            // We only want to render the messages button if there are unseen messages.
+                                            // This way we don't take up any space that might overlap the channel name
+                                            // when there are no notifications.
+                                            has_messages_notification.then(|| messages_button(cx)),
+                                        )
+                                        .child(
+                                            // We always want the notes button to take up space to prevent layout
+                                            // shift when hovering over the channel.
+                                            // However, if there are is no notes notification we just show an empty slot.
+                                            notes_button(cx)
+                                                .when(!has_notes_notification, |this| {
+                                                    this.visible_on_hover("")
+                                                }),
+                                        ),
+                                ),
+                            )
+                        } else {
+                            None
+                        },
+                    )
+                    .end_hover_slot(
+                        // When we hover the channel entry we want to always show both buttons.
+                        button_container(cx).child(
+                            h_stack()
+                                .px_1()
+                                // The element hover background has a slight transparency to it, so we
+                                // need to apply it to the inner element so that it blends with the solid
+                                // background color of the absolutely-positioned element.
+                                .group_hover("", |style| {
+                                    style.bg(cx.theme().colors().ghost_element_hover)
+                                })
+                                .child(messages_button(cx))
+                                .child(notes_button(cx)),
+                        ),
                     ),
             )
             .tooltip(|cx| Tooltip::text("Join channel", cx))

crates/search2/src/buffer_search.rs 🔗

@@ -7,7 +7,7 @@ use crate::{
     ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
 };
 use collections::HashMap;
-use editor::{Editor, EditorElement, EditorStyle};
+use editor::{Editor, EditorElement, EditorStyle, Tab};
 use futures::channel::oneshot;
 use gpui::{
     actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView,
@@ -190,6 +190,7 @@ impl Render for BufferSearchBar {
             .w_full()
             .gap_2()
             .key_context(key_context)
+            .capture_action(cx.listener(Self::tab))
             .on_action(cx.listener(Self::previous_history_query))
             .on_action(cx.listener(Self::next_history_query))
             .on_action(cx.listener(Self::dismiss))
@@ -932,6 +933,14 @@ impl BufferSearchBar {
         }
     }
 
+    fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
+        if let Some(item) = self.active_searchable_item.as_ref() {
+            let focus_handle = item.focus_handle(cx);
+            cx.focus(&focus_handle);
+            cx.stop_propagation();
+        }
+    }
+
     fn next_history_query(&mut self, _: &NextHistoryQuery, cx: &mut ViewContext<Self>) {
         if let Some(new_query) = self.search_history.next().map(str::to_string) {
             let _ = self.search(&new_query, Some(self.search_options), cx);

crates/workspace2/src/pane.rs 🔗

@@ -1687,7 +1687,7 @@ impl Pane {
                         ),
                 )
             })
-            .when(self.has_focus(cx), |tab_bar| {
+            .when(self.was_focused || self.has_focus(cx), |tab_bar| {
                 tab_bar.end_child({
                     let render_tab_buttons = self.render_tab_bar_buttons.clone();
                     render_tab_buttons(self, cx)