1337 lines · 46.9 KB · Rust
RawDownload
use editor::{    Anchor, Editor, HighlightKey, MultiBufferSnapshot, SelectionEffects, ToPoint,    scroll::Autoscroll,};use gpui::{    Action, App, AppContext as _, Context, 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::{BufferId, Point, ToOffset};use menu::{SelectNext, SelectPrevious};use std::{mem, ops::Range, sync::Arc, time::Duration};use theme::ActiveTheme;use theme::SyntaxTheme;use ui::{    ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, WithScrollbar, prelude::*,}; 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 {    range: Range<Anchor>,    buffer_id: BufferId,    buffer_point_range: Range<Point>,    range_display: SharedString,    style: HighlightStyle,    category: HighlightCategory,} /// 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,    refresh_task: Task<()>,} 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,} struct SemanticHighlightEntry {    range: Range<Anchor>,    style: HighlightStyle,    category: HighlightCategory,} struct HighlightRefreshInput {    multi_buffer_snapshot: MultiBufferSnapshot,    text_highlights: Vec<(HighlightKey, Arc<(HighlightStyle, Vec<Range<Anchor>>)>)>,    semantic_highlights: Vec<SemanticHighlightEntry>,    syntax_theme: Arc<SyntaxTheme>,} 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,            refresh_task: Task::ready(()),        };         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 active_editor = match active_item {            Some(active_item) => {                if active_item.item_id() == cx.entity_id() {                    return;                } else {                    match active_item.downcast::<Editor>() {                        Some(active_editor) => active_editor,                        None => {                            self.clear(cx);                            return;                        }                    }                }            }            None => {                self.clear(cx);                return;            }        };         let is_different_editor = self            .editor            .as_ref()            .is_none_or(|state| state.editor != active_editor);        if is_different_editor {            self.set_editor(active_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.refresh_task = Task::ready(());        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(_) => this.schedule_refresh_highlights(window, cx),                editor::EditorEvent::SelectionsChanged { .. } => {                    this.update_selection_from_editor(cx);                }                _ => return,            });         self.editor = Some(EditorState {            editor,            _subscription: subscription,        });        self.refresh_task = Task::ready(());        self.cached_entries.clear();        self.display_items.clear();        self.selected_item_ix = None;        self.hovered_item_ix = None;        self.schedule_refresh_highlights(window, cx);        cx.notify();    }     fn schedule_refresh_highlights(&mut self, window: &mut Window, cx: &mut Context<Self>) {        self.refresh_task = cx.spawn_in(window, async move |this, cx| {            cx.background_executor()                .timer(Duration::from_millis(30))                .await;             let Some(input) = this                .update(cx, |this, cx| this.highlight_refresh_input(cx))                .ok()                .flatten()            else {                return;            };             let new_highlights = cx                .background_spawn(async move { build_highlight_entries(input) })                .await;             this.update_in(cx, |this, _window, cx| {                this.apply_highlight_refresh(new_highlights, cx);            })            .ok();        });    }     fn highlight_refresh_input(&self, cx: &mut Context<Self>) -> Option<HighlightRefreshInput> {        let editor_state = self.editor.as_ref()?;        let editor = editor_state.editor.read(cx);        let display_map = editor.display_map.clone();        let project = editor.project().cloned()?;        let multi_buffer = editor.buffer().clone();        let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);        let syntax_theme = cx.theme().syntax().clone();         let (text_highlights, semantic_token_highlights) =            display_map.update(cx, |display_map, _| {                let text_highlights = display_map                    .all_text_highlights()                    .map(|(key, highlights)| (*key, highlights.clone()))                    .collect::<Vec<_>>();                let semantic_token_highlights = display_map                    .all_semantic_token_highlights()                    .map(|(buffer_id, (tokens, interner))| {                        (*buffer_id, tokens.clone(), interner.clone())                    })                    .collect::<Vec<_>>();                (text_highlights, semantic_token_highlights)            });         let mut semantic_highlights = Vec::new();        project.read(cx).lsp_store().update(cx, |lsp_store, cx| {            for (buffer_id, tokens, interner) in semantic_token_highlights {                let language_name = multi_buffer                    .read(cx)                    .buffer(buffer_id)                    .and_then(|buffer| buffer.read(cx).language().map(|language| language.name()));                 for token in tokens.iter() {                    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| {                                            syntax_theme.style_for_name(style_name).is_some()                                        })                                        .map(|style_name| SharedString::from(style_name.clone()))                                        .or(theme_key)                                })                        });                     semantic_highlights.push(SemanticHighlightEntry {                        range: token.range.start..token.range.end,                        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,                        },                    });                }            }        });         Some(HighlightRefreshInput {            multi_buffer_snapshot,            text_highlights,            semantic_highlights,            syntax_theme,        })    }     fn apply_highlight_refresh(        &mut self,        new_highlights: Vec<HighlightEntry>,        cx: &mut Context<Self>,    ) {        let Some(editor) = self.editor.as_ref().map(|state| state.editor.clone()) else {            return;        };        let editor = editor.read(cx);        let cursor_position = editor.selections.newest_anchor().head();        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);         self.is_singleton = multi_buffer_snapshot.is_singleton();        self.cached_entries = new_highlights;        self.hovered_item_ix = None;        self.rebuild_display_items(&multi_buffer_snapshot, cx);         let should_scroll = !mem::take(&mut self.skip_next_scroll);        self.update_selection_for_cursor(&cursor_position, &multi_buffer_snapshot, should_scroll);        self.sync_editor_highlight_for_selected_entry(cx);        cx.notify();    }     fn rebuild_display_items(&mut self, snapshot: &MultiBufferSnapshot, cx: &App) {        self.display_items.clear();         let mut last_range_end = 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_range_end.is_none_or(|anchor| {                    snapshot                        .excerpt_containing(anchor..entry.range.start)                        .is_none()                });                if excerpt_changed {                    last_range_end = Some(entry.range.end);                    let label = excerpt_label_for(entry, 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,        }    }     fn update_selection_from_editor(&mut self, cx: &mut Context<Self>) {        let Some(editor_state) = self.editor.as_ref() else {            return;        };         let (cursor_position, multi_buffer_snapshot) = {            let editor = editor_state.editor.read(cx);            (                editor.selections.newest_anchor().head(),                editor.buffer().read(cx).snapshot(cx),            )        };         let should_update_selection = !mem::take(&mut self.skip_next_scroll);        if !should_update_selection {            self.sync_editor_highlight_for_selected_entry(cx);            return;        }         let should_notify =

Lines 1–500 of 1337.