1use editor::{Anchor, Editor};
2use gpui::{
3 elements::*, AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
4};
5use language::{BufferSnapshot, OutlineItem};
6use search::ProjectSearchView;
7use std::borrow::Cow;
8use theme::SyntaxTheme;
9use workspace::{ItemHandle, Settings, ToolbarItemLocation, ToolbarItemView};
10
11pub enum Event {
12 UpdateLocation,
13}
14
15pub struct Breadcrumbs {
16 editor: Option<ViewHandle<Editor>>,
17 project_search: Option<ViewHandle<ProjectSearchView>>,
18 subscriptions: Vec<Subscription>,
19}
20
21impl Breadcrumbs {
22 pub fn new() -> Self {
23 Self {
24 editor: Default::default(),
25 subscriptions: Default::default(),
26 project_search: Default::default(),
27 }
28 }
29
30 fn active_symbols(
31 &self,
32 theme: &SyntaxTheme,
33 cx: &AppContext,
34 ) -> Option<(BufferSnapshot, Vec<OutlineItem<Anchor>>)> {
35 let editor = self.editor.as_ref()?.read(cx);
36 let cursor = editor.newest_anchor_selection().head();
37 let (buffer, symbols) = editor
38 .buffer()
39 .read(cx)
40 .read(cx)
41 .symbols_containing(cursor, Some(theme))?;
42 Some((buffer, symbols))
43 }
44}
45
46impl Entity for Breadcrumbs {
47 type Event = Event;
48}
49
50impl View for Breadcrumbs {
51 fn ui_name() -> &'static str {
52 "Breadcrumbs"
53 }
54
55 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
56 let theme = cx.global::<Settings>().theme.clone();
57 let (buffer, symbols) =
58 if let Some((buffer, symbols)) = self.active_symbols(&theme.editor.syntax, cx) {
59 (buffer, symbols)
60 } else {
61 return Empty::new().boxed();
62 };
63
64 let filename = if let Some(path) = buffer.path() {
65 path.to_string_lossy()
66 } else {
67 Cow::Borrowed("untitled")
68 };
69
70 Flex::row()
71 .with_child(Label::new(filename.to_string(), theme.breadcrumbs.text.clone()).boxed())
72 .with_children(symbols.into_iter().flat_map(|symbol| {
73 [
74 Label::new(" 〉 ".to_string(), theme.breadcrumbs.text.clone()).boxed(),
75 Text::new(symbol.text, theme.breadcrumbs.text.clone())
76 .with_highlights(symbol.highlight_ranges)
77 .boxed(),
78 ]
79 }))
80 .contained()
81 .with_style(theme.breadcrumbs.container)
82 .aligned()
83 .left()
84 .boxed()
85 }
86}
87
88impl ToolbarItemView for Breadcrumbs {
89 fn set_active_pane_item(
90 &mut self,
91 active_pane_item: Option<&dyn ItemHandle>,
92 cx: &mut ViewContext<Self>,
93 ) -> ToolbarItemLocation {
94 cx.notify();
95 self.subscriptions.clear();
96 self.editor = None;
97 self.project_search = None;
98 if let Some(item) = active_pane_item {
99 if let Some(editor) = item.act_as::<Editor>(cx) {
100 self.subscriptions
101 .push(cx.subscribe(&editor, |_, _, event, cx| match event {
102 editor::Event::BufferEdited => cx.notify(),
103 editor::Event::SelectionsChanged { local } if *local => cx.notify(),
104 _ => {}
105 }));
106 self.editor = Some(editor);
107 if let Some(project_search) = item.downcast::<ProjectSearchView>() {
108 self.subscriptions
109 .push(cx.subscribe(&project_search, |_, _, _, cx| {
110 cx.emit(Event::UpdateLocation);
111 }));
112 self.project_search = Some(project_search.clone());
113
114 if project_search.read(cx).has_matches() {
115 ToolbarItemLocation::Secondary
116 } else {
117 ToolbarItemLocation::Hidden
118 }
119 } else {
120 ToolbarItemLocation::PrimaryLeft { flex: None }
121 }
122 } else {
123 ToolbarItemLocation::Hidden
124 }
125 } else {
126 ToolbarItemLocation::Hidden
127 }
128 }
129
130 fn location_for_event(
131 &self,
132 _: &Event,
133 current_location: ToolbarItemLocation,
134 cx: &AppContext,
135 ) -> ToolbarItemLocation {
136 if let Some(project_search) = self.project_search.as_ref() {
137 if project_search.read(cx).has_matches() {
138 ToolbarItemLocation::Secondary
139 } else {
140 ToolbarItemLocation::Hidden
141 }
142 } else {
143 current_location
144 }
145 }
146}