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