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