1use editor::Editor;
2use gpui::{
3 Element, EventEmitter, FocusableView, IntoElement, ParentElement, Render, StyledText,
4 Subscription, ViewContext,
5};
6use itertools::Itertools;
7use std::cmp;
8use theme::ActiveTheme;
9use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
10use workspace::{
11 item::{BreadcrumbText, ItemEvent, ItemHandle},
12 ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
13};
14
15pub struct Breadcrumbs {
16 pane_focused: bool,
17 active_item: Option<Box<dyn ItemHandle>>,
18 subscription: Option<Subscription>,
19}
20
21impl Default for Breadcrumbs {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27impl Breadcrumbs {
28 pub fn new() -> Self {
29 Self {
30 pane_focused: false,
31 active_item: Default::default(),
32 subscription: Default::default(),
33 }
34 }
35}
36
37impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
38
39impl Render for Breadcrumbs {
40 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
41 const MAX_SEGMENTS: usize = 12;
42 let element = h_flex().text_ui(cx);
43 let Some(active_item) = self.active_item.as_ref() else {
44 return element;
45 };
46 let Some(mut segments) = active_item.breadcrumbs(cx.theme(), cx) else {
47 return element;
48 };
49
50 let prefix_end_ix = cmp::min(segments.len(), MAX_SEGMENTS / 2);
51 let suffix_start_ix = cmp::max(
52 prefix_end_ix,
53 segments.len().saturating_sub(MAX_SEGMENTS / 2),
54 );
55 if suffix_start_ix > prefix_end_ix {
56 segments.splice(
57 prefix_end_ix..suffix_start_ix,
58 Some(BreadcrumbText {
59 text: "⋯".into(),
60 highlights: None,
61 font: None,
62 }),
63 );
64 }
65
66 let highlighted_segments = segments.into_iter().map(|segment| {
67 let mut text_style = cx.text_style();
68 if let Some(font) = segment.font {
69 text_style.font_family = font.family;
70 text_style.font_features = font.features;
71 text_style.font_style = font.style;
72 text_style.font_weight = font.weight;
73 }
74 text_style.color = Color::Muted.color(cx);
75
76 StyledText::new(segment.text.replace('\n', ""))
77 .with_highlights(&text_style, segment.highlights.unwrap_or_default())
78 .into_any()
79 });
80 let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
81 Label::new("›").color(Color::Placeholder).into_any_element()
82 });
83
84 let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
85 match active_item
86 .downcast::<Editor>()
87 .map(|editor| editor.downgrade())
88 {
89 Some(editor) => element.child(
90 ButtonLike::new("toggle outline view")
91 .child(breadcrumbs_stack)
92 .style(ButtonStyle::Transparent)
93 .on_click({
94 let editor = editor.clone();
95 move |_, cx| {
96 if let Some(editor) = editor.upgrade() {
97 outline::toggle(editor, &editor::actions::ToggleOutline, cx)
98 }
99 }
100 })
101 .tooltip(move |cx| {
102 if let Some(editor) = editor.upgrade() {
103 let focus_handle = editor.read(cx).focus_handle(cx);
104 Tooltip::for_action_in(
105 "Show symbol outline",
106 &editor::actions::ToggleOutline,
107 &focus_handle,
108 cx,
109 )
110 } else {
111 Tooltip::for_action(
112 "Show symbol outline",
113 &editor::actions::ToggleOutline,
114 cx,
115 )
116 }
117 }),
118 ),
119 None => element
120 // Match the height of the `ButtonLike` in the other arm.
121 .h(rems_from_px(22.))
122 .child(breadcrumbs_stack),
123 }
124 }
125}
126
127impl ToolbarItemView for Breadcrumbs {
128 fn set_active_pane_item(
129 &mut self,
130 active_pane_item: Option<&dyn ItemHandle>,
131 cx: &mut ViewContext<Self>,
132 ) -> ToolbarItemLocation {
133 cx.notify();
134 self.active_item = None;
135
136 let Some(item) = active_pane_item else {
137 return ToolbarItemLocation::Hidden;
138 };
139
140 let this = cx.view().downgrade();
141 self.subscription = Some(item.subscribe_to_item_events(
142 cx,
143 Box::new(move |event, cx| {
144 if let ItemEvent::UpdateBreadcrumbs = event {
145 this.update(cx, |this, cx| {
146 cx.notify();
147 if let Some(active_item) = this.active_item.as_ref() {
148 cx.emit(ToolbarItemEvent::ChangeLocation(
149 active_item.breadcrumb_location(cx),
150 ))
151 }
152 })
153 .ok();
154 }
155 }),
156 ));
157 self.active_item = Some(item.boxed_clone());
158 item.breadcrumb_location(cx)
159 }
160
161 fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
162 self.pane_focused = pane_focused;
163 }
164}