Add file icons to multibuffer view (#36836)

<img width="1988" height="1420" alt="multi-buffer-icons-git-diff"
src="https://github.com/user-attachments/assets/48f9722f-ca09-4aa7-ad7a-0b7e85f440d9"
/>

Unfortunately, `cargo format` decided to reformat everything. Probably,
because of hitting the right margin, no idea. The essence of this change
is the following:

```rust
.map(|path_header| {
    let filename = filename
        .map(SharedString::from)
        .unwrap_or_else(|| "untitled".into());
    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);

    let label = 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())
        },
    );

    path_header.child(icon).child(label)
})
``` 

Release Notes:

- Added file icons to multi buffer view
This commit is contained in:
Aleksei Gusev 2025-08-24 19:57:12 +03:00 committed by GitHub
parent a79aef7bdd
commit 11545c669e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -74,7 +74,7 @@ use std::{
fmt::{self, Write}, fmt::{self, Write},
iter, mem, iter, mem,
ops::{Deref, Range}, ops::{Deref, Range},
path::Path, path::{self, Path},
rc::Rc, rc::Rc,
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
@ -90,8 +90,8 @@ use unicode_segmentation::UnicodeSegmentation;
use util::post_inc; use util::post_inc;
use util::{RangeExt, ResultExt, debug_panic}; use util::{RangeExt, ResultExt, debug_panic};
use workspace::{ use workspace::{
CollaboratorId, OpenInTerminal, OpenTerminal, RevealInProjectPanel, Workspace, item::Item, CollaboratorId, ItemSettings, OpenInTerminal, OpenTerminal, RevealInProjectPanel, Workspace,
notifications::NotifyTaskExt, item::Item, notifications::NotifyTaskExt,
}; };
/// Determines what kinds of highlights should be applied to a lines background. /// Determines what kinds of highlights should be applied to a lines background.
@ -3603,176 +3603,187 @@ impl EditorElement {
let focus_handle = editor.focus_handle(cx); let focus_handle = editor.focus_handle(cx);
let colors = cx.theme().colors(); let colors = cx.theme().colors();
let header = let header = div()
div() .p_1()
.p_1() .w_full()
.w_full() .h(FILE_HEADER_HEIGHT as f32 * window.line_height())
.h(FILE_HEADER_HEIGHT as f32 * window.line_height()) .child(
.child( h_flex()
h_flex() .size_full()
.size_full() .gap_2()
.gap_2() .flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667))) .pl_0p5()
.pl_0p5() .pr_5()
.pr_5() .rounded_sm()
.rounded_sm() .when(is_sticky, |el| el.shadow_md())
.when(is_sticky, |el| el.shadow_md()) .border_1()
.border_1() .map(|div| {
.map(|div| { let border_color = if is_selected
let border_color = if is_selected && is_folded
&& is_folded && focus_handle.contains_focused(window, cx)
&& focus_handle.contains_focused(window, cx) {
{ colors.border_focused
colors.border_focused } else {
} else { colors.border
colors.border };
}; div.border_color(border_color)
div.border_color(border_color) })
}) .bg(colors.editor_subheader_background)
.bg(colors.editor_subheader_background) .hover(|style| style.bg(colors.element_hover))
.hover(|style| style.bg(colors.element_hover)) .map(|header| {
.map(|header| { let editor = self.editor.clone();
let editor = self.editor.clone(); let buffer_id = for_excerpt.buffer_id;
let buffer_id = for_excerpt.buffer_id; let toggle_chevron_icon =
let toggle_chevron_icon = FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path); header.child(
header.child( div()
div() .hover(|style| style.bg(colors.element_selected))
.hover(|style| style.bg(colors.element_selected)) .rounded_xs()
.rounded_xs() .child(
.child( ButtonLike::new("toggle-buffer-fold")
ButtonLike::new("toggle-buffer-fold") .style(ui::ButtonStyle::Transparent)
.style(ui::ButtonStyle::Transparent) .height(px(28.).into())
.height(px(28.).into()) .width(px(28.))
.width(px(28.)) .children(toggle_chevron_icon)
.children(toggle_chevron_icon) .tooltip({
.tooltip({ let focus_handle = focus_handle.clone();
let focus_handle = focus_handle.clone(); move |window, cx| {
move |window, cx| { Tooltip::with_meta_in(
Tooltip::with_meta_in( "Toggle Excerpt Fold",
"Toggle Excerpt Fold", Some(&ToggleFold),
Some(&ToggleFold), "Alt+click to toggle all",
"Alt+click to toggle all", &focus_handle,
&focus_handle, window,
cx,
)
}
})
.on_click(move |event, window, cx| {
if event.modifiers().alt {
// Alt+click toggles all buffers
editor.update(cx, |editor, cx| {
editor.toggle_fold_all(
&ToggleFoldAll,
window, window,
cx, cx,
) );
} });
}) } else {
.on_click(move |event, window, cx| { // Regular click toggles single buffer
if event.modifiers().alt { if is_folded {
// Alt+click toggles all buffers
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.toggle_fold_all( editor.unfold_buffer(buffer_id, cx);
&ToggleFoldAll,
window,
cx,
);
}); });
} else { } else {
// Regular click toggles single buffer editor.update(cx, |editor, cx| {
if is_folded { editor.fold_buffer(buffer_id, cx);
editor.update(cx, |editor, cx| { });
editor.unfold_buffer(buffer_id, cx);
});
} else {
editor.update(cx, |editor, cx| {
editor.fold_buffer(buffer_id, cx);
});
}
} }
}), }
), }),
) ),
})
.children(
editor
.addons
.values()
.filter_map(|addon| {
addon.render_buffer_header_controls(for_excerpt, window, cx)
})
.take(1),
) )
.child( })
h_flex() .children(
.size(Pixels(12.0)) editor
.justify_center() .addons
.children(indicator), .values()
) .filter_map(|addon| {
.child( addon.render_buffer_header_controls(for_excerpt, window, cx)
h_flex() })
.cursor_pointer() .take(1),
.id("path header block") )
.size_full() .child(
.justify_between() h_flex()
.overflow_hidden() .size(Pixels(12.0))
.child( .justify_center()
h_flex() .children(indicator),
.gap_2() )
.child( .child(
Label::new( h_flex()
filename .cursor_pointer()
.map(SharedString::from) .id("path header block")
.unwrap_or_else(|| "untitled".into()), .size_full()
) .justify_between()
.single_line() .overflow_hidden()
.when_some(file_status, |el, status| { .child(
el.color(if status.is_conflicted() { h_flex()
Color::Conflict .gap_2()
} else if status.is_modified() { .map(|path_header| {
Color::Modified let filename = filename
} else if status.is_deleted() { .map(SharedString::from)
Color::Disabled .unwrap_or_else(|| "untitled".into());
} else {
Color::Created path_header
}) .when(ItemSettings::get_global(cx).file_icons, |el| {
.when(status.is_deleted(), |el| el.strikethrough()) let path = path::Path::new(filename.as_str());
}), let icon = FileIcons::get_icon(path, cx)
) .unwrap_or_default();
.when_some(parent_path, |then, path| { let icon =
then.child(div().child(path).text_color( Icon::from_path(icon).color(Color::Muted);
if file_status.is_some_and(FileStatus::is_deleted) { el.child(icon)
colors.text_disabled })
} else { .child(Label::new(filename).single_line().when_some(
colors.text_muted 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()
})
}, },
)) ))
}), })
) .when_some(parent_path, |then, path| {
.when( then.child(div().child(path).text_color(
can_open_excerpts && is_selected && relative_path.is_some(), if file_status.is_some_and(FileStatus::is_deleted) {
|el| { colors.text_disabled
el.child( } else {
h_flex() colors.text_muted
.id("jump-to-file-button") },
.gap_2p5() ))
.child(Label::new("Jump To File")) }),
.children( )
KeyBinding::for_action_in( .when(
&OpenExcerpts, can_open_excerpts && is_selected && relative_path.is_some(),
&focus_handle, |el| {
window, el.child(
cx, h_flex()
) .id("jump-to-file-button")
.map(|binding| binding.into_any_element()), .gap_2p5()
), .child(Label::new("Jump To File"))
) .children(
}, KeyBinding::for_action_in(
) &OpenExcerpts,
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) &focus_handle,
.on_click(window.listener_for(&self.editor, { window,
move |editor, e: &ClickEvent, window, cx| { cx,
editor.open_excerpts_common( )
Some(jump_data.clone()), .map(|binding| binding.into_any_element()),
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 file = for_excerpt.buffer.file().cloned(); let file = for_excerpt.buffer.file().cloned();
let editor = self.editor.clone(); let editor = self.editor.clone();