1046 lines · 36.7 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 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.