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 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, ]); 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), SemanticToken { token_type: Option<SharedString>, token_modifiers: Option<SharedString>, },} impl HighlightCategory { fn label(&self) -> SharedString { match self { HighlightCategory::Text(key) => format!("text: {key:?}").into(), HighlightCategory::SemanticToken { token_type: Some(token_type), token_modifiers: Some(modifiers), } => format!("semantic token: {token_type} [{modifiers}]").into(), HighlightCategory::SemanticToken { token_type: Some(token_type), token_modifiers: None, } => format!("semantic token: {token_type}").into(), HighlightCategory::SemanticToken { token_type: None, token_modifiers: Some(modifiers), } => format!("semantic token [{modifiers}]").into(), HighlightCategory::SemanticToken { token_type: None, token_modifiers: None, } => "semantic token".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_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_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(); 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; }; 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), }, 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::SemanticToken { .. } => self.show_semantic_tokens, } } fn scroll_to_cursor_position(&mut self, cursor: &Anchor, snapshot: &MultiBufferSnapshot) { let cursor_point = cursor.to_point(snapshot); let cursor_key = (cursor_point.row, cursor_point.column); let cursor_excerpt = cursor.excerpt_id; let best = self .display_items .iter() .enumerate() .filter_map(|(display_ix, item)| match item { DisplayItem::Entry { entry_ix } => { let entry = &self.cached_entries[*entry_ix]; Some((display_ix, *entry_ix, entry)) } _ => None, }) .filter(|(_, _, entry)| { let (excerpt_id, start_row, start_col, end_row, end_col) = entry.sort_key; if !self.is_singleton && excerpt_id != cursor_excerpt { return false; } let start = (start_row, start_col); let end = (end_row, end_col); cursor_key >= start && cursor_key <= end }) .min_by_key(|(_, _, entry)| { let (_, start_row, start_col, end_row, end_col) = entry.sort_key; (end_row - start_row, end_col.saturating_sub(start_col)) }) .map(|(display_ix, entry_ix, _)| (display_ix, entry_ix)); if let Some((display_ix, entry_ix)) = best { self.selected_item_ix = Some(entry_ix); self.list_scroll_handle .scroll_to_item(display_ix, ScrollStrategy::Center); } } fn update_editor_with_range_for_entry( &self, entry_ix: usize, window: &mut Window, cx: &mut Context<Self>, f: &mut dyn FnMut(&mut Editor, Range<Anchor>, usize, &mut Window, &mut Context<Editor>), ) -> Option<()> { let editor_state = self.editor.as_ref()?; let entry = self.cached_entries.get(entry_ix)?; let range = entry.range.clone(); let key = cx.entity_id().as_u64() as usize; editor_state.editor.update(cx, |editor, cx| { f(editor, range, key, window, cx); }); Some(()) } fn render_entry(&self, entry: &HighlightEntry, selected: bool, cx: &App) -> Div { let colors = cx.theme().colors(); let style_preview = render_style_preview(entry.style, selected, cx); h_flex() .gap_1() .child(style_preview) .child(Label::new(entry.range_display.clone()).color(Color::Default)) .child( Label::new(entry.category.label()) .size(LabelSize::Small) .color(Color::Muted), ) .text_bg(if selected { colors.element_selected } else { Hsla::default() }) .pl(rems(0.5)) .hover(|style| style.bg(colors.element_hover)) } fn render_separator(&self, label: &SharedString, cx: &App) -> Div { let colors = cx.theme().colors(); h_flex() .gap_1() .px(rems(0.5)) .bg(colors.surface_background) .border_b_1() .border_color(colors.border_variant) .child( Label::new(label.clone()) .size(LabelSize::Small) .color(Color::Muted), ) } fn compute_items( &mut self, visible_range: Range<usize>, _window: &mut Window, cx: &mut Context<Self>, ) -> Vec<Div> { let mut items = Vec::new(); for display_ix in visible_range { let Some(display_item) = self.display_items.get(display_ix) else { continue; }; match display_item {Lines 1–500 of 1046. 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 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, ] ); 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), SemanticToken { token_type: Option<SharedString>, token_modifiers: Option<SharedString>, }, } impl HighlightCategory { fn label(&self) -> SharedString { match self { HighlightCategory::Text(key) => format!("text: {key:?}").into(), HighlightCategory::SemanticToken { token_type: Some(token_type), token_modifiers: Some(modifiers), } => format!("semantic token: {token_type} [{modifiers}]").into(), HighlightCategory::SemanticToken { token_type: Some(token_type), token_modifiers: None, } => format!("semantic token: {token_type}").into(), HighlightCategory::SemanticToken { token_type: None, token_modifiers: Some(modifiers), } => format!("semantic token [{modifiers}]").into(), HighlightCategory::SemanticToken { token_type: None, token_modifiers: None, } => "semantic token".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_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_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(); 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; }; 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), }, 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::SemanticToken { .. } => self.show_semantic_tokens, } } fn scroll_to_cursor_position(&mut self, cursor: &Anchor, snapshot: &MultiBufferSnapshot) { let cursor_point = cursor.to_point(snapshot); let cursor_key = (cursor_point.row, cursor_point.column); let cursor_excerpt = cursor.excerpt_id; let best = self .display_items .iter() .enumerate() .filter_map(|(display_ix, item)| match item { DisplayItem::Entry { entry_ix } => { let entry = &self.cached_entries[*entry_ix]; Some((display_ix, *entry_ix, entry)) } _ => None, }) .filter(|(_, _, entry)| { let (excerpt_id, start_row, start_col, end_row, end_col) = entry.sort_key; if !self.is_singleton && excerpt_id != cursor_excerpt { return false; } let start = (start_row, start_col); let end = (end_row, end_col); cursor_key >= start && cursor_key <= end }) .min_by_key(|(_, _, entry)| { let (_, start_row, start_col, end_row, end_col) = entry.sort_key; (end_row - start_row, end_col.saturating_sub(start_col)) }) .map(|(display_ix, entry_ix, _)| (display_ix, entry_ix)); if let Some((display_ix, entry_ix)) = best { self.selected_item_ix = Some(entry_ix); self.list_scroll_handle .scroll_to_item(display_ix, ScrollStrategy::Center); } } fn update_editor_with_range_for_entry( &self, entry_ix: usize, window: &mut Window, cx: &mut Context<Self>, f: &mut dyn FnMut(&mut Editor, Range<Anchor>, usize, &mut Window, &mut Context<Editor>), ) -> Option<()> { let editor_state = self.editor.as_ref()?; let entry = self.cached_entries.get(entry_ix)?; let range = entry.range.clone(); let key = cx.entity_id().as_u64() as usize; editor_state.editor.update(cx, |editor, cx| { f(editor, range, key, window, cx); }); Some(()) } fn render_entry(&self, entry: &HighlightEntry, selected: bool, cx: &App) -> Div { let colors = cx.theme().colors(); let style_preview = render_style_preview(entry.style, selected, cx); h_flex() .gap_1() .child(style_preview) .child(Label::new(entry.range_display.clone()).color(Color::Default)) .child( Label::new(entry.category.label()) .size(LabelSize::Small) .color(Color::Muted), ) .text_bg(if selected { colors.element_selected } else { Hsla::default() }) .pl(rems(0.5)) .hover(|style| style.bg(colors.element_hover)) } fn render_separator(&self, label: &SharedString, cx: &App) -> Div { let colors = cx.theme().colors(); h_flex() .gap_1() .px(rems(0.5)) .bg(colors.surface_background) .border_b_1() .border_color(colors.border_variant) .child( Label::new(label.clone()) .size(LabelSize::Small) .color(Color::Muted), ) } fn compute_items( &mut self, visible_range: Range<usize>, _window: &mut Window, cx: &mut Context<Self>, ) -> Vec<Div> { let mut items = Vec::new(); for display_ix in visible_range { let Some(display_item) = self.display_items.get(display_ix) else { continue; }; match display_item {