1use editor::Editor;
2use gpui::{
3 Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
4 ViewContext,
5};
6use itertools::Itertools;
7use theme::ActiveTheme;
8use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
9use workspace::{
10 item::{ItemEvent, ItemHandle},
11 ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
12};
13
14pub struct Breadcrumbs {
15 pane_focused: bool,
16 active_item: Option<Box<dyn ItemHandle>>,
17 subscription: Option<Subscription>,
18}
19
20impl Breadcrumbs {
21 pub fn new() -> Self {
22 Self {
23 pane_focused: false,
24 active_item: Default::default(),
25 subscription: Default::default(),
26 }
27 }
28}
29
30impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
31
32impl Render for Breadcrumbs {
33 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
34 let element = h_stack().text_ui();
35 let Some(active_item) = self.active_item.as_ref() else {
36 return element;
37 };
38 let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
39 return element;
40 };
41
42 let highlighted_segments = segments.into_iter().map(|segment| {
43 let mut text_style = cx.text_style();
44 text_style.color = Color::Muted.color(cx);
45
46 StyledText::new(segment.text)
47 .with_highlights(&text_style, segment.highlights.unwrap_or_default())
48 .into_any()
49 });
50 let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
51 Label::new("›").color(Color::Muted).into_any_element()
52 });
53
54 let breadcrumbs_stack = h_stack().gap_1().children(breadcrumbs);
55 match active_item
56 .downcast::<Editor>()
57 .map(|editor| editor.downgrade())
58 {
59 Some(editor) => element.child(
60 ButtonLike::new("toggle outline view")
61 .child(breadcrumbs_stack)
62 .style(ButtonStyle::Subtle)
63 .on_click(move |_, cx| {
64 if let Some(editor) = editor.upgrade() {
65 outline::toggle(editor, &outline::Toggle, cx)
66 }
67 })
68 .tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
69 ),
70 None => element
71 // Match the height of the `ButtonLike` in the other arm.
72 .h(rems(22. / 16.))
73 .child(breadcrumbs_stack),
74 }
75 }
76}
77
78impl ToolbarItemView for Breadcrumbs {
79 fn set_active_pane_item(
80 &mut self,
81 active_pane_item: Option<&dyn ItemHandle>,
82 cx: &mut ViewContext<Self>,
83 ) -> ToolbarItemLocation {
84 cx.notify();
85 self.active_item = None;
86 if let Some(item) = active_pane_item {
87 let this = cx.view().downgrade();
88 self.subscription = Some(item.subscribe_to_item_events(
89 cx,
90 Box::new(move |event, cx| {
91 if let ItemEvent::UpdateBreadcrumbs = event {
92 this.update(cx, |this, cx| {
93 cx.notify();
94 if let Some(active_item) = this.active_item.as_ref() {
95 cx.emit(ToolbarItemEvent::ChangeLocation(
96 active_item.breadcrumb_location(cx),
97 ))
98 }
99 })
100 .ok();
101 }
102 }),
103 ));
104 self.active_item = Some(item.boxed_clone());
105 item.breadcrumb_location(cx)
106 } else {
107 ToolbarItemLocation::Hidden
108 }
109 }
110
111 fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
112 self.pane_focused = pane_focused;
113 }
114}