1263 lines · 44.7 KB · Rust
RawDownload
use std::sync::Arc; use collections::{HashMap, HashSet};use futures::future::join_all;use gpui::{MouseButton, SharedString, Task, TaskExt, WeakEntity};use itertools::Itertools;use language::{BufferId, ClientCommand};use multi_buffer::{Anchor, MultiBufferRow, MultiBufferSnapshot, ToPoint as _};use project::{CodeAction, TaskSourceKind};use task::TaskContext; use ui::{Context, Window, div, prelude::*}; use crate::{    Editor, LSP_REQUEST_DEBOUNCE_TIMEOUT, SelectionEffects,    actions::ToggleCodeLens,    display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock},    hover_links::HoverLink,}; #[derive(Clone, Debug)]struct CodeLensLine {    position: Anchor,    indent_column: u32,    items: Vec<CodeLensItem>,} #[derive(Clone, Debug)]struct CodeLensItem {    title: SharedString,    action: CodeAction,} pub(super) struct CodeLensBlock {    block_id: CustomBlockId,    anchor: Anchor,    line: CodeLensLine,} pub(super) struct CodeLensState {    pub(super) blocks: HashMap<BufferId, Vec<CodeLensBlock>>,    actions: HashMap<BufferId, Vec<CodeAction>>,    resolve_task: Task<()>,} impl Default for CodeLensState {    fn default() -> Self {        Self {            blocks: HashMap::default(),            actions: HashMap::default(),            resolve_task: Task::ready(()),        }    }} pub(super) fn try_handle_client_command(    action: &CodeAction,    editor: &mut Editor,    workspace: &gpui::Entity<workspace::Workspace>,    window: &mut Window,    cx: &mut Context<Editor>,) -> bool {    let Some(command) = action.lsp_action.command() else {        return false;    };     let arguments = command.arguments.as_deref().unwrap_or_default();    let project = workspace.read(cx).project().clone();    let client_command = project        .read(cx)        .lsp_store()        .read(cx)        .language_server_adapter_for_id(action.server_id)        .and_then(|adapter| adapter.adapter.client_command(&command.command, arguments))        .or_else(|| match command.command.as_str() {            "editor.action.showReferences"            | "editor.action.goToLocations"            | "editor.action.peekLocations" => Some(ClientCommand::ShowLocations),            _ => None,        });     match client_command {        Some(ClientCommand::ScheduleTask(task_template)) => {            schedule_task(task_template, action, editor, workspace, window, cx)        }        Some(ClientCommand::ShowLocations) => {            try_show_references(arguments, action, editor, window, cx)        }        None => false,    }} fn schedule_task(    task_template: task::TaskTemplate,    action: &CodeAction,    editor: &Editor,    workspace: &gpui::Entity<workspace::Workspace>,    window: &mut Window,    cx: &mut Context<Editor>,) -> bool {    let task_context = TaskContext {        cwd: task_template.cwd.as_ref().map(std::path::PathBuf::from),        ..TaskContext::default()    };    let language_name = editor        .buffer()        .read(cx)        .buffer(action.range.start.buffer_id)        .and_then(|buffer| buffer.read(cx).language())        .map(|language| language.name());    let task_source_kind = match language_name {        Some(language_name) => TaskSourceKind::Lsp {            server: action.server_id,            language_name: SharedString::from(language_name),        },        None => TaskSourceKind::AbsPath {            id_base: "code-lens".into(),            abs_path: task_template                .cwd                .as_ref()                .map(std::path::PathBuf::from)                .unwrap_or_default(),        },    };     workspace.update(cx, |workspace, cx| {        workspace.schedule_task(            task_source_kind,            &task_template,            &task_context,            false,            window,            cx,        );    });    true} fn try_show_references(    arguments: &[serde_json::Value],    action: &CodeAction,    editor: &mut Editor,    window: &mut Window,    cx: &mut Context<Editor>,) -> bool {    if arguments.len() < 3 {        return false;    }    let Ok(locations) = serde_json::from_value::<Vec<lsp::Location>>(arguments[2].clone()) else {        return false;    };    if locations.is_empty() {        return false;    }     let server_id = action.server_id;    let nav_entry = editor.navigation_entry(editor.selections.newest_anchor().head(), cx);    let links = locations        .into_iter()        .map(|location| HoverLink::InlayHint(location, server_id))        .collect();    editor        .navigate_to_hover_links(None, links, nav_entry, false, window, cx)        .detach_and_log_err(cx);     true} impl Editor {    pub(super) fn refresh_code_lenses(        &mut self,        for_buffer: Option<BufferId>,        _window: &Window,        cx: &mut Context<Self>,    ) {        if !self.lsp_data_enabled() || self.code_lens.is_none() {            return;        }        let Some(project) = self.project.clone() else {            return;        };         let buffers_to_query = self            .visible_buffers(cx)            .into_iter()            .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))            .chain(for_buffer.and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id)))            .filter(|editor_buffer| {                let editor_buffer_id = editor_buffer.read(cx).remote_id();                for_buffer.is_none_or(|buffer_id| buffer_id == editor_buffer_id)                    && self.registered_buffers.contains_key(&editor_buffer_id)            })            .unique_by(|buffer| buffer.read(cx).remote_id())            .collect::<Vec<_>>();         if buffers_to_query.is_empty() {            return;        }         let project = project.downgrade();        self.refresh_code_lens_task = cx.spawn(async move |editor, cx| {            cx.background_executor()                .timer(LSP_REQUEST_DEBOUNCE_TIMEOUT)                .await;             let Some(tasks) = project                .update(cx, |project, cx| {                    project.lsp_store().update(cx, |lsp_store, cx| {                        buffers_to_query                            .into_iter()                            .map(|buffer| {                                let buffer_id = buffer.read(cx).remote_id();                                let task = lsp_store.code_lens_actions(&buffer, cx);                                async move { (buffer_id, task.await) }                            })                            .collect::<Vec<_>>()                    })                })                .ok()            else {                return;            };             let results = join_all(tasks).await;            if results.is_empty() {                return;            }             editor                .update(cx, |editor, cx| {                    let snapshot = editor.buffer().read(cx).snapshot(cx);                    for (buffer_id, result) in results {                        let actions = match result {                            Ok(Some(actions)) => actions,                            Ok(None) => continue,                            Err(e) => {                                log::error!(                                    "Failed to fetch code lenses for buffer {buffer_id:?}: {e:#}"                                );                                continue;                            }                        };                        editor.apply_lens_actions_for_buffer(buffer_id, actions, &snapshot, cx);                    }                    editor.resolve_visible_code_lenses(cx);                })                .ok();        });    }     /// Reconciles the set of blocks for `buffer_id` with `actions`. For each    /// existing block at row `R`:    /// - if the new fetch has no lens at `R` → remove the block (the lens is    ///   gone, e.g. the function was deleted);    /// - if the new fetch has a titled lens at `R` whose rendered text    ///   differs from the block's current line → swap the renderer in place    ///   via [`Editor::replace_blocks`];    /// - if the new fetch has a titled lens at `R` with the same rendered    ///   text → keep the block as-is;    /// - if the new fetch has a lens at `R` but no `command` yet (the server    ///   sent a shallow response that needs a separate `resolve`) → keep the    ///   block as-is. The previously rendered (resolved) content stays on    ///   screen until the next viewport-driven `resolve` produces a new    ///   title; only then does the comparison-and-replace happen. This is    ///   what keeps the post-edit screen from flickering for shallow servers    ///   like `rust-analyzer`.    ///    /// Rows present in the new fetch with a title but no existing block get    /// a fresh block inserted.    fn apply_lens_actions_for_buffer(        &mut self,        buffer_id: BufferId,        actions: Vec<CodeAction>,        snapshot: &MultiBufferSnapshot,        cx: &mut Context<Self>,    ) {        let mut rows_with_any_lens = HashSet::default();        let mut titled_lenses = Vec::new();        for action in &actions {            let Some(position) = snapshot.anchor_in_excerpt(action.range.start) else {                continue;            };             rows_with_any_lens.insert(MultiBufferRow(position.to_point(snapshot).row));            if let project::LspAction::CodeLens(lens) = &action.lsp_action {                if let Some(title) = lens                    .command                    .as_ref()                    .map(|cmd| SharedString::from(&cmd.title))                {                    titled_lenses.push((                        position,                        CodeLensItem {                            title,                            action: action.clone(),                        },                    ));                }            }        }         let mut new_lines_by_row = group_lenses_by_row(titled_lenses, snapshot)            .map(|line| (MultiBufferRow(line.position.to_point(snapshot).row), line))            .collect::<HashMap<_, _>>();         let editor_handle = cx.entity().downgrade();        let code_lens = self.code_lens.get_or_insert_with(CodeLensState::default);        let old_blocks = code_lens.blocks.remove(&buffer_id).unwrap_or_default();         let mut kept_blocks = Vec::new();        let mut renderers_to_replace = HashMap::default();        let mut blocks_to_remove = HashSet::default();        let mut covered_rows = HashSet::default();         for old in old_blocks {            let row = MultiBufferRow(old.anchor.to_point(snapshot).row);            if !rows_with_any_lens.contains(&row) {                blocks_to_remove.insert(old.block_id);                continue;            }            covered_rows.insert(row);            let Some(new_line) = new_lines_by_row.remove(&row) else {                kept_blocks.push(old);                continue;            };            if rendered_text_matches(&old.line, &new_line) {                kept_blocks.push(old);            } else {                let mut updated = old;                updated.line = new_line.clone();                renderers_to_replace.insert(                    updated.block_id,                    build_code_lens_renderer(new_line, editor_handle.clone()),                );                kept_blocks.push(updated);            }        }         let mut to_insert = Vec::new();        for (row, new_line) in new_lines_by_row {            if covered_rows.contains(&row) {                continue;            }            let anchor = new_line.position;            let props = BlockProperties {                placement: BlockPlacement::Above(anchor),                height: Some(1),                style: BlockStyle::Spacer,                render: build_code_lens_renderer(new_line.clone(), editor_handle.clone()),                priority: 0,            };            to_insert.push((props, anchor, new_line));        }         if !blocks_to_remove.is_empty() {            self.remove_blocks(blocks_to_remove, None, cx);        }        if !renderers_to_replace.is_empty() {            self.replace_blocks(renderers_to_replace, None, cx);        }        if !to_insert.is_empty() {            let mut props = Vec::with_capacity(to_insert.len());            let mut metadata = Vec::with_capacity(to_insert.len());            for (p, anchor, line) in to_insert {                props.push(p);                metadata.push((anchor, line));            }            let block_ids = self.insert_blocks(props, None, cx);            for (block_id, (anchor, line)) in block_ids.into_iter().zip(metadata) {                kept_blocks.push(CodeLensBlock {                    block_id,                    anchor,                    line,                });            }        }         let code_lens = self.code_lens.get_or_insert_with(CodeLensState::default);        if actions.is_empty() {            code_lens.actions.remove(&buffer_id);        } else {            code_lens.actions.insert(buffer_id, actions);        }        if kept_blocks.is_empty() {            code_lens.blocks.remove(&buffer_id);        } else {            code_lens.blocks.insert(buffer_id, kept_blocks);        }        cx.notify();    }     pub fn supports_code_lens(&self, cx: &ui::App) -> bool {        let Some(project) = self.project.as_ref() else {            return false;        };        let lsp_store = project.read(cx).lsp_store().read(cx);        lsp_store            .lsp_server_capabilities            .values()            .any(|caps| caps.code_lens_provider.is_some())    }     pub fn code_lens_enabled(&self) -> bool {        self.code_lens.is_some()    }     pub fn toggle_code_lens_action(        &mut self,        _: &ToggleCodeLens,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let currently_enabled = self.code_lens.is_some();        self.toggle_code_lens(!currently_enabled, window, cx);    }     pub(super) fn toggle_code_lens(        &mut self,        enabled: bool,        window: &mut Window,        cx: &mut Context<Self>,    ) {        if enabled {            self.code_lens.get_or_insert_with(CodeLensState::default);            self.refresh_code_lenses(None, window, cx);        } else {            self.clear_code_lenses(cx);        }    }     pub(super) fn resolve_visible_code_lenses(&mut self, cx: &mut Context<Self>) {        if !self.lsp_data_enabled() || self.code_lens.is_none() {            return;        }        let Some(project) = self.project.clone() else {            return;        };         let resolve_tasks = self            .visible_buffer_ranges(cx)            .into_iter()            .filter_map(|(snapshot, visible_range, _)| {                let buffer_id = snapshot.remote_id();                let buffer = self.buffer.read(cx).buffer(buffer_id)?;                let visible_anchor_range = snapshot.anchor_before(visible_range.start)                    ..snapshot.anchor_after(visible_range.end);                let task = project.update(cx, |project, cx| {                    project.lsp_store().update(cx, |lsp_store, cx| {                        lsp_store.resolve_visible_code_lenses(&buffer, visible_anchor_range, cx)                    })                });                Some((buffer_id, task))            })            .collect::<Vec<_>>();        if resolve_tasks.is_empty() {            return;        }         let code_lens = self.code_lens.get_or_insert_with(CodeLensState::default);        code_lens.resolve_task = cx.spawn(async move |editor, cx| {            let resolved_per_buffer = join_all(                resolve_tasks                    .into_iter()                    .map(|(buffer_id, task)| async move { (buffer_id, task.await) }),            )            .await;            editor                .update(cx, |editor, cx| {                    let snapshot = editor.buffer().read(cx).snapshot(cx);                    for (buffer_id, newly_resolved) in resolved_per_buffer {                        if newly_resolved.is_empty() {                            continue;                        }                        let Some(mut actions) = editor                            .code_lens                            .as_ref()                            .and_then(|state| state.actions.get(&buffer_id))                            .cloned()                        else {                            continue;                        };                        for resolved in newly_resolved {                            if let Some(unresolved) = actions.iter_mut().find(|action| {                                action.server_id == resolved.server_id                                    && action.range == resolved.range                            }) {                                *unresolved = resolved;                            }                        }                        editor.apply_lens_actions_for_buffer(buffer_id, actions, &snapshot, cx);                    }                })                .ok();        });    }     pub(super) fn clear_code_lenses(&mut self, cx: &mut Context<Self>) {        if let Some(code_lens) = self.code_lens.take() {            let all_blocks = code_lens                .blocks

Lines 1–500 of 1263.