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 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}