Layout completion single-line documentation to the right of item (#3845)

Marshall Bowers created

Don't love how this turned out, it seems uniform list doesn't tie the
widths of its items together, so there needs to be a set size to fill in
order to put the documentation detail on the right side. That means it
won't be able to shrink for completion lists which don't need the full
width. Zed1 has this same limitation but it would be nice to do it nicer
now

Before
![CleanShot 2024-01-02 at 16 04
04](https://github.com/zed-industries/zed/assets/30666851/6c154465-83cd-41c3-a16a-5f2ff5041bc1)

After
![CleanShot 2024-01-02 at 16 05
29](https://github.com/zed-industries/zed/assets/30666851/4f641040-a06a-4474-a2c7-bd3c89ae049e)

Release Notes:

- N/A

Change summary

crates/editor2/src/editor.rs                | 48 +++++++++++-----------
crates/ui2/src/components/list/list_item.rs |  3 +
2 files changed, 28 insertions(+), 23 deletions(-)

Detailed changes

crates/editor2/src/editor.rs 🔗

@@ -98,7 +98,9 @@ pub use sum_tree::Bias;
 use sum_tree::TreeMap;
 use text::{OffsetUtf16, Rope};
 use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};
-use ui::{h_stack, ButtonSize, ButtonStyle, Icon, IconButton, Popover, Tooltip};
+use ui::{
+    h_stack, ButtonSize, ButtonStyle, Icon, IconButton, ListItem, ListItemSpacing, Popover, Tooltip,
+};
 use ui::{prelude::*, IconSize};
 use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
 use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};
@@ -1201,6 +1203,7 @@ impl CompletionsMenu {
                     .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
             })
         };
+
         let list = uniform_list(
             cx.view().clone(),
             "completions",
@@ -1238,28 +1241,27 @@ impl CompletionsMenu {
                             .with_highlights(&style.text, highlights);
                         let documentation_label =
                             if let Some(Documentation::SingleLine(text)) = documentation {
-                                Some(SharedString::from(text.clone()))
-                                    .filter(|text| !text.trim().is_empty())
+                                if text.trim().is_empty() {
+                                    None
+                                } else {
+                                    Some(
+                                        h_stack().ml_4().child(
+                                            Label::new(text.clone())
+                                                .size(LabelSize::Small)
+                                                .color(Color::Muted),
+                                        ),
+                                    )
+                                }
                             } else {
                                 None
                             };
 
-                        div()
-                            .id(mat.candidate_id)
-                            .min_w(px(220.))
-                            .max_w(px(540.))
-                            .whitespace_nowrap()
-                            .overflow_hidden()
-                            .px_1()
-                            .rounded(px(4.))
-                            .bg(cx.theme().colors().ghost_element_background)
-                            .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
-                            .when(item_ix == selected_item, |div| {
-                                div.bg(cx.theme().colors().ghost_element_selected)
-                            })
-                            .on_mouse_down(
-                                MouseButton::Left,
-                                cx.listener(move |editor, _event, cx| {
+                        div().min_w(px(220.)).max_w(px(540.)).child(
+                            ListItem::new(mat.candidate_id)
+                                .inset(true)
+                                .spacing(ListItemSpacing::Sparse)
+                                .selected(item_ix == selected_item)
+                                .on_click(cx.listener(move |editor, _event, cx| {
                                     cx.stop_propagation();
                                     editor
                                         .confirm_completion(
@@ -1269,10 +1271,10 @@ impl CompletionsMenu {
                                             cx,
                                         )
                                         .map(|task| task.detach_and_log_err(cx));
-                                }),
-                            )
-                            .child(completion_label)
-                            .children(documentation_label)
+                                }))
+                                .child(h_stack().overflow_hidden().child(completion_label))
+                                .end_slot::<Div>(documentation_label),
+                        )
                     })
                     .collect()
             },

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

@@ -224,6 +224,9 @@ impl RenderOnce for ListItem {
                     }))
                     .child(
                         h_stack()
+                            // HACK: We need to set *any* width value here in order for this container to size correctly.
+                            // Without this the `h_stack` will overflow the parent `inner_list_item`.
+                            .w_px()
                             .flex_1()
                             .gap_1()
                             .children(self.start_slot)