editor: Put more syntax walking features on the background (#48450)

Lukas Wirth created

Currently we always compute breadcrumbs and sticky headers on every
editor paint which is not cheap to do especially in bigger files, moving
this off to be computed on event handling where they change and then
caching them can save serveral milliseconds per render in bigger files.

This also puts matching brackets refreshing and document highlights on a
background task, as this tends to block the main task for prolonged time
as well.

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

Cargo.lock                                      |   1 
crates/breadcrumbs/Cargo.toml                   |   1 
crates/breadcrumbs/src/breadcrumbs.rs           |   3 
crates/debugger_ui/src/stack_trace_view.rs      |   4 
crates/editor/src/editor.rs                     | 131 +++++++++++++-----
crates/editor/src/editor_tests.rs               |   9 
crates/editor/src/element.rs                    |  21 --
crates/editor/src/highlight_matching_bracket.rs |  59 +++++---
crates/editor/src/items.rs                      |   5 
crates/git_ui/src/file_diff_view.rs             |   4 
crates/git_ui/src/file_history_view.rs          |   6 
crates/git_ui/src/multi_diff_view.rs            |   5 
crates/image_viewer/src/image_viewer.rs         |   4 
crates/onboarding/src/multibuffer_hint.rs       |   2 
crates/terminal_view/src/terminal_view.rs       |   2 
crates/workspace/src/item.rs                    |   9 
16 files changed, 157 insertions(+), 109 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -2348,7 +2348,6 @@ version = "0.1.0"
 dependencies = [
  "editor",
  "gpui",
- "theme",
  "ui",
  "workspace",
 ]

crates/breadcrumbs/Cargo.toml πŸ”—

@@ -15,7 +15,6 @@ doctest = false
 [dependencies]
 editor.workspace = true
 gpui.workspace = true
-theme.workspace = true
 ui.workspace = true
 workspace.workspace = true
 

crates/breadcrumbs/src/breadcrumbs.rs πŸ”—

@@ -1,6 +1,5 @@
 use editor::render_breadcrumb_text;
 use gpui::{Context, EventEmitter, IntoElement, Render, Subscription, Window};
-use theme::ActiveTheme;
 use ui::prelude::*;
 use workspace::{
     ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
@@ -44,7 +43,7 @@ impl Render for Breadcrumbs {
             return element.into_any_element();
         };
 
-        let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
+        let Some(segments) = active_item.breadcrumbs(cx) else {
             return element.into_any_element();
         };
 

crates/debugger_ui/src/stack_trace_view.rs πŸ”—

@@ -439,8 +439,8 @@ impl Item for StackTraceView {
         ToolbarItemLocation::PrimaryLeft
     }
 
-    fn breadcrumbs(&self, theme: &theme::Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
-        self.editor.breadcrumbs(theme, cx)
+    fn breadcrumbs(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
+        self.editor.breadcrumbs(cx)
     }
 
     fn added_to_workspace(

crates/editor/src/editor.rs πŸ”—

@@ -1340,6 +1340,11 @@ pub struct Editor {
     semantic_tokens_enabled: bool,
     update_semantic_tokens_task: Task<()>,
     semantic_tokens_fetched_for_buffers: HashMap<BufferId, clock::Global>,
+    pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
+    refresh_outline_symbols_task: Task<()>,
+    outline_symbols: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
+    sticky_headers_task: Task<()>,
+    sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
 }
 
 #[derive(Debug, PartialEq)]
@@ -1969,37 +1974,43 @@ impl Editor {
         Editor::new_internal(mode, buffer, project, None, window, cx)
     }
 
-    pub fn sticky_headers(
-        &self,
+    pub fn refresh_sticky_headers(
+        &mut self,
         display_snapshot: &DisplaySnapshot,
-        style: &EditorStyle,
-        cx: &App,
-    ) -> Option<Vec<OutlineItem<Anchor>>> {
-        let multi_buffer = self.buffer().read(cx);
-        let multi_buffer_snapshot = multi_buffer.snapshot(cx);
+        cx: &mut Context<Editor>,
+    ) {
+        if !self.mode.is_full() {
+            return;
+        }
+        let multi_buffer = display_snapshot.buffer_snapshot();
         let multi_buffer_visible_start = self
             .scroll_manager
             .native_anchor(display_snapshot, cx)
             .anchor
-            .to_point(&multi_buffer_snapshot);
-        let max_row = multi_buffer_snapshot.max_point().row;
+            .to_point(&multi_buffer);
+        let max_row = multi_buffer.max_point().row;
 
         let start_row = (multi_buffer_visible_start.row).min(max_row);
         let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
-
-        if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
-            let outline_items = buffer
+        let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
+            return;
+        };
+        let buffer = buffer.clone();
+        let &excerpt_id = excerpt_id;
+        let syntax = self.style(cx).syntax.clone();
+        let background_task = cx.background_spawn(async move {
+            buffer
                 .outline_items_containing(
                     Point::new(start_row, 0)..Point::new(end_row, 0),
                     true,
-                    Some(style.syntax.as_ref()),
+                    Some(syntax.as_ref()),
                 )
                 .into_iter()
                 .map(|outline_item| OutlineItem {
                     depth: outline_item.depth,
-                    range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
+                    range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
                     source_range_for_text: Anchor::range_in_buffer(
-                        *excerpt_id,
+                        excerpt_id,
                         outline_item.source_range_for_text,
                     ),
                     text: outline_item.text,
@@ -2007,15 +2018,21 @@ impl Editor {
                     name_ranges: outline_item.name_ranges,
                     body_range: outline_item
                         .body_range
-                        .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
+                        .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
                     annotation_range: outline_item
                         .annotation_range
-                        .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
-                });
-            return Some(outline_items.collect());
-        }
-
-        None
+                        .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
+                })
+                .collect()
+        });
+        self.sticky_headers_task = cx.spawn(async move |this, cx| {
+            let sticky_headers = background_task.await;
+            this.update(cx, |this, cx| {
+                this.sticky_headers = Some(sticky_headers);
+                cx.notify();
+            })
+            .ok();
+        });
     }
 
     fn new_internal(
@@ -2578,6 +2595,11 @@ impl Editor {
             update_semantic_tokens_task: Task::ready(()),
             semantic_tokens_fetched_for_buffers: HashMap::default(),
             number_deleted_lines: false,
+            refresh_matching_bracket_highlights_task: Task::ready(()),
+            refresh_outline_symbols_task: Task::ready(()),
+            outline_symbols: None,
+            sticky_headers_task: Task::ready(()),
+            sticky_headers: None,
         };
 
         if is_minimap {
@@ -2633,6 +2655,7 @@ impl Editor {
                                 .ok();
                         });
                     }
+                    editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
                 }
                 EditorEvent::Edited { .. } => {
                     let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
@@ -3577,6 +3600,7 @@ impl Editor {
 
             self.refresh_selected_text_highlights(false, window, cx);
             self.refresh_matching_bracket_highlights(window, cx);
+            self.refresh_outline_symbols(cx);
             self.update_visible_edit_prediction(window, cx);
             self.edit_prediction_requires_modifier_in_indent_conflict = true;
             self.inline_blame_popover.take();
@@ -7291,17 +7315,27 @@ impl Editor {
         }
 
         let snapshot = cursor_buffer.read(cx).snapshot();
-        let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
-        let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
-        if start_word_range != end_word_range {
-            self.document_highlights_task.take();
-            self.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
-            self.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
-            return None;
-        }
+        let word_ranges = cx.background_spawn(async move {
+            // this might look odd to put on the background thread, but
+            // `surrounding_word` can be quite expensive as it calls into
+            // tree-sitter language scopes
+            let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
+            let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
+            (start_word_range, end_word_range)
+        });
 
         let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
         self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
+            let (start_word_range, end_word_range) = word_ranges.await;
+            if start_word_range != end_word_range {
+                this.update(cx, |this, cx| {
+                    this.document_highlights_task.take();
+                    this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
+                    this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
+                })
+                .ok();
+                return;
+            }
             cx.background_executor()
                 .timer(Duration::from_millis(debounce))
                 .await;
@@ -7562,6 +7596,26 @@ impl Editor {
         });
     }
 
+    #[ztracing::instrument(skip_all)]
+    fn refresh_outline_symbols(&mut self, cx: &mut Context<Editor>) {
+        if !self.mode.is_full() {
+            return;
+        }
+        let cursor = self.selections.newest_anchor().head();
+        let multibuffer = self.buffer().read(cx).snapshot(cx);
+        let syntax = cx.theme().syntax().clone();
+        let background_task = cx
+            .background_spawn(async move { multibuffer.symbols_containing(cursor, Some(&syntax)) });
+        self.refresh_outline_symbols_task = cx.spawn(async move |this, cx| {
+            let symbols = background_task.await;
+            this.update(cx, |this, cx| {
+                this.outline_symbols = symbols;
+                cx.notify();
+            })
+            .ok();
+        });
+    }
+
     #[ztracing::instrument(skip_all)]
     fn refresh_selected_text_highlights(
         &mut self,
@@ -23773,6 +23827,8 @@ impl Editor {
                 self.refresh_code_actions(window, cx);
                 self.refresh_single_line_folds(window, cx);
                 self.refresh_matching_bracket_highlights(window, cx);
+                self.refresh_outline_symbols(cx);
+                self.refresh_sticky_headers(&self.snapshot(window, cx), cx);
                 if self.has_active_edit_prediction() {
                     self.update_visible_edit_prediction(window, cx);
                 }
@@ -25180,14 +25236,11 @@ impl Editor {
             show_underlines: self.diagnostics_enabled(),
         }
     }
-    fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
-        let cursor = self.selections.newest_anchor().head();
+    fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
         let multibuffer = self.buffer().read(cx);
         let is_singleton = multibuffer.is_singleton();
-        let (buffer_id, symbols) = multibuffer
-            .read(cx)
-            .symbols_containing(cursor, Some(variant.syntax()))?;
-        let buffer = multibuffer.buffer(buffer_id)?;
+        let (buffer_id, symbols) = self.outline_symbols.as_ref()?;
+        let buffer = multibuffer.buffer(*buffer_id)?;
 
         let buffer = buffer.read(cx);
         let settings = ThemeSettings::get_global(cx);
@@ -25220,9 +25273,9 @@ impl Editor {
             vec![]
         };
 
-        breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
-            text: symbol.text,
-            highlights: Some(symbol.highlight_ranges),
+        breadcrumbs.extend(symbols.iter().map(|symbol| BreadcrumbText {
+            text: symbol.text.clone(),
+            highlights: Some(symbol.highlight_ranges.clone()),
             font: Some(settings.buffer_font.clone()),
         }));
         Some(breadcrumbs)

crates/editor/src/editor_tests.rs πŸ”—

@@ -29063,8 +29063,10 @@ async fn test_sticky_scroll(cx: &mut TestAppContext) {
     let mut sticky_headers = |offset: ScrollOffset| {
         cx.update_editor(|e, window, cx| {
             e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
-            let style = e.style(cx).clone();
-            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
+        });
+        cx.run_until_parked();
+        cx.update_editor(|e, window, cx| {
+            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
                 .into_iter()
                 .map(
                     |StickyHeader {
@@ -29291,6 +29293,7 @@ async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
                 cx,
             );
         });
+        cx.run_until_parked();
         cx.simulate_click(
             gpui::Point {
                 x: px(0.),
@@ -29298,9 +29301,9 @@ async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
             },
             Modifiers::none(),
         );
+        cx.run_until_parked();
         cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
     };
-
     assert_eq!(
         scroll_and_click(
             4.5, // impl Bar is halfway off the screen

crates/editor/src/element.rs πŸ”—

@@ -4457,7 +4457,6 @@ impl EditorElement {
         gutter_dimensions: &GutterDimensions,
         gutter_hitbox: &Hitbox,
         text_hitbox: &Hitbox,
-        style: &EditorStyle,
         relative_line_numbers: RelativeLineNumbers,
         relative_to: Option<DisplayRow>,
         window: &mut Window,
@@ -4467,7 +4466,7 @@ impl EditorElement {
             .show_line_numbers
             .unwrap_or_else(|| EditorSettings::get_global(cx).gutter.line_numbers);
 
-        let rows = Self::sticky_headers(self.editor.read(cx), snapshot, style, cx);
+        let rows = Self::sticky_headers(self.editor.read(cx), snapshot);
 
         let mut lines = Vec::<StickyHeaderLine>::new();
 
@@ -4536,22 +4535,13 @@ impl EditorElement {
         })
     }
 
-    pub(crate) fn sticky_headers(
-        editor: &Editor,
-        snapshot: &EditorSnapshot,
-        style: &EditorStyle,
-        cx: &App,
-    ) -> Vec<StickyHeader> {
+    pub(crate) fn sticky_headers(editor: &Editor, snapshot: &EditorSnapshot) -> Vec<StickyHeader> {
         let scroll_top = snapshot.scroll_position().y;
 
         let mut end_rows = Vec::<DisplayRow>::new();
         let mut rows = Vec::<StickyHeader>::new();
 
-        let items = editor
-            .sticky_headers(&snapshot.display_snapshot, style, cx)
-            .unwrap_or_default();
-
-        for item in items {
+        for item in editor.sticky_headers.iter().flatten() {
             let start_point = item.range.start.to_point(snapshot.buffer_snapshot());
             let end_point = item.range.end.to_point(snapshot.buffer_snapshot());
 
@@ -4587,7 +4577,7 @@ impl EditorElement {
 
             end_rows.push(end_row);
             rows.push(StickyHeader {
-                item,
+                item: item.clone(),
                 sticky_row,
                 start_point,
                 offset,
@@ -8060,7 +8050,7 @@ pub(crate) fn render_buffer_header(
     let editor_handle: &dyn ItemHandle = editor;
 
     let breadcrumbs = if is_selected {
-        editor_read.breadcrumbs_inner(cx.theme(), cx)
+        editor_read.breadcrumbs_inner(cx)
     } else {
         None
     };
@@ -10278,7 +10268,6 @@ impl Element for EditorElement {
                             &gutter_dimensions,
                             &gutter_hitbox,
                             &text_hitbox,
-                            &style,
                             relative,
                             current_selection_head,
                             window,

crates/editor/src/highlight_matching_bracket.rs πŸ”—

@@ -1,5 +1,5 @@
 use crate::{Editor, HighlightKey, RangeToAnchorExt};
-use gpui::{Context, HighlightStyle, Window};
+use gpui::{AppContext, Context, HighlightStyle, Window};
 use language::CursorShape;
 use multi_buffer::MultiBufferOffset;
 use theme::ActiveTheme;
@@ -14,13 +14,13 @@ impl Editor {
         self.clear_highlights(HighlightKey::MatchingBracket, cx);
 
         let snapshot = self.snapshot(window, cx);
-        let buffer_snapshot = snapshot.buffer_snapshot();
         let newest_selection = self.selections.newest::<MultiBufferOffset>(&snapshot);
         // Don't highlight brackets if the selection isn't empty
         if !newest_selection.is_empty() {
             return;
         }
 
+        let buffer_snapshot = snapshot.buffer_snapshot();
         let head = newest_selection.head();
         if head > buffer_snapshot.len() {
             log::error!("bug: cursor offset is out of range while refreshing bracket highlights");
@@ -35,27 +35,35 @@ impl Editor {
                 tail += tail_ch.len_utf8();
             }
         }
-
-        if let Some((opening_range, closing_range)) =
-            buffer_snapshot.innermost_enclosing_bracket_ranges(head..tail, None)
-        {
-            self.highlight_text(
-                HighlightKey::MatchingBracket,
-                vec![
-                    opening_range.to_anchors(&buffer_snapshot),
-                    closing_range.to_anchors(&buffer_snapshot),
-                ],
-                HighlightStyle {
-                    background_color: Some(
-                        cx.theme()
-                            .colors()
-                            .editor_document_highlight_bracket_background,
-                    ),
-                    ..Default::default()
-                },
-                cx,
-            )
-        }
+        let task = cx.background_spawn({
+            let buffer_snapshot = buffer_snapshot.clone();
+            async move { buffer_snapshot.innermost_enclosing_bracket_ranges(head..tail, None) }
+        });
+        self.refresh_matching_bracket_highlights_task = cx.spawn(async move |editor, cx| {
+            if let Some((opening_range, closing_range)) = task.await {
+                let buffer_snapshot = snapshot.buffer_snapshot();
+                editor
+                    .update(cx, |editor, cx| {
+                        editor.highlight_text(
+                            HighlightKey::MatchingBracket,
+                            vec![
+                                opening_range.to_anchors(&buffer_snapshot),
+                                closing_range.to_anchors(&buffer_snapshot),
+                            ],
+                            HighlightStyle {
+                                background_color: Some(
+                                    cx.theme()
+                                        .colors()
+                                        .editor_document_highlight_bracket_background,
+                                ),
+                                ..Default::default()
+                            },
+                            cx,
+                        )
+                    })
+                    .ok();
+            }
+        });
     }
 }
 
@@ -117,6 +125,7 @@ mod tests {
                 another_test(1, 2, 3);
             }
         "#});
+        cx.run_until_parked();
         cx.assert_editor_text_highlights(
             HighlightKey::MatchingBracket,
             indoc! {r#"
@@ -131,6 +140,7 @@ mod tests {
                 another_test(1, Λ‡2, 3);
             }
         "#});
+        cx.run_until_parked();
         cx.assert_editor_text_highlights(
             HighlightKey::MatchingBracket,
             indoc! {r#"
@@ -145,6 +155,7 @@ mod tests {
                 anotherˇ_test(1, 2, 3);
             }
         "#});
+        cx.run_until_parked();
         cx.assert_editor_text_highlights(
             HighlightKey::MatchingBracket,
             indoc! {r#"
@@ -160,6 +171,7 @@ mod tests {
                 another_test(1, 2, 3);
             }
         "#});
+        cx.run_until_parked();
         cx.assert_editor_text_highlights(
             HighlightKey::MatchingBracket,
             indoc! {r#"
@@ -175,6 +187,7 @@ mod tests {
                 another_test(1, 2, 3);
             }
         "#});
+        cx.run_until_parked();
         cx.assert_editor_text_highlights(
             HighlightKey::MatchingBracket,
             indoc! {r#"

crates/editor/src/items.rs πŸ”—

@@ -39,7 +39,6 @@ use std::{
     sync::Arc,
 };
 use text::{BufferId, BufferSnapshot, Selection};
-use theme::Theme;
 use ui::{IconDecorationKind, prelude::*};
 use util::{ResultExt, TryFutureExt, paths::PathExt};
 use workspace::{
@@ -978,9 +977,9 @@ impl Item for Editor {
     }
 
     // In a non-singleton case, the breadcrumbs are actually shown on sticky file headers of the multibuffer.
-    fn breadcrumbs(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
+    fn breadcrumbs(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
         if self.buffer.read(cx).is_singleton() {
-            self.breadcrumbs_inner(variant, cx)
+            self.breadcrumbs_inner(cx)
         } else {
             None
         }

crates/git_ui/src/file_diff_view.rs πŸ”—

@@ -323,8 +323,8 @@ impl Item for FileDiffView {
         ToolbarItemLocation::PrimaryLeft
     }
 
-    fn breadcrumbs(&self, theme: &theme::Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
-        self.editor.breadcrumbs(theme, cx)
+    fn breadcrumbs(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
+        self.editor.breadcrumbs(cx)
     }
 
     fn added_to_workspace(

crates/git_ui/src/file_history_view.rs πŸ”—

@@ -639,11 +639,7 @@ impl Item for FileHistoryView {
         false
     }
 
-    fn breadcrumbs(
-        &self,
-        _theme: &theme::Theme,
-        _cx: &App,
-    ) -> Option<Vec<workspace::item::BreadcrumbText>> {
+    fn breadcrumbs(&self, _cx: &App) -> Option<Vec<workspace::item::BreadcrumbText>> {
         None
     }
 

crates/git_ui/src/multi_diff_view.rs πŸ”—

@@ -13,7 +13,6 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
-use theme;
 use ui::{Color, Icon, IconName, Label, LabelCommon as _};
 use util::paths::PathStyle;
 use util::rel_path::RelPath;
@@ -339,8 +338,8 @@ impl Item for MultiDiffView {
         ToolbarItemLocation::PrimaryLeft
     }
 
-    fn breadcrumbs(&self, theme: &theme::Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
-        self.editor.breadcrumbs(theme, cx)
+    fn breadcrumbs(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
+        self.editor.breadcrumbs(cx)
     }
 
     fn added_to_workspace(

crates/image_viewer/src/image_viewer.rs πŸ”—

@@ -17,7 +17,7 @@ use language::File as _;
 use persistence::IMAGE_VIEWER;
 use project::{ImageItem, Project, ProjectPath, image_store::ImageItemEvent};
 use settings::Settings;
-use theme::{Theme, ThemeSettings};
+use theme::ThemeSettings;
 use ui::{Tooltip, prelude::*};
 use util::paths::PathExt;
 use workspace::{
@@ -558,7 +558,7 @@ impl Item for ImageView {
         }
     }
 
-    fn breadcrumbs(&self, _theme: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
+    fn breadcrumbs(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
         let text = breadcrumbs_text_for_image(self.project.read(cx), self.image_item.read(cx), cx);
         let settings = ThemeSettings::get_global(cx);
 

crates/onboarding/src/multibuffer_hint.rs πŸ”—

@@ -80,7 +80,7 @@ impl MultibufferHint {
         };
 
         if active_pane_item.buffer_kind(cx) == ItemBufferKind::Singleton
-            || active_pane_item.breadcrumbs(cx.theme(), cx).is_none()
+            || active_pane_item.breadcrumbs(cx).is_none()
             || !active_pane_item.can_save(cx)
         {
             return ToolbarItemLocation::Hidden;

crates/terminal_view/src/terminal_view.rs πŸ”—

@@ -1494,7 +1494,7 @@ impl Item for TerminalView {
         }
     }
 
-    fn breadcrumbs(&self, _: &theme::Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
+    fn breadcrumbs(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
         Some(vec![BreadcrumbText {
             text: self.terminal().read(cx).breadcrumb_text.clone(),
             highlights: None,

crates/workspace/src/item.rs πŸ”—

@@ -31,7 +31,6 @@ use std::{
     sync::Arc,
     time::Duration,
 };
-use theme::Theme;
 use ui::{Color, Icon, IntoElement, Label, LabelCommon};
 use util::ResultExt;
 
@@ -328,7 +327,7 @@ pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
         ToolbarItemLocation::Hidden
     }
 
-    fn breadcrumbs(&self, _theme: &Theme, _cx: &App) -> Option<Vec<BreadcrumbText>> {
+    fn breadcrumbs(&self, _cx: &App) -> Option<Vec<BreadcrumbText>> {
         None
     }
 
@@ -535,7 +534,7 @@ pub trait ItemHandle: 'static + Send {
     ) -> gpui::Subscription;
     fn to_searchable_item_handle(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
     fn breadcrumb_location(&self, cx: &App) -> ToolbarItemLocation;
-    fn breadcrumbs(&self, theme: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>>;
+    fn breadcrumbs(&self, cx: &App) -> Option<Vec<BreadcrumbText>>;
     fn breadcrumb_prefix(&self, window: &mut Window, cx: &mut App) -> Option<gpui::AnyElement>;
     fn show_toolbar(&self, cx: &App) -> bool;
     fn pixel_position_of_cursor(&self, cx: &App) -> Option<Point<Pixels>>;
@@ -1059,8 +1058,8 @@ impl<T: Item> ItemHandle for Entity<T> {
         self.read(cx).breadcrumb_location(cx)
     }
 
-    fn breadcrumbs(&self, theme: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
-        self.read(cx).breadcrumbs(theme, cx)
+    fn breadcrumbs(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
+        self.read(cx).breadcrumbs(cx)
     }
 
     fn breadcrumb_prefix(&self, window: &mut Window, cx: &mut App) -> Option<gpui::AnyElement> {