use editor::{ Anchor, Editor, ExcerptId, HighlightKey, MultiBufferSnapshot, SelectionEffects, ToPoint, scroll::Autoscroll,};use gpui::{ Action, App, AppContext as _, Context, Corner, Div, Entity, EntityId, EventEmitter, FocusHandle, Focusable, HighlightStyle, Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, ScrollStrategy, SharedString, Styled, Task, UniformListScrollHandle, WeakEntity, Window, actions, div, rems, uniform_list,};use language::ToOffset;use menu::{SelectNext, SelectPrevious};use std::{mem, ops::Range};use theme::ActiveTheme;use ui::{ ButtonCommon, ButtonLike, ButtonStyle, Color, ContextMenu, FluentBuilder as _, IconButton, IconName, IconPosition, IconSize, Label, LabelCommon, LabelSize, PopoverMenu, PopoverMenuHandle, StyledExt, Toggleable, Tooltip, WithScrollbar, h_flex, v_flex,};use workspace::{ Event as WorkspaceEvent, SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{Item, ItemHandle},}; actions!( dev, [ /// Opens the highlights tree view for the current file. OpenHighlightsTreeView, ]); actions!( highlights_tree_view, [ /// Toggles showing text highlights. ToggleTextHighlights, /// Toggles showing semantic token highlights. ToggleSemanticTokens, /// Toggles showing syntax token highlights. ToggleSyntaxTokens, ]); pub fn init(cx: &mut App) { cx.observe_new(move |workspace: &mut Workspace, _, _| { workspace.register_action(move |workspace, _: &OpenHighlightsTreeView, window, cx| { let active_item = workspace.active_item(cx); let workspace_handle = workspace.weak_handle(); let highlights_tree_view = cx.new(|cx| HighlightsTreeView::new(workspace_handle, active_item, window, cx)); workspace.split_item( SplitDirection::Right, Box::new(highlights_tree_view), window, cx, ) }); }) .detach();} #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]pub enum HighlightCategory { Text(HighlightKey), SyntaxToken { capture_name: SharedString, theme_key: Option<SharedString>, }, SemanticToken { token_type: Option<SharedString>, token_modifiers: Option<SharedString>, theme_key: Option<SharedString>, },} impl HighlightCategory { fn label(&self) -> SharedString { match self { HighlightCategory::Text(key) => format!("text: {key:?}").into(), HighlightCategory::SyntaxToken { capture_name, theme_key: Some(theme_key), } => format!("syntax: {capture_name} \u{2192} {theme_key}").into(), HighlightCategory::SyntaxToken { capture_name, theme_key: None, } => format!("syntax: {capture_name}").into(), HighlightCategory::SemanticToken { token_type, token_modifiers, theme_key, } => { let label = match (token_type, token_modifiers) { (Some(token_type), Some(modifiers)) => { format!("semantic token: {token_type} [{modifiers}]") } (Some(token_type), None) => format!("semantic token: {token_type}"), (None, Some(modifiers)) => format!("semantic token [{modifiers}]"), (None, None) => "semantic token".to_string(), }; if let Some(theme_key) = theme_key { format!("{label} \u{2192} {theme_key}").into() } else { label.into() } } } }} #[derive(Debug, Clone)]struct HighlightEntry { excerpt_id: ExcerptId, range: Range<Anchor>, range_display: SharedString, style: HighlightStyle, category: HighlightCategory, sort_key: (ExcerptId, u32, u32, u32, u32),} /// An item in the display list: either a separator between excerpts or a highlight entry.#[derive(Debug, Clone)]enum DisplayItem { ExcerptSeparator { label: SharedString, }, Entry { /// Index into `cached_entries`. entry_ix: usize, },} pub struct HighlightsTreeView { workspace_handle: WeakEntity<Workspace>, editor: Option<EditorState>, list_scroll_handle: UniformListScrollHandle, selected_item_ix: Option<usize>, hovered_item_ix: Option<usize>, focus_handle: FocusHandle, cached_entries: Vec<HighlightEntry>, display_items: Vec<DisplayItem>, is_singleton: bool, show_text_highlights: bool, show_syntax_tokens: bool, show_semantic_tokens: bool, skip_next_scroll: bool,} pub struct HighlightsTreeToolbarItemView { tree_view: Option<Entity<HighlightsTreeView>>, _subscription: Option<gpui::Subscription>, toggle_settings_handle: PopoverMenuHandle<ContextMenu>,} struct EditorState { editor: Entity<Editor>, _subscription: gpui::Subscription,} impl HighlightsTreeView { pub fn new( workspace_handle: WeakEntity<Workspace>, active_item: Option<Box<dyn ItemHandle>>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self { workspace_handle: workspace_handle.clone(), list_scroll_handle: UniformListScrollHandle::new(), editor: None, hovered_item_ix: None, selected_item_ix: None, focus_handle: cx.focus_handle(), cached_entries: Vec::new(), display_items: Vec::new(), is_singleton: true, show_text_highlights: true, show_syntax_tokens: true, show_semantic_tokens: true, skip_next_scroll: false, }; this.handle_item_updated(active_item, window, cx); cx.subscribe_in( &workspace_handle.upgrade().unwrap(), window, move |this, workspace, event, window, cx| match event { WorkspaceEvent::ItemAdded { .. } | WorkspaceEvent::ActiveItemChanged => { this.handle_item_updated(workspace.read(cx).active_item(cx), window, cx) } WorkspaceEvent::ItemRemoved { item_id } => { this.handle_item_removed(item_id, window, cx); } _ => {} }, ) .detach(); this } fn handle_item_updated( &mut self, active_item: Option<Box<dyn ItemHandle>>, window: &mut Window, cx: &mut Context<Self>, ) { let Some(editor) = active_item .filter(|item| item.item_id() != cx.entity_id()) .and_then(|item| item.downcast::<Editor>()) else { self.clear(cx); return; }; let is_different_editor = self .editor .as_ref() .is_none_or(|state| state.editor != editor); if is_different_editor { self.set_editor(editor, window, cx); } } fn handle_item_removed( &mut self, item_id: &EntityId, _window: &mut Window, cx: &mut Context<Self>, ) { if self .editor .as_ref() .is_some_and(|state| state.editor.entity_id() == *item_id) { self.clear(cx); } } fn clear(&mut self, cx: &mut Context<Self>) { self.cached_entries.clear(); self.display_items.clear(); self.selected_item_ix = None; self.hovered_item_ix = None; if let Some(state) = self.editor.take() { Self::clear_editor_highlights(&state.editor, cx); } cx.notify(); } fn set_editor(&mut self, editor: Entity<Editor>, window: &mut Window, cx: &mut Context<Self>) { if let Some(state) = &self.editor { if state.editor == editor { return; } Self::clear_editor_highlights(&state.editor, cx); } let subscription = cx.subscribe_in(&editor, window, |this, _, event, window, cx| match event { editor::EditorEvent::Reparsed(_) | editor::EditorEvent::SelectionsChanged { .. } => { this.refresh_highlights(window, cx); } _ => return, }); self.editor = Some(EditorState { editor, _subscription: subscription, }); self.refresh_highlights(window, cx); } fn refresh_highlights(&mut self, _window: &mut Window, cx: &mut Context<Self>) { let Some(editor_state) = self.editor.as_ref() else { self.clear(cx); return; }; let (display_map, project, multi_buffer, cursor_position) = { let editor = editor_state.editor.read(cx); let cursor = editor.selections.newest_anchor().head(); ( editor.display_map.clone(), editor.project().cloned(), editor.buffer().clone(), cursor, ) }; let Some(project) = project else { return; }; let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); let is_singleton = multi_buffer_snapshot.is_singleton(); self.is_singleton = is_singleton; let mut entries = Vec::new(); let semantic_theme = cx.theme().syntax().clone(); display_map.update(cx, |display_map, cx| { for (key, text_highlights) in display_map.all_text_highlights() { for range in &text_highlights.1 { let excerpt_id = range.start.excerpt_id; let (range_display, sort_key) = format_anchor_range( range, excerpt_id, &multi_buffer_snapshot, is_singleton, ); entries.push(HighlightEntry { excerpt_id, range: range.clone(), range_display, style: text_highlights.0, category: HighlightCategory::Text(*key), sort_key, }); } } project.read(cx).lsp_store().update(cx, |lsp_store, cx| { for (buffer_id, (tokens, interner)) in display_map.all_semantic_token_highlights() { let language_name = multi_buffer .read(cx) .buffer(*buffer_id) .and_then(|buf| buf.read(cx).language().map(|l| l.name())); for token in tokens.iter() { let range = token.range.start..token.range.end; let excerpt_id = range.start.excerpt_id; let (range_display, sort_key) = format_anchor_range( &range, excerpt_id, &multi_buffer_snapshot, is_singleton, ); let Some(stylizer) = lsp_store.get_or_create_token_stylizer( token.server_id, language_name.as_ref(), cx, ) else { continue; }; let theme_key = stylizer .rules_for_token(token.token_type) .and_then(|rules| { rules .iter() .filter(|rule| { rule.token_modifiers.iter().all(|modifier| { stylizer .has_modifier(token.token_modifiers, modifier) }) }) .fold(None, |theme_key, rule| { rule.style .iter() .find(|style_name| { semantic_theme.get_opt(style_name).is_some() }) .map(|style_name| { SharedString::from(style_name.clone()) }) .or(theme_key) }) }); entries.push(HighlightEntry { excerpt_id, range, range_display, style: interner[token.style], category: HighlightCategory::SemanticToken { token_type: stylizer.token_type_name(token.token_type).cloned(), token_modifiers: stylizer .token_modifiers(token.token_modifiers) .map(SharedString::from), theme_key, }, sort_key, }); } } }); }); let syntax_theme = cx.theme().syntax().clone(); for (excerpt_id, buffer_snapshot, excerpt_range) in multi_buffer_snapshot.excerpts() { let start_offset = excerpt_range.context.start.to_offset(buffer_snapshot); let end_offset = excerpt_range.context.end.to_offset(buffer_snapshot); let range = start_offset..end_offset; let captures = buffer_snapshot.captures(range, |grammar| { grammar.highlights_config.as_ref().map(|c| &c.query) }); let grammars: Vec<_> = captures.grammars().to_vec(); let highlight_maps: Vec<_> = grammars.iter().map(|g| g.highlight_map()).collect(); for capture in captures { let highlight_id = highlight_maps[capture.grammar_index].get(capture.index); let Some(style) = highlight_id.style(&syntax_theme) else { continue; }; let theme_key = highlight_id .name(&syntax_theme) .map(|theme_key| SharedString::from(theme_key.to_string())); let capture_name = grammars[capture.grammar_index] .highlights_config .as_ref() .and_then(|config| config.query.capture_names().get(capture.index as usize)) .map(|capture_name| SharedString::from((*capture_name).to_string())) .unwrap_or_else(|| SharedString::from("unknown")); let start_anchor = buffer_snapshot.anchor_before(capture.node.start_byte()); let end_anchor = buffer_snapshot.anchor_after(capture.node.end_byte()); let start = multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, start_anchor); let end = multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, end_anchor); let (start, end) = match (start, end) { (Some(s), Some(e)) => (s, e), _ => continue, }; let range = start..end; let (range_display, sort_key) = format_anchor_range(&range, excerpt_id, &multi_buffer_snapshot, is_singleton); entries.push(HighlightEntry { excerpt_id, range, range_display, style, category: HighlightCategory::SyntaxToken { capture_name, theme_key, }, sort_key, }); } } entries.sort_by(|a, b| { a.sort_key .cmp(&b.sort_key) .then_with(|| a.category.cmp(&b.category)) }); entries.dedup_by(|a, b| a.sort_key == b.sort_key && a.category == b.category); self.cached_entries = entries; self.rebuild_display_items(&multi_buffer_snapshot, cx); if self.skip_next_scroll { self.skip_next_scroll = false; } else { self.scroll_to_cursor_position(&cursor_position, &multi_buffer_snapshot); } cx.notify(); } fn rebuild_display_items(&mut self, snapshot: &MultiBufferSnapshot, cx: &App) { self.display_items.clear(); let mut last_excerpt_id: Option<ExcerptId> = None; for (entry_ix, entry) in self.cached_entries.iter().enumerate() { if !self.should_show_entry(entry) { continue; } if !self.is_singleton { let excerpt_changed = last_excerpt_id.is_none_or(|last_id| last_id != entry.excerpt_id); if excerpt_changed { last_excerpt_id = Some(entry.excerpt_id); let label = excerpt_label_for(entry.excerpt_id, snapshot, cx); self.display_items .push(DisplayItem::ExcerptSeparator { label }); } } self.display_items.push(DisplayItem::Entry { entry_ix }); } } fn should_show_entry(&self, entry: &HighlightEntry) -> bool { match entry.category { HighlightCategory::Text(_) => self.show_text_highlights, HighlightCategory::SyntaxToken { .. } => self.show_syntax_tokens, HighlightCategory::SemanticToken { .. } => self.show_semantic_tokens, }Lines 1–500 of 1185. Load more
use editor::{ Anchor, Editor, ExcerptId, HighlightKey, MultiBufferSnapshot, SelectionEffects, ToPoint, scroll::Autoscroll, }; use gpui::{ Action, App, AppContext as _, Context, Corner, Div, Entity, EntityId, EventEmitter, FocusHandle, Focusable, HighlightStyle, Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, ScrollStrategy, SharedString, Styled, Task, UniformListScrollHandle, WeakEntity, Window, actions, div, rems, uniform_list, }; use language::ToOffset; use menu::{SelectNext, SelectPrevious}; use std::{mem, ops::Range}; use theme::ActiveTheme; use ui::{ ButtonCommon, ButtonLike, ButtonStyle, Color, ContextMenu, FluentBuilder as _, IconButton, IconName, IconPosition, IconSize, Label, LabelCommon, LabelSize, PopoverMenu, PopoverMenuHandle, StyledExt, Toggleable, Tooltip, WithScrollbar, h_flex, v_flex, }; use workspace::{ Event as WorkspaceEvent, SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{Item, ItemHandle}, }; actions!( dev, [ /// Opens the highlights tree view for the current file. OpenHighlightsTreeView, ] ); actions!( highlights_tree_view, [ /// Toggles showing text highlights. ToggleTextHighlights, /// Toggles showing semantic token highlights. ToggleSemanticTokens, /// Toggles showing syntax token highlights. ToggleSyntaxTokens, ] ); pub fn init(cx: &mut App) { cx.observe_new(move |workspace: &mut Workspace, _, _| { workspace.register_action(move |workspace, _: &OpenHighlightsTreeView, window, cx| { let active_item = workspace.active_item(cx); let workspace_handle = workspace.weak_handle(); let highlights_tree_view = cx.new(|cx| HighlightsTreeView::new(workspace_handle, active_item, window, cx)); workspace.split_item( SplitDirection::Right, Box::new(highlights_tree_view), window, cx, ) }); }) .detach(); } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum HighlightCategory { Text(HighlightKey), SyntaxToken { capture_name: SharedString, theme_key: Option<SharedString>, }, SemanticToken { token_type: Option<SharedString>, token_modifiers: Option<SharedString>, theme_key: Option<SharedString>, }, } impl HighlightCategory { fn label(&self) -> SharedString { match self { HighlightCategory::Text(key) => format!("text: {key:?}").into(), HighlightCategory::SyntaxToken { capture_name, theme_key: Some(theme_key), } => format!("syntax: {capture_name} \u{2192} {theme_key}").into(), HighlightCategory::SyntaxToken { capture_name, theme_key: None, } => format!("syntax: {capture_name}").into(), HighlightCategory::SemanticToken { token_type, token_modifiers, theme_key, } => { let label = match (token_type, token_modifiers) { (Some(token_type), Some(modifiers)) => { format!("semantic token: {token_type} [{modifiers}]") } (Some(token_type), None) => format!("semantic token: {token_type}"), (None, Some(modifiers)) => format!("semantic token [{modifiers}]"), (None, None) => "semantic token".to_string(), }; if let Some(theme_key) = theme_key { format!("{label} \u{2192} {theme_key}").into() } else { label.into() } } } } } #[derive(Debug, Clone)] struct HighlightEntry { excerpt_id: ExcerptId, range: Range<Anchor>, range_display: SharedString, style: HighlightStyle, category: HighlightCategory, sort_key: (ExcerptId, u32, u32, u32, u32), } /// An item in the display list: either a separator between excerpts or a highlight entry. #[derive(Debug, Clone)] enum DisplayItem { ExcerptSeparator { label: SharedString, }, Entry { /// Index into `cached_entries`. entry_ix: usize, }, } pub struct HighlightsTreeView { workspace_handle: WeakEntity<Workspace>, editor: Option<EditorState>, list_scroll_handle: UniformListScrollHandle, selected_item_ix: Option<usize>, hovered_item_ix: Option<usize>, focus_handle: FocusHandle, cached_entries: Vec<HighlightEntry>, display_items: Vec<DisplayItem>, is_singleton: bool, show_text_highlights: bool, show_syntax_tokens: bool, show_semantic_tokens: bool, skip_next_scroll: bool, } pub struct HighlightsTreeToolbarItemView { tree_view: Option<Entity<HighlightsTreeView>>, _subscription: Option<gpui::Subscription>, toggle_settings_handle: PopoverMenuHandle<ContextMenu>, } struct EditorState { editor: Entity<Editor>, _subscription: gpui::Subscription, } impl HighlightsTreeView { pub fn new( workspace_handle: WeakEntity<Workspace>, active_item: Option<Box<dyn ItemHandle>>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self { workspace_handle: workspace_handle.clone(), list_scroll_handle: UniformListScrollHandle::new(), editor: None, hovered_item_ix: None, selected_item_ix: None, focus_handle: cx.focus_handle(), cached_entries: Vec::new(), display_items: Vec::new(), is_singleton: true, show_text_highlights: true, show_syntax_tokens: true, show_semantic_tokens: true, skip_next_scroll: false, }; this.handle_item_updated(active_item, window, cx); cx.subscribe_in( &workspace_handle.upgrade().unwrap(), window, move |this, workspace, event, window, cx| match event { WorkspaceEvent::ItemAdded { .. } | WorkspaceEvent::ActiveItemChanged => { this.handle_item_updated(workspace.read(cx).active_item(cx), window, cx) } WorkspaceEvent::ItemRemoved { item_id } => { this.handle_item_removed(item_id, window, cx); } _ => {} }, ) .detach(); this } fn handle_item_updated( &mut self, active_item: Option<Box<dyn ItemHandle>>, window: &mut Window, cx: &mut Context<Self>, ) { let Some(editor) = active_item .filter(|item| item.item_id() != cx.entity_id()) .and_then(|item| item.downcast::<Editor>()) else { self.clear(cx); return; }; let is_different_editor = self .editor .as_ref() .is_none_or(|state| state.editor != editor); if is_different_editor { self.set_editor(editor, window, cx); } } fn handle_item_removed( &mut self, item_id: &EntityId, _window: &mut Window, cx: &mut Context<Self>, ) { if self .editor .as_ref() .is_some_and(|state| state.editor.entity_id() == *item_id) { self.clear(cx); } } fn clear(&mut self, cx: &mut Context<Self>) { self.cached_entries.clear(); self.display_items.clear(); self.selected_item_ix = None; self.hovered_item_ix = None; if let Some(state) = self.editor.take() { Self::clear_editor_highlights(&state.editor, cx); } cx.notify(); } fn set_editor(&mut self, editor: Entity<Editor>, window: &mut Window, cx: &mut Context<Self>) { if let Some(state) = &self.editor { if state.editor == editor { return; } Self::clear_editor_highlights(&state.editor, cx); } let subscription = cx.subscribe_in(&editor, window, |this, _, event, window, cx| match event { editor::EditorEvent::Reparsed(_) | editor::EditorEvent::SelectionsChanged { .. } => { this.refresh_highlights(window, cx); } _ => return, }); self.editor = Some(EditorState { editor, _subscription: subscription, }); self.refresh_highlights(window, cx); } fn refresh_highlights(&mut self, _window: &mut Window, cx: &mut Context<Self>) { let Some(editor_state) = self.editor.as_ref() else { self.clear(cx); return; }; let (display_map, project, multi_buffer, cursor_position) = { let editor = editor_state.editor.read(cx); let cursor = editor.selections.newest_anchor().head(); ( editor.display_map.clone(), editor.project().cloned(), editor.buffer().clone(), cursor, ) }; let Some(project) = project else { return; }; let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); let is_singleton = multi_buffer_snapshot.is_singleton(); self.is_singleton = is_singleton; let mut entries = Vec::new(); let semantic_theme = cx.theme().syntax().clone(); display_map.update(cx, |display_map, cx| { for (key, text_highlights) in display_map.all_text_highlights() { for range in &text_highlights.1 { let excerpt_id = range.start.excerpt_id; let (range_display, sort_key) = format_anchor_range( range, excerpt_id, &multi_buffer_snapshot, is_singleton, ); entries.push(HighlightEntry { excerpt_id, range: range.clone(), range_display, style: text_highlights.0, category: HighlightCategory::Text(*key), sort_key, }); } } project.read(cx).lsp_store().update(cx, |lsp_store, cx| { for (buffer_id, (tokens, interner)) in display_map.all_semantic_token_highlights() { let language_name = multi_buffer .read(cx) .buffer(*buffer_id) .and_then(|buf| buf.read(cx).language().map(|l| l.name())); for token in tokens.iter() { let range = token.range.start..token.range.end; let excerpt_id = range.start.excerpt_id; let (range_display, sort_key) = format_anchor_range( &range, excerpt_id, &multi_buffer_snapshot, is_singleton, ); let Some(stylizer) = lsp_store.get_or_create_token_stylizer( token.server_id, language_name.as_ref(), cx, ) else { continue; }; let theme_key = stylizer .rules_for_token(token.token_type) .and_then(|rules| { rules .iter() .filter(|rule| { rule.token_modifiers.iter().all(|modifier| { stylizer .has_modifier(token.token_modifiers, modifier) }) }) .fold(None, |theme_key, rule| { rule.style .iter() .find(|style_name| { semantic_theme.get_opt(style_name).is_some() }) .map(|style_name| { SharedString::from(style_name.clone()) }) .or(theme_key) }) }); entries.push(HighlightEntry { excerpt_id, range, range_display, style: interner[token.style], category: HighlightCategory::SemanticToken { token_type: stylizer.token_type_name(token.token_type).cloned(), token_modifiers: stylizer .token_modifiers(token.token_modifiers) .map(SharedString::from), theme_key, }, sort_key, }); } } }); }); let syntax_theme = cx.theme().syntax().clone(); for (excerpt_id, buffer_snapshot, excerpt_range) in multi_buffer_snapshot.excerpts() { let start_offset = excerpt_range.context.start.to_offset(buffer_snapshot); let end_offset = excerpt_range.context.end.to_offset(buffer_snapshot); let range = start_offset..end_offset; let captures = buffer_snapshot.captures(range, |grammar| { grammar.highlights_config.as_ref().map(|c| &c.query) }); let grammars: Vec<_> = captures.grammars().to_vec(); let highlight_maps: Vec<_> = grammars.iter().map(|g| g.highlight_map()).collect(); for capture in captures { let highlight_id = highlight_maps[capture.grammar_index].get(capture.index); let Some(style) = highlight_id.style(&syntax_theme) else { continue; }; let theme_key = highlight_id .name(&syntax_theme) .map(|theme_key| SharedString::from(theme_key.to_string())); let capture_name = grammars[capture.grammar_index] .highlights_config .as_ref() .and_then(|config| config.query.capture_names().get(capture.index as usize)) .map(|capture_name| SharedString::from((*capture_name).to_string())) .unwrap_or_else(|| SharedString::from("unknown")); let start_anchor = buffer_snapshot.anchor_before(capture.node.start_byte()); let end_anchor = buffer_snapshot.anchor_after(capture.node.end_byte()); let start = multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, start_anchor); let end = multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, end_anchor); let (start, end) = match (start, end) { (Some(s), Some(e)) => (s, e), _ => continue, }; let range = start..end; let (range_display, sort_key) = format_anchor_range(&range, excerpt_id, &multi_buffer_snapshot, is_singleton); entries.push(HighlightEntry { excerpt_id, range, range_display, style, category: HighlightCategory::SyntaxToken { capture_name, theme_key, }, sort_key, }); } } entries.sort_by(|a, b| { a.sort_key .cmp(&b.sort_key) .then_with(|| a.category.cmp(&b.category)) }); entries.dedup_by(|a, b| a.sort_key == b.sort_key && a.category == b.category); self.cached_entries = entries; self.rebuild_display_items(&multi_buffer_snapshot, cx); if self.skip_next_scroll { self.skip_next_scroll = false; } else { self.scroll_to_cursor_position(&cursor_position, &multi_buffer_snapshot); } cx.notify(); } fn rebuild_display_items(&mut self, snapshot: &MultiBufferSnapshot, cx: &App) { self.display_items.clear(); let mut last_excerpt_id: Option<ExcerptId> = None; for (entry_ix, entry) in self.cached_entries.iter().enumerate() { if !self.should_show_entry(entry) { continue; } if !self.is_singleton { let excerpt_changed = last_excerpt_id.is_none_or(|last_id| last_id != entry.excerpt_id); if excerpt_changed { last_excerpt_id = Some(entry.excerpt_id); let label = excerpt_label_for(entry.excerpt_id, snapshot, cx); self.display_items .push(DisplayItem::ExcerptSeparator { label }); } } self.display_items.push(DisplayItem::Entry { entry_ix }); } } fn should_show_entry(&self, entry: &HighlightEntry) -> bool { match entry.category { HighlightCategory::Text(_) => self.show_text_highlights, HighlightCategory::SyntaxToken { .. } => self.show_syntax_tokens, HighlightCategory::SemanticToken { .. } => self.show_semantic_tokens, }