1673 lines · 60.0 KB · Rust
RawDownload
use std::sync::Arc; use collections::{HashMap, HashSet};use futures::{StreamExt as _, future::join_all, stream::FuturesUnordered};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, lsp_store::code_lens::CodeLensActions};use task::TaskContext;use text::ToOffset as _; 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,}; static EMPTY_LENS_FALLBACK_TITLE: SharedString = SharedString::new_static("0 references");const CODE_LENS_SEPARATOR: &str = " | "; #[derive(Clone, Debug)]struct CodeLensLine {    position: Anchor,    indent_column: u32,    items: Vec<CodeLensItem>,} #[derive(Clone, Debug)]struct CodeLensItem {    title: Option<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, CodeLensActions>,    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::LspLocation(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_per_buffer) = 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 code_lens_per_buffer = join_all(tasks_per_buffer).await;            if code_lens_per_buffer.is_empty() {                return;            }             editor                .update(cx, |editor, cx| {                    let snapshot = editor.buffer().read(cx).snapshot(cx);                    for (buffer_id, result) in code_lens_per_buffer {                        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();        });    }     /// Reconcile blocks for `buffer_id` against the latest `actions`.    ///    /// Lenses without a command cannot be rendered, as it's the only textual data in the [`lsp::CodeLens`].    /// Worst case, we can know it only after asynchronously issuing a resolve request to the server.    /// To avoid flickering during typing, keep a placeholder block for each lens and replace it with a resolved command's block when available,    /// or with a synthetic "0 references" title if the server resolve did not return a command.    ///    /// Also keep the old block until the fresh resolve lands, to avoid flickering during typing.    fn apply_lens_actions_for_buffer(        &mut self,        buffer_id: BufferId,        actions: CodeLensActions,        snapshot: &MultiBufferSnapshot,        cx: &mut Context<Self>,    ) {        let mut all_lenses = Vec::new();        for (_, action) in actions.iter().sorted_by_key(|(id, _)| **id) {            let Some(position) = snapshot.anchor_in_excerpt(action.range.start) else {                continue;            };            if let project::LspAction::CodeLens(lens) = &action.lsp_action {                let title = lens                    .command                    .as_ref()                    .filter(|cmd| !cmd.title.is_empty())                    .map(|cmd| SharedString::from(&cmd.title));                all_lenses.push((                    position,                    CodeLensItem {                        title,                        action: action.clone(),                    },                ));            }        }         let mut new_lines_by_row = group_lenses_by_row(all_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);            let Some(new_line) = new_lines_by_row.remove(&row) else {                blocks_to_remove.insert(old.block_id);                continue;            };            covered_rows.insert(row);            let new_all_pending = new_line                .items                .iter()                .all(|item| item.title.is_none() && !item.action.resolved);            let old_has_rendered = old                .line                .items                .iter()                .any(|item| displayed_title(item).is_some());            if new_all_pending && old_has_rendered {                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 lsp_store = project.read(cx).lsp_store();         let mut pending_resolves = Vec::new();        for (buffer_snapshot, visible_range, _) in self.visible_buffer_ranges(cx) {            let buffer_id = buffer_snapshot.remote_id();            let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {                continue;            };            let Some(actions) = self                .code_lens                .as_ref()                .and_then(|state| state.actions.get(&buffer_id))            else {                continue;            };            for (lens_id, action) in actions {                if action.resolved {                    continue;                }                if let project::LspAction::CodeLens(lens) = &action.lsp_action {                    if lens.command.is_some() {                        continue;                    }                }                let action_offset = action.range.start.to_offset(&buffer_snapshot);                if action_offset < visible_range.start.0 || action_offset > visible_range.end.0 {                    continue;                }                let resolve_task = lsp_store.update(cx, |lsp_store, cx| {                    lsp_store.resolve_code_lens(&buffer, action.server_id, *lens_id, cx)                });                pending_resolves.push((buffer_id, resolve_task));            }        }        if pending_resolves.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 mut resolves_in_progress = pending_resolves                .into_iter()                .map(|(buffer_id, task)| async move { (buffer_id, task.await) })                .collect::<FuturesUnordered<_>>();            while let Some((buffer_id, resolve_result)) = resolves_in_progress.next().await {                let Some((resolved_id, resolved)) = resolve_result else {                    continue;                };                editor                    .update(cx, |editor, cx| {                        let snapshot = editor.buffer().read(cx).snapshot(cx);                        let Some(mut actions) = editor                            .code_lens                            .as_ref()                            .and_then(|state| state.actions.get(&buffer_id))                            .cloned()                        else {                            return;                        };                        if let Some(slot) = actions.get_mut(&resolved_id) {                            *slot = resolved;                        }                        editor.apply_lens_actions_for_buffer(buffer_id, actions, &snapshot, cx);                    })

Lines 1–500 of 1673.