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