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