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
43 let element = h_flex()
44 .id("breadcrumb-container")
45 .flex_grow()
46 .overflow_x_scroll()
47 .text_ui(cx);
48
49 let Some(active_item) = self.active_item.as_ref() else {
50 return element;
51 };
52
53 let Some(mut segments) = active_item.breadcrumbs(cx.theme(), cx) else {
54 return element;
55 };
56
57 let prefix_end_ix = cmp::min(segments.len(), MAX_SEGMENTS / 2);
58 let suffix_start_ix = cmp::max(
59 prefix_end_ix,
60 segments.len().saturating_sub(MAX_SEGMENTS / 2),
61 );
62
63 if suffix_start_ix > prefix_end_ix {
64 segments.splice(
65 prefix_end_ix..suffix_start_ix,
66 Some(BreadcrumbText {
67 text: "⋯".into(),
68 highlights: None,
69 font: None,
70 }),
71 );
72 }
73
74 let highlighted_segments = segments.into_iter().map(|segment| {
75 let mut text_style = cx.text_style();
76 if let Some(font) = segment.font {
77 text_style.font_family = font.family;
78 text_style.font_features = font.features;
79 text_style.font_style = font.style;
80 text_style.font_weight = font.weight;
81 }
82 text_style.color = Color::Muted.color(cx);
83
84 StyledText::new(segment.text.replace('\n', ""))
85 .with_highlights(&text_style, segment.highlights.unwrap_or_default())
86 .into_any()
87 });
88 let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
89 Label::new("›").color(Color::Placeholder).into_any_element()
90 });
91
92 let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
93
94 match active_item
95 .downcast::<Editor>()
96 .map(|editor| editor.downgrade())
97 {
98 Some(editor) => element.child(
99 ButtonLike::new("toggle outline view")
100 .child(breadcrumbs_stack)
101 .style(ButtonStyle::Transparent)
102 .on_click({
103 let editor = editor.clone();
104 move |_, cx| {
105 if let Some(editor) = editor.upgrade() {
106 outline::toggle(editor, &editor::actions::ToggleOutline, cx)
107 }
108 }
109 })
110 .tooltip(move |cx| {
111 if let Some(editor) = editor.upgrade() {
112 let focus_handle = editor.read(cx).focus_handle(cx);
113 Tooltip::for_action_in(
114 "Show Symbol Outline",
115 &editor::actions::ToggleOutline,
116 &focus_handle,
117 cx,
118 )
119 } else {
120 Tooltip::for_action(
121 "Show Symbol Outline",
122 &editor::actions::ToggleOutline,
123 cx,
124 )
125 }
126 }),
127 ),
128 None => element
129 // Match the height of the `ButtonLike` in the other arm.
130 .h(rems_from_px(22.))
131 .child(breadcrumbs_stack),
132 }
133 }
134}
135
136impl ToolbarItemView for Breadcrumbs {
137 fn set_active_pane_item(
138 &mut self,
139 active_pane_item: Option<&dyn ItemHandle>,
140 cx: &mut ViewContext<Self>,
141 ) -> ToolbarItemLocation {
142 cx.notify();
143 self.active_item = None;
144
145 let Some(item) = active_pane_item else {
146 return ToolbarItemLocation::Hidden;
147 };
148
149 let this = cx.view().downgrade();
150 self.subscription = Some(item.subscribe_to_item_events(
151 cx,
152 Box::new(move |event, cx| {
153 if let ItemEvent::UpdateBreadcrumbs = event {
154 this.update(cx, |this, cx| {
155 cx.notify();
156 if let Some(active_item) = this.active_item.as_ref() {
157 cx.emit(ToolbarItemEvent::ChangeLocation(
158 active_item.breadcrumb_location(cx),
159 ))
160 }
161 })
162 .ok();
163 }
164 }),
165 ));
166 self.active_item = Some(item.boxed_clone());
167 item.breadcrumb_location(cx)
168 }
169
170 fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
171 self.pane_focused = pane_focused;
172 }
173}