From 2f463370cc387b38a9e14a6c9da2aea6317838e6 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Wed, 5 Nov 2025 14:59:50 -0600 Subject: [PATCH] Refactor buffer headers to collapse on click (#42021) 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 --- crates/editor/src/element.rs | 144 ++++++++++++++++++++++------------- 1 file changed, 92 insertions(+), 52 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a5433745d05a6e8536c1b9faff7cf34a360cad47..0053afe1637309688e57625f9b7511888a0410aa 100644 --- a/crates/editor/src/element.rs +++ b/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) -> 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,