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::Flex, 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 .blocksLines 1–500 of 1263. Load more
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::Flex, 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