1use gpui::{
2 Div, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
3 ViewContext, WeakView,
4};
5use itertools::Itertools;
6use theme::ActiveTheme;
7use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
8use workspace::{
9 item::{ItemEvent, ItemHandle},
10 ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
11};
12
13pub enum Event {
14 UpdateLocation,
15}
16
17pub struct Breadcrumbs {
18 pane_focused: bool,
19 active_item: Option<Box<dyn ItemHandle>>,
20 subscription: Option<Subscription>,
21 workspace: WeakView<Workspace>,
22}
23
24impl Breadcrumbs {
25 pub fn new(workspace: &Workspace) -> Self {
26 Self {
27 pane_focused: false,
28 active_item: Default::default(),
29 subscription: Default::default(),
30 workspace: workspace.weak_handle(),
31 }
32 }
33}
34
35impl EventEmitter<Event> for Breadcrumbs {}
36impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
37
38impl Render for Breadcrumbs {
39 type Element = Div;
40
41 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
42 let element = h_stack().text_ui();
43
44 let Some(active_item) = &self
45 .active_item
46 .as_ref()
47 .filter(|item| item.downcast::<editor::Editor>().is_some())
48 else {
49 return element;
50 };
51
52 let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
53 return element;
54 };
55
56 let highlighted_segments = segments.into_iter().map(|segment| {
57 StyledText::new(segment.text)
58 .with_highlights(&cx.text_style(), segment.highlights.unwrap_or_default())
59 .into_any()
60 });
61 let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
62 Label::new("›").into_any_element()
63 });
64
65 element.child(
66 ButtonLike::new("toggle outline view")
67 .style(ButtonStyle::Subtle)
68 .child(h_stack().gap_1().children(breadcrumbs))
69 // We disable the button when the containing pane is not focused:
70 // Because right now all the breadcrumb does is open the outline view, which is an
71 // action which operates on the active editor, clicking the breadcrumbs of another
72 // editor could cause weirdness. I remember that at one point it actually caused a
73 // panic weirdly.
74 //
75 // It might be possible that with changes around how focus is managed that we
76 // might be able to update the active editor to the one with the breadcrumbs
77 // clicked on? That or we could just add a code path for being able to open the
78 // outline for a specific editor. Long term we'd like for it to be an actual
79 // breadcrumb bar so that problem goes away
80 //
81 // — Julia (https://github.com/zed-industries/zed/pull/3505#pullrequestreview-1766198050)
82 .disabled(!self.pane_focused)
83 .on_click(cx.listener(|breadcrumbs, _, cx| {
84 if let Some(workspace) = breadcrumbs.workspace.upgrade() {
85 workspace.update(cx, |workspace, cx| {
86 outline::toggle(workspace, &outline::Toggle, cx)
87 })
88 }
89 }))
90 .tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
91 )
92 }
93}
94
95impl ToolbarItemView for Breadcrumbs {
96 fn set_active_pane_item(
97 &mut self,
98 active_pane_item: Option<&dyn ItemHandle>,
99 cx: &mut ViewContext<Self>,
100 ) -> ToolbarItemLocation {
101 cx.notify();
102 self.active_item = None;
103 if let Some(item) = active_pane_item {
104 let this = cx.view().downgrade();
105 self.subscription = Some(item.subscribe_to_item_events(
106 cx,
107 Box::new(move |event, cx| {
108 if let ItemEvent::UpdateBreadcrumbs = event {
109 this.update(cx, |_, cx| {
110 cx.emit(Event::UpdateLocation);
111 cx.notify();
112 })
113 .ok();
114 }
115 }),
116 ));
117 self.active_item = Some(item.boxed_clone());
118 item.breadcrumb_location(cx)
119 } else {
120 ToolbarItemLocation::Hidden
121 }
122 }
123
124 // fn location_for_event(
125 // &self,
126 // _: &Event,
127 // current_location: ToolbarItemLocation,
128 // cx: &AppContext,
129 // ) -> ToolbarItemLocation {
130 // if let Some(active_item) = self.active_item.as_ref() {
131 // active_item.breadcrumb_location(cx)
132 // } else {
133 // current_location
134 // }
135 // }
136
137 fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
138 self.pane_focused = pane_focused;
139 }
140}