Refactor buffer headers to collapse on click (#42021)

Matt Miller created

Release Notes:
Updated how clicking on multi-buffer headers works to provide better
control and prevent unexpected navigation:

Clicking the header now collapses/expands the file section instead of
opening the file.
Opening files can be done by clicking the filename or the "Open file"
button on the right side of the header.
Existing shortcuts continue to work: use the left chevron to collapse or
your keyboard shortcut to jump to the file

**Demo:**

https://github.com/user-attachments/assets/dca9ccc5-bd98-416c-97af-43b4e4b2f903

Change summary

crates/editor/src/element.rs | 144 ++++++++++++++++++++++++-------------
1 file changed, 92 insertions(+), 52 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -4004,41 +4004,51 @@ impl EditorElement {
                             .size_full()
                             .justify_between()
                             .overflow_hidden()
-                            .child(
-                                h_flex()
-                                    .gap_2()
-                                    .map(|path_header| {
-                                        let filename = filename
-                                            .map(SharedString::from)
-                                            .unwrap_or_else(|| "untitled".into());
-
-                                        path_header
-                                            .when(ItemSettings::get_global(cx).file_icons, |el| {
-                                                let path = path::Path::new(filename.as_str());
-                                                let icon = FileIcons::get_icon(path, cx)
-                                                    .unwrap_or_default();
-                                                let icon =
-                                                    Icon::from_path(icon).color(Color::Muted);
-                                                el.child(icon)
-                                            })
-                                            .child(Label::new(filename).single_line().when_some(
-                                                file_status,
-                                                |el, status| {
-                                                    el.color(if status.is_conflicted() {
-                                                        Color::Conflict
-                                                    } else if status.is_modified() {
-                                                        Color::Modified
-                                                    } else if status.is_deleted() {
-                                                        Color::Disabled
-                                                    } else {
-                                                        Color::Created
-                                                    })
-                                                    .when(status.is_deleted(), |el| {
-                                                        el.strikethrough()
-                                                    })
-                                                },
-                                            ))
+                            .child(h_flex().gap_2().map(|path_header| {
+                                let filename = filename
+                                    .map(SharedString::from)
+                                    .unwrap_or_else(|| "untitled".into());
+
+                                path_header
+                                    .when(ItemSettings::get_global(cx).file_icons, |el| {
+                                        let path = path::Path::new(filename.as_str());
+                                        let icon =
+                                            FileIcons::get_icon(path, cx).unwrap_or_default();
+                                        let icon = Icon::from_path(icon).color(Color::Muted);
+                                        el.child(icon)
                                     })
+                                    .child(
+                                        ButtonLike::new("filename-button")
+                                            .style(ButtonStyle::Subtle)
+                                            .child(
+                                                div()
+                                                    .child(
+                                                        Label::new(filename)
+                                                            .single_line()
+                                                            .color(file_status_label_color(
+                                                                file_status,
+                                                            ))
+                                                            .when(
+                                                                file_status.is_some_and(|s| {
+                                                                    s.is_deleted()
+                                                                }),
+                                                                |label| label.strikethrough(),
+                                                            ),
+                                                    )
+                                                    .group_hover("", |div| div.underline()),
+                                            )
+                                            .on_click(window.listener_for(&self.editor, {
+                                                let jump_data = jump_data.clone();
+                                                move |editor, e: &ClickEvent, window, cx| {
+                                                    editor.open_excerpts_common(
+                                                        Some(jump_data.clone()),
+                                                        e.modifiers().secondary(),
+                                                        window,
+                                                        cx,
+                                                    );
+                                                }
+                                            })),
+                                    )
                                     .when_some(parent_path, |then, path| {
                                         then.child(div().child(path).text_color(
                                             if file_status.is_some_and(FileStatus::is_deleted) {
@@ -4047,33 +4057,47 @@ impl EditorElement {
                                                 colors.text_muted
                                             },
                                         ))
-                                    }),
-                            )
+                                    })
+                            }))
                             .when(
                                 can_open_excerpts && is_selected && relative_path.is_some(),
                                 |el| {
                                     el.child(
-                                        h_flex()
-                                            .id("jump-to-file-button")
-                                            .gap_2p5()
-                                            .child(Label::new("Jump To File"))
-                                            .child(KeyBinding::for_action_in(
-                                                &OpenExcerpts,
-                                                &focus_handle,
-                                                cx,
-                                            )),
+                                        ButtonLike::new("open-file-button")
+                                            .style(ButtonStyle::OutlinedGhost)
+                                            .child(
+                                                h_flex()
+                                                    .gap_2p5()
+                                                    .child(Label::new("Open file"))
+                                                    .child(KeyBinding::for_action_in(
+                                                        &OpenExcerpts,
+                                                        &focus_handle,
+                                                        cx,
+                                                    )),
+                                            )
+                                            .on_click(window.listener_for(&self.editor, {
+                                                let jump_data = jump_data.clone();
+                                                move |editor, e: &ClickEvent, window, cx| {
+                                                    editor.open_excerpts_common(
+                                                        Some(jump_data.clone()),
+                                                        e.modifiers().secondary(),
+                                                        window,
+                                                        cx,
+                                                    );
+                                                }
+                                            })),
                                     )
                                 },
                             )
                             .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
                             .on_click(window.listener_for(&self.editor, {
-                                move |editor, e: &ClickEvent, window, cx| {
-                                    editor.open_excerpts_common(
-                                        Some(jump_data.clone()),
-                                        e.modifiers().secondary(),
-                                        window,
-                                        cx,
-                                    );
+                                let buffer_id = for_excerpt.buffer_id;
+                                move |editor, _e: &ClickEvent, _window, cx| {
+                                    if is_folded {
+                                        editor.unfold_buffer(buffer_id, cx);
+                                    } else {
+                                        editor.fold_buffer(buffer_id, cx);
+                                    }
                                 }
                             })),
                     ),
@@ -7512,6 +7536,22 @@ impl EditorElement {
     }
 }
 
+fn file_status_label_color(file_status: Option<FileStatus>) -> Color {
+    file_status.map_or(Color::Default, |status| {
+        if status.is_conflicted() {
+            Color::Conflict
+        } else if status.is_modified() {
+            Color::Modified
+        } else if status.is_deleted() {
+            Color::Disabled
+        } else if status.is_created() {
+            Color::Created
+        } else {
+            Color::Default
+        }
+    })
+}
+
 fn header_jump_data(
     snapshot: &EditorSnapshot,
     block_row_start: DisplayRow,