1185 lines · 43.1 KB · Rust
RawDownload
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.