@@ -1,16 +1,10 @@
-use editor::Editor;
-use gpui::{
- Context, Element, EventEmitter, Focusable, FontWeight, IntoElement, ParentElement, Render,
- StyledText, Subscription, Window,
-};
-use itertools::Itertools;
-use settings::Settings;
-use std::cmp;
+use editor::render_breadcrumb_text;
+use gpui::{Context, EventEmitter, IntoElement, Render, Subscription, Window};
use theme::ActiveTheme;
-use ui::{ButtonLike, ButtonStyle, Label, Tooltip, prelude::*};
+use ui::prelude::*;
use workspace::{
- TabBarSettings, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
- item::{BreadcrumbText, ItemEvent, ItemHandle},
+ ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
+ item::{ItemEvent, ItemHandle},
};
pub struct Breadcrumbs {
@@ -37,129 +31,30 @@ impl Breadcrumbs {
impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
-// Potential idea:
-// - Rename this to "BreadcrumbToolbar" or something
-// - Create a wrapping "Breadcrumb" struct for Vec<BreadcrumbText>
-// - Implement render for _that_ breadcrumb struct.
-// - Call that from here to eliminate much of the logic.
-// - This will change the Item interface, so do it only after you're happy with the features thus far
impl Render for Breadcrumbs {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- const MAX_SEGMENTS: usize = 12;
-
let element = h_flex()
.id("breadcrumb-container")
.flex_grow()
.h_8()
.overflow_x_scroll()
.text_ui(cx);
-
let Some(active_item) = self.active_item.as_ref() else {
- return element;
+ return element.into_any_element();
};
- // Begin - logic we should copy/move
- let Some(mut segments) = active_item.breadcrumbs(cx.theme(), cx) else {
- return element;
+ let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
+ return element.into_any_element();
};
-
- let prefix_end_ix = cmp::min(segments.len(), MAX_SEGMENTS / 2);
- let suffix_start_ix = cmp::max(
- prefix_end_ix,
- segments.len().saturating_sub(MAX_SEGMENTS / 2),
- );
-
- if suffix_start_ix > prefix_end_ix {
- segments.splice(
- prefix_end_ix..suffix_start_ix,
- Some(BreadcrumbText {
- text: "⋯".into(),
- highlights: None,
- font: None,
- }),
- );
- }
-
- let highlighted_segments = segments.into_iter().enumerate().map(|(index, segment)| {
- let mut text_style = window.text_style();
- if let Some(ref font) = segment.font {
- text_style.font_family = font.family.clone();
- text_style.font_features = font.features.clone();
- text_style.font_style = font.style;
- text_style.font_weight = font.weight;
- }
- text_style.color = Color::Muted.color(cx);
-
- if index == 0
- && !TabBarSettings::get_global(cx).show
- && active_item.is_dirty(cx)
- && let Some(styled_element) = apply_dirty_filename_style(&segment, &text_style, cx)
- {
- return styled_element;
- }
-
- StyledText::new(segment.text.replace('\n', "⏎"))
- .with_default_highlights(&text_style, segment.highlights.unwrap_or_default())
- .into_any()
- });
- let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
- Label::new("›").color(Color::Placeholder).into_any_element()
- });
-
- let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
-
let prefix_element = active_item.breadcrumb_prefix(window, cx);
-
- let breadcrumbs = if let Some(prefix) = prefix_element {
- h_flex().gap_1p5().child(prefix).child(breadcrumbs_stack)
- } else {
- breadcrumbs_stack
- };
-
- match active_item
- .downcast::<Editor>()
- .map(|editor| editor.downgrade())
- {
- Some(editor) => element.child(
- ButtonLike::new("toggle outline view")
- .child(breadcrumbs)
- .style(ButtonStyle::Transparent)
- .on_click({
- let editor = editor.clone();
- move |_, window, cx| {
- if let Some((editor, callback)) = editor
- .upgrade()
- .zip(zed_actions::outline::TOGGLE_OUTLINE.get())
- {
- callback(editor.to_any_view(), window, cx);
- }
- }
- })
- .tooltip(move |_window, cx| {
- if let Some(editor) = editor.upgrade() {
- let focus_handle = editor.read(cx).focus_handle(cx);
- Tooltip::for_action_in(
- "Show Symbol Outline",
- &zed_actions::outline::ToggleOutline,
- &focus_handle,
- cx,
- )
- } else {
- Tooltip::for_action(
- "Show Symbol Outline",
- &zed_actions::outline::ToggleOutline,
- cx,
- )
- }
- }),
- ),
- None => element
- // Match the height and padding of the `ButtonLike` in the other arm.
- .h(rems_from_px(22.))
- .pl_1()
- .child(breadcrumbs),
- }
- // End
+ render_breadcrumb_text(
+ segments,
+ prefix_element,
+ Box::new(active_item.as_ref()),
+ window,
+ cx,
+ )
+ .into_any_element()
}
}
@@ -208,46 +103,3 @@ impl ToolbarItemView for Breadcrumbs {
self.pane_focused = pane_focused;
}
}
-
-fn apply_dirty_filename_style(
- segment: &BreadcrumbText,
- text_style: &gpui::TextStyle,
- cx: &mut Context<Breadcrumbs>,
-) -> Option<gpui::AnyElement> {
- let text = segment.text.replace('\n', "⏎");
-
- let filename_position = std::path::Path::new(&segment.text)
- .file_name()
- .and_then(|f| {
- let filename_str = f.to_string_lossy();
- segment.text.rfind(filename_str.as_ref())
- })?;
-
- let bold_weight = FontWeight::BOLD;
- let default_color = Color::Default.color(cx);
-
- if filename_position == 0 {
- let mut filename_style = text_style.clone();
- filename_style.font_weight = bold_weight;
- filename_style.color = default_color;
-
- return Some(
- StyledText::new(text)
- .with_default_highlights(&filename_style, [])
- .into_any(),
- );
- }
-
- let highlight_style = gpui::HighlightStyle {
- font_weight: Some(bold_weight),
- color: Some(default_color),
- ..Default::default()
- };
-
- let highlight = vec![(filename_position..text.len(), highlight_style)];
- Some(
- StyledText::new(text)
- .with_default_highlights(text_style, highlight)
- .into_any(),
- )
-}
@@ -41,7 +41,7 @@ use git::{Oid, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatu
use gpui::{
Action, Along, AnyElement, App, AppContext, AvailableSpace, Axis as ScrollbarAxis, BorderStyle,
Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle,
- DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId,
+ DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, FontWeight,
GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero,
KeybindingKeystroke, Length, Modifiers, ModifiersChangedEvent, MouseButton, MouseClickEvent,
MouseDownEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent, PaintQuad, ParentElement,
@@ -3880,7 +3880,7 @@ impl EditorElement {
let editor = self.editor.read(cx);
let multi_buffer = editor.buffer.read(cx);
let is_read_only = self.editor.read(cx).read_only(cx);
- let weak_editor = self.editor.downgrade();
+ let editor_handle: Box<&dyn ItemHandle> = Box::new(&self.editor);
let breadcrumbs = if is_selected {
editor.breadcrumbs_inner(cx.theme(), cx)
@@ -4100,10 +4100,10 @@ impl EditorElement {
)
})
.when_some(breadcrumbs, |then, breadcrumbs| {
- then.child(self.render_breadcrumb_text(
+ then.child(render_breadcrumb_text(
breadcrumbs,
- None, // TODO gotta figure this out somehow
- weak_editor,
+ None,
+ editor_handle,
window,
cx,
))
@@ -4261,105 +4261,6 @@ impl EditorElement {
})
}
- // TODO This has too much code in common with Breadcrumb::render. We should find a way to DRY it.
- fn render_breadcrumb_text(
- &self,
- mut segments: Vec<BreadcrumbText>,
- prefix: Option<gpui::AnyElement>,
- editor: WeakEntity<Editor>,
- window: &mut Window,
- cx: &App,
- ) -> impl IntoElement {
- const MAX_SEGMENTS: usize = 12;
-
- let element = h_flex().id("breadcrumb-container").flex_grow().text_ui(cx);
-
- let prefix_end_ix = cmp::min(segments.len(), MAX_SEGMENTS / 2);
- let suffix_start_ix = cmp::max(
- prefix_end_ix,
- segments.len().saturating_sub(MAX_SEGMENTS / 2),
- );
-
- if suffix_start_ix > prefix_end_ix {
- segments.splice(
- prefix_end_ix..suffix_start_ix,
- Some(BreadcrumbText {
- text: "⋯".into(),
- highlights: None,
- font: None,
- }),
- );
- }
-
- let highlighted_segments = segments.into_iter().enumerate().map(|(_index, segment)| {
- let mut text_style = window.text_style();
- if let Some(ref font) = segment.font {
- text_style.font_family = font.family.clone();
- text_style.font_features = font.features.clone();
- text_style.font_style = font.style;
- text_style.font_weight = font.weight;
- }
- text_style.color = Color::Muted.color(cx);
-
- // TODO this shouldn't apply here, but will in the formal breadcrumb (e.g. singleton buffer). Need to resolve the difference.
- // if index == 0
- // && !TabBarSettings::get_global(cx).show
- // && active_item.is_dirty(cx)
- // && let Some(styled_element) = apply_dirty_filename_style(&segment, &text_style, cx)
- // {
- // return styled_element;
- // }
-
- StyledText::new(segment.text.replace('\n', "⏎"))
- .with_default_highlights(&text_style, segment.highlights.unwrap_or_default())
- .into_any()
- });
- let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
- Label::new("›").color(Color::Placeholder).into_any_element()
- });
-
- let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
-
- let breadcrumbs = if let Some(prefix) = prefix {
- h_flex().gap_1p5().child(prefix).child(breadcrumbs_stack)
- } else {
- breadcrumbs_stack
- };
- element.child(
- ButtonLike::new("toggle outline view")
- .child(breadcrumbs)
- .style(ButtonStyle::Transparent)
- .on_click({
- let editor = editor.clone();
- move |_, window, cx| {
- if let Some((editor, callback)) = editor
- .upgrade()
- .zip(zed_actions::outline::TOGGLE_OUTLINE.get())
- {
- callback(editor.to_any_view(), window, cx);
- }
- }
- })
- .tooltip(move |_window, cx| {
- if let Some(editor) = editor.upgrade() {
- let focus_handle = editor.read(cx).focus_handle(cx);
- Tooltip::for_action_in(
- "Show Symbol Outline",
- &zed_actions::outline::ToggleOutline,
- &focus_handle,
- cx,
- )
- } else {
- Tooltip::for_action(
- "Show Symbol Outline",
- &zed_actions::outline::ToggleOutline,
- cx,
- )
- }
- }),
- )
- }
-
fn render_blocks(
&self,
rows: Range<DisplayRow>,
@@ -7997,6 +7898,156 @@ impl EditorElement {
}
}
+pub fn render_breadcrumb_text(
+ mut segments: Vec<BreadcrumbText>,
+ prefix: Option<gpui::AnyElement>,
+ active_item: Box<&dyn ItemHandle>,
+ window: &mut Window,
+ cx: &App,
+) -> impl IntoElement {
+ const MAX_SEGMENTS: usize = 12;
+
+ let element = h_flex().id("breadcrumb-container").flex_grow().text_ui(cx);
+
+ let prefix_end_ix = cmp::min(segments.len(), MAX_SEGMENTS / 2);
+ let suffix_start_ix = cmp::max(
+ prefix_end_ix,
+ segments.len().saturating_sub(MAX_SEGMENTS / 2),
+ );
+
+ if suffix_start_ix > prefix_end_ix {
+ segments.splice(
+ prefix_end_ix..suffix_start_ix,
+ Some(BreadcrumbText {
+ text: "⋯".into(),
+ highlights: None,
+ font: None,
+ }),
+ );
+ }
+
+ let highlighted_segments = segments.into_iter().enumerate().map(|(index, segment)| {
+ let mut text_style = window.text_style();
+ if let Some(ref font) = segment.font {
+ text_style.font_family = font.family.clone();
+ text_style.font_features = font.features.clone();
+ text_style.font_style = font.style;
+ text_style.font_weight = font.weight;
+ }
+ text_style.color = Color::Muted.color(cx);
+
+ if index == 0
+ && !workspace::TabBarSettings::get_global(cx).show
+ && active_item.is_dirty(cx)
+ && let Some(styled_element) = apply_dirty_filename_style(&segment, &text_style, cx)
+ {
+ return styled_element;
+ }
+
+ StyledText::new(segment.text.replace('\n', "⏎"))
+ .with_default_highlights(&text_style, segment.highlights.unwrap_or_default())
+ .into_any()
+ });
+ let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
+ Label::new("›").color(Color::Placeholder).into_any_element()
+ });
+
+ let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
+
+ let breadcrumbs = if let Some(prefix) = prefix {
+ h_flex().gap_1p5().child(prefix).child(breadcrumbs_stack)
+ } else {
+ breadcrumbs_stack
+ };
+
+ match active_item
+ .downcast::<Editor>()
+ .map(|editor| editor.downgrade())
+ {
+ Some(editor) => element.child(
+ ButtonLike::new("toggle outline view")
+ .child(breadcrumbs)
+ .style(ButtonStyle::Transparent)
+ .on_click({
+ let editor = editor.clone();
+ move |_, window, cx| {
+ if let Some((editor, callback)) = editor
+ .upgrade()
+ .zip(zed_actions::outline::TOGGLE_OUTLINE.get())
+ {
+ callback(editor.to_any_view(), window, cx);
+ }
+ }
+ })
+ .tooltip(move |_window, cx| {
+ if let Some(editor) = editor.upgrade() {
+ let focus_handle = editor.read(cx).focus_handle(cx);
+ Tooltip::for_action_in(
+ "Show Symbol Outline",
+ &zed_actions::outline::ToggleOutline,
+ &focus_handle,
+ cx,
+ )
+ } else {
+ Tooltip::for_action(
+ "Show Symbol Outline",
+ &zed_actions::outline::ToggleOutline,
+ cx,
+ )
+ }
+ }),
+ ),
+ None => element
+ // Match the height and padding of the `ButtonLike` in the other arm.
+ .h(rems_from_px(22.))
+ .pl_1()
+ .child(breadcrumbs),
+ }
+}
+
+fn apply_dirty_filename_style(
+ segment: &BreadcrumbText,
+ text_style: &gpui::TextStyle,
+ cx: &App,
+) -> Option<gpui::AnyElement> {
+ let text = segment.text.replace('\n', "⏎");
+
+ let filename_position = std::path::Path::new(&segment.text)
+ .file_name()
+ .and_then(|f| {
+ let filename_str = f.to_string_lossy();
+ segment.text.rfind(filename_str.as_ref())
+ })?;
+
+ let bold_weight = FontWeight::BOLD;
+ let default_color = Color::Default.color(cx);
+
+ if filename_position == 0 {
+ let mut filename_style = text_style.clone();
+ filename_style.font_weight = bold_weight;
+ filename_style.color = default_color;
+
+ return Some(
+ StyledText::new(text)
+ .with_default_highlights(&filename_style, [])
+ .into_any(),
+ );
+ }
+
+ let highlight_style = gpui::HighlightStyle {
+ font_weight: Some(bold_weight),
+ color: Some(default_color),
+ ..Default::default()
+ };
+
+ let highlight = vec![(filename_position..text.len(), highlight_style)];
+ Some(
+ StyledText::new(text)
+ .with_default_highlights(text_style, highlight)
+ .into_any(),
+ )
+}
+
fn file_status_label_color(file_status: Option<FileStatus>) -> Color {
file_status.map_or(Color::Default, |status| {
if status.is_conflicted() {