breadcrumbs.rs

  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}