breadcrumbs.rs

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