Show inline previews for LSP document colors (#32816)

https://github.com/user-attachments/assets/ad0fa304-e4fb-4598-877d-c02141f35d6f

Closes https://github.com/zed-industries/zed/issues/4678

Also adds the code to support `textDocument/colorPresentation`
counterpart that serves as a resolve mechanism for the document colors.
The resolve itself is not run though, and the editor does not
accommodate color presentations in the editor yet — until a well
described use case is provided.

Use `lsp_document_colors` editor settings to alter the presentation and
turn the feature off.

Release Notes:

- Start showing inline previews for LSP document colors
This commit is contained in:
Kirill Bulatov 2025-06-17 16:46:21 +03:00 committed by GitHub
parent acb0210d26
commit f46957584f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1796 additions and 268 deletions

View file

@ -1,11 +1,11 @@
mod signature_help;
use crate::{
CodeAction, CompletionSource, CoreCompletion, CoreCompletionResponse, DocumentHighlight,
DocumentSymbol, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel,
InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
LspAction, LspPullDiagnostics, MarkupContent, PrepareRenameResponse, ProjectTransaction,
PulledDiagnostics, ResolveState,
CodeAction, CompletionSource, CoreCompletion, CoreCompletionResponse, DocumentColor,
DocumentHighlight, DocumentSymbol, Hover, HoverBlock, HoverBlockKind, InlayHint,
InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
LocationLink, LspAction, LspPullDiagnostics, MarkupContent, PrepareRenameResponse,
ProjectTransaction, PulledDiagnostics, ResolveState,
lsp_store::{LocalLspStore, LspStore},
};
use anyhow::{Context as _, Result};
@ -244,6 +244,9 @@ pub(crate) struct InlayHints {
#[derive(Debug, Copy, Clone)]
pub(crate) struct GetCodeLens;
#[derive(Debug, Copy, Clone)]
pub(crate) struct GetDocumentColor;
impl GetCodeLens {
pub(crate) fn can_resolve_lens(capabilities: &ServerCapabilities) -> bool {
capabilities
@ -4143,6 +4146,144 @@ impl LspCommand for GetDocumentDiagnostics {
}
}
#[async_trait(?Send)]
impl LspCommand for GetDocumentColor {
type Response = Vec<DocumentColor>;
type LspRequest = lsp::request::DocumentColor;
type ProtoRequest = proto::GetDocumentColor;
fn display_name(&self) -> &str {
"Document color"
}
fn check_capabilities(&self, server_capabilities: AdapterServerCapabilities) -> bool {
server_capabilities
.server_capabilities
.color_provider
.is_some()
}
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &App,
) -> Result<lsp::DocumentColorParams> {
Ok(lsp::DocumentColorParams {
text_document: make_text_document_identifier(path)?,
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
})
}
async fn response_from_lsp(
self,
message: Vec<lsp::ColorInformation>,
_: Entity<LspStore>,
_: Entity<Buffer>,
_: LanguageServerId,
_: AsyncApp,
) -> Result<Self::Response> {
Ok(message
.into_iter()
.map(|color| DocumentColor {
lsp_range: color.range,
color: color.color,
resolved: false,
color_presentations: Vec::new(),
})
.collect())
}
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
proto::GetDocumentColor {
project_id,
buffer_id: buffer.remote_id().to_proto(),
version: serialize_version(&buffer.version()),
}
}
async fn from_proto(
_: Self::ProtoRequest,
_: Entity<LspStore>,
_: Entity<Buffer>,
_: AsyncApp,
) -> Result<Self> {
Ok(Self {})
}
fn response_to_proto(
response: Self::Response,
_: &mut LspStore,
_: PeerId,
buffer_version: &clock::Global,
_: &mut App,
) -> proto::GetDocumentColorResponse {
proto::GetDocumentColorResponse {
colors: response
.into_iter()
.map(|color| {
let start = point_from_lsp(color.lsp_range.start).0;
let end = point_from_lsp(color.lsp_range.end).0;
proto::ColorInformation {
red: color.color.red,
green: color.color.green,
blue: color.color.blue,
alpha: color.color.alpha,
lsp_range_start: Some(proto::PointUtf16 {
row: start.row,
column: start.column,
}),
lsp_range_end: Some(proto::PointUtf16 {
row: end.row,
column: end.column,
}),
}
})
.collect(),
version: serialize_version(buffer_version),
}
}
async fn response_from_proto(
self,
message: proto::GetDocumentColorResponse,
_: Entity<LspStore>,
_: Entity<Buffer>,
_: AsyncApp,
) -> Result<Self::Response> {
Ok(message
.colors
.into_iter()
.filter_map(|color| {
let start = color.lsp_range_start?;
let start = PointUtf16::new(start.row, start.column);
let end = color.lsp_range_end?;
let end = PointUtf16::new(end.row, end.column);
Some(DocumentColor {
resolved: false,
color_presentations: Vec::new(),
lsp_range: lsp::Range {
start: point_to_lsp(start),
end: point_to_lsp(end),
},
color: lsp::Color {
red: color.red,
green: color.green,
blue: color.blue,
alpha: color.alpha,
},
})
})
.collect())
}
fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId> {
BufferId::new(message.buffer_id)
}
}
fn process_related_documents(
diagnostics: &mut HashMap<lsp::Url, LspPullDiagnostics>,
server_id: LanguageServerId,

View file

@ -3,9 +3,9 @@ pub mod lsp_ext_command;
pub mod rust_analyzer_ext;
use crate::{
CodeAction, Completion, CompletionResponse, CompletionSource, CoreCompletion, Hover, InlayHint,
LspAction, LspPullDiagnostics, ProjectItem, ProjectPath, ProjectTransaction, PulledDiagnostics,
ResolveState, Symbol, ToolchainStore,
CodeAction, ColorPresentation, Completion, CompletionResponse, CompletionSource,
CoreCompletion, DocumentColor, Hover, InlayHint, LspAction, LspPullDiagnostics, ProjectItem,
ProjectPath, ProjectTransaction, PulledDiagnostics, ResolveState, Symbol, ToolchainStore,
buffer_store::{BufferStore, BufferStoreEvent},
environment::ProjectEnvironment,
lsp_command::{self, *},
@ -24,6 +24,7 @@ use crate::{
use anyhow::{Context as _, Result, anyhow};
use async_trait::async_trait;
use client::{TypedEnvelope, proto};
use clock::Global;
use collections::{BTreeMap, BTreeSet, HashMap, HashSet, btree_map};
use futures::{
AsyncWriteExt, Future, FutureExt, StreamExt,
@ -48,7 +49,10 @@ use language::{
FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, language_settings,
},
point_to_lsp,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
proto::{
deserialize_anchor, deserialize_lsp_edit, deserialize_version, serialize_anchor,
serialize_lsp_edit, serialize_version,
},
range_from_lsp, range_to_lsp,
};
use lsp::{
@ -320,7 +324,7 @@ impl LocalLspStore {
if let Some(lsp_store) = this.upgrade() {
lsp_store
.update(cx, |lsp_store, cx| {
lsp_store.remove_result_ids(server_id);
lsp_store.cleanup_lsp_data(server_id);
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id))
})
.ok();
@ -3481,6 +3485,22 @@ pub struct LspStore {
_maintain_buffer_languages: Task<()>,
diagnostic_summaries:
HashMap<WorktreeId, HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>>,
lsp_data: Option<LspData>,
}
type DocumentColorTask = Shared<Task<std::result::Result<Vec<DocumentColor>, Arc<anyhow::Error>>>>;
#[derive(Debug)]
struct LspData {
mtime: MTime,
buffer_lsp_data: HashMap<LanguageServerId, HashMap<PathBuf, BufferLspData>>,
colors_update: HashMap<PathBuf, DocumentColorTask>,
last_version_queried: HashMap<PathBuf, Global>,
}
#[derive(Debug, Default)]
struct BufferLspData {
colors: Option<Vec<DocumentColor>>,
}
pub enum LspStoreEvent {
@ -3553,6 +3573,7 @@ impl LspStore {
client.add_entity_request_handler(Self::handle_inlay_hints);
client.add_entity_request_handler(Self::handle_get_project_symbols);
client.add_entity_request_handler(Self::handle_resolve_inlay_hint);
client.add_entity_request_handler(Self::handle_get_color_presentation);
client.add_entity_request_handler(Self::handle_open_buffer_for_symbol);
client.add_entity_request_handler(Self::handle_refresh_inlay_hints);
client.add_entity_request_handler(Self::handle_refresh_code_lens);
@ -3707,9 +3728,9 @@ impl LspStore {
languages: languages.clone(),
language_server_statuses: Default::default(),
nonce: StdRng::from_entropy().r#gen(),
diagnostic_summaries: Default::default(),
diagnostic_summaries: HashMap::default(),
lsp_data: None,
active_entry: None,
_maintain_workspace_config,
_maintain_buffer_languages: Self::maintain_buffer_languages(languages, cx),
}
@ -3763,7 +3784,8 @@ impl LspStore {
languages: languages.clone(),
language_server_statuses: Default::default(),
nonce: StdRng::from_entropy().r#gen(),
diagnostic_summaries: Default::default(),
diagnostic_summaries: HashMap::default(),
lsp_data: None,
active_entry: None,
toolchain_store,
_maintain_workspace_config,
@ -3890,7 +3912,7 @@ impl LspStore {
cx: &mut Context<Self>,
) {
match event {
language::BufferEvent::Edited { .. } => {
language::BufferEvent::Edited => {
self.on_buffer_edited(buffer, cx);
}
@ -4835,6 +4857,105 @@ impl LspStore {
}
}
pub fn resolve_color_presentation(
&mut self,
mut color: DocumentColor,
buffer: Entity<Buffer>,
server_id: LanguageServerId,
cx: &mut Context<Self>,
) -> Task<Result<DocumentColor>> {
if color.resolved {
return Task::ready(Ok(color));
}
if let Some((upstream_client, project_id)) = self.upstream_client() {
let start = color.lsp_range.start;
let end = color.lsp_range.end;
let request = proto::GetColorPresentation {
project_id,
server_id: server_id.to_proto(),
buffer_id: buffer.read(cx).remote_id().into(),
color: Some(proto::ColorInformation {
red: color.color.red,
green: color.color.green,
blue: color.color.blue,
alpha: color.color.alpha,
lsp_range_start: Some(proto::PointUtf16 {
row: start.line,
column: start.character,
}),
lsp_range_end: Some(proto::PointUtf16 {
row: end.line,
column: end.character,
}),
}),
};
cx.background_spawn(async move {
let response = upstream_client
.request(request)
.await
.context("color presentation proto request")?;
color.resolved = true;
color.color_presentations = response
.presentations
.into_iter()
.map(|presentation| ColorPresentation {
label: presentation.label,
text_edit: presentation.text_edit.and_then(deserialize_lsp_edit),
additional_text_edits: presentation
.additional_text_edits
.into_iter()
.filter_map(deserialize_lsp_edit)
.collect(),
})
.collect();
Ok(color)
})
} else {
let path = match buffer
.update(cx, |buffer, cx| {
Some(crate::File::from_dyn(buffer.file())?.abs_path(cx))
})
.context("buffer with the missing path")
{
Ok(path) => path,
Err(e) => return Task::ready(Err(e)),
};
let Some(lang_server) = buffer.update(cx, |buffer, cx| {
self.language_server_for_local_buffer(buffer, server_id, cx)
.map(|(_, server)| server.clone())
}) else {
return Task::ready(Ok(color));
};
cx.background_spawn(async move {
let resolve_task = lang_server.request::<lsp::request::ColorPresentationRequest>(
lsp::ColorPresentationParams {
text_document: make_text_document_identifier(&path)?,
color: color.color,
range: color.lsp_range,
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
},
);
color.color_presentations = resolve_task
.await
.into_response()
.context("color presentation resolve LSP request")?
.into_iter()
.map(|presentation| ColorPresentation {
label: presentation.label,
text_edit: presentation.text_edit,
additional_text_edits: presentation
.additional_text_edits
.unwrap_or_default(),
})
.collect();
color.resolved = true;
Ok(color)
})
}
}
pub(crate) fn linked_edit(
&mut self,
buffer: &Entity<Buffer>,
@ -5063,7 +5184,13 @@ impl LspStore {
},
cx,
);
cx.spawn(async move |_, _| Ok(all_actions_task.await.into_iter().flatten().collect()))
cx.spawn(async move |_, _| {
Ok(all_actions_task
.await
.into_iter()
.flat_map(|(_, actions)| actions)
.collect())
})
}
}
@ -5123,7 +5250,13 @@ impl LspStore {
} else {
let code_lens_task =
self.request_multiple_lsp_locally(buffer_handle, None::<usize>, GetCodeLens, cx);
cx.spawn(async move |_, _| Ok(code_lens_task.await.into_iter().flatten().collect()))
cx.spawn(async move |_, _| {
Ok(code_lens_task
.await
.into_iter()
.flat_map(|(_, code_lens)| code_lens)
.collect())
})
}
}
@ -5870,6 +6003,293 @@ impl LspStore {
}
}
pub fn pull_diagnostics_for_buffer(
&mut self,
buffer: Entity<Buffer>,
cx: &mut Context<Self>,
) -> Task<anyhow::Result<()>> {
let diagnostics = self.pull_diagnostics(buffer, cx);
cx.spawn(async move |lsp_store, cx| {
let diagnostics = diagnostics.await.context("pulling diagnostics")?;
lsp_store.update(cx, |lsp_store, cx| {
for diagnostics_set in diagnostics {
let LspPullDiagnostics::Response {
server_id,
uri,
diagnostics,
} = diagnostics_set
else {
continue;
};
let adapter = lsp_store.language_server_adapter_for_id(server_id);
let disk_based_sources = adapter
.as_ref()
.map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
.unwrap_or(&[]);
match diagnostics {
PulledDiagnostics::Unchanged { result_id } => {
lsp_store
.merge_diagnostics(
server_id,
lsp::PublishDiagnosticsParams {
uri: uri.clone(),
diagnostics: Vec::new(),
version: None,
},
Some(result_id),
DiagnosticSourceKind::Pulled,
disk_based_sources,
|_, _| true,
cx,
)
.log_err();
}
PulledDiagnostics::Changed {
diagnostics,
result_id,
} => {
lsp_store
.merge_diagnostics(
server_id,
lsp::PublishDiagnosticsParams {
uri: uri.clone(),
diagnostics,
version: None,
},
result_id,
DiagnosticSourceKind::Pulled,
disk_based_sources,
|old_diagnostic, _| match old_diagnostic.source_kind {
DiagnosticSourceKind::Pulled => false,
DiagnosticSourceKind::Other
| DiagnosticSourceKind::Pushed => true,
},
cx,
)
.log_err();
}
}
}
})
})
}
pub fn document_colors(
&mut self,
update_on_edit: bool,
for_server_id: Option<LanguageServerId>,
buffer: Entity<Buffer>,
cx: &mut Context<Self>,
) -> Option<DocumentColorTask> {
let buffer_mtime = buffer.read(cx).saved_mtime()?;
let abs_path = crate::File::from_dyn(buffer.read(cx).file())?.abs_path(cx);
let buffer_version = buffer.read(cx).version();
let ignore_existing_mtime = update_on_edit
&& self.lsp_data.as_ref().is_none_or(|lsp_data| {
lsp_data.last_version_queried.get(&abs_path) != Some(&buffer_version)
});
let mut has_other_versions = false;
let mut received_colors_data = false;
let mut outdated_lsp_data = false;
let buffer_lsp_data = self
.lsp_data
.as_ref()
.into_iter()
.filter(|lsp_data| {
if ignore_existing_mtime {
return false;
}
has_other_versions |= lsp_data.mtime != buffer_mtime;
lsp_data.mtime == buffer_mtime
})
.flat_map(|lsp_data| lsp_data.buffer_lsp_data.values())
.filter_map(|buffer_data| buffer_data.get(&abs_path))
.filter_map(|buffer_data| {
let colors = buffer_data.colors.as_deref()?;
received_colors_data = true;
Some(colors)
})
.flatten()
.cloned()
.collect::<Vec<_>>();
if buffer_lsp_data.is_empty() || for_server_id.is_some() {
if received_colors_data && for_server_id.is_none() {
return None;
} else if has_other_versions && !ignore_existing_mtime {
return None;
}
if ignore_existing_mtime
|| self.lsp_data.is_none()
|| self
.lsp_data
.as_ref()
.is_some_and(|lsp_data| buffer_mtime != lsp_data.mtime)
{
self.lsp_data = Some(LspData {
mtime: buffer_mtime,
buffer_lsp_data: HashMap::default(),
colors_update: HashMap::default(),
last_version_queried: HashMap::default(),
});
outdated_lsp_data = true;
}
{
let lsp_data = self.lsp_data.as_mut()?;
match for_server_id {
Some(for_server_id) if !outdated_lsp_data => {
lsp_data.buffer_lsp_data.remove(&for_server_id);
}
None | Some(_) => {
let existing_task = lsp_data.colors_update.get(&abs_path).cloned();
if !outdated_lsp_data && existing_task.is_some() {
return existing_task;
}
for buffer_data in lsp_data.buffer_lsp_data.values_mut() {
if let Some(buffer_data) = buffer_data.get_mut(&abs_path) {
buffer_data.colors = None;
}
}
}
}
}
let task_abs_path = abs_path.clone();
let new_task = cx
.spawn(async move |lsp_store, cx| {
cx.background_executor().timer(Duration::from_millis(50)).await;
let fetched_colors = match lsp_store
.update(cx, |lsp_store, cx| {
lsp_store.fetch_document_colors(buffer, cx)
}) {
Ok(fetch_task) => fetch_task.await
.with_context(|| {
format!(
"Fetching document colors for buffer with path {task_abs_path:?}"
)
}),
Err(e) => return Err(Arc::new(e)),
};
let fetched_colors = match fetched_colors {
Ok(fetched_colors) => fetched_colors,
Err(e) => return Err(Arc::new(e)),
};
let lsp_colors = lsp_store.update(cx, |lsp_store, _| {
let lsp_data = lsp_store.lsp_data.as_mut().with_context(|| format!(
"Document lsp data got updated between fetch and update for path {task_abs_path:?}"
))?;
let mut lsp_colors = Vec::new();
anyhow::ensure!(lsp_data.mtime == buffer_mtime, "Buffer lsp data got updated between fetch and update for path {task_abs_path:?}");
for (server_id, colors) in fetched_colors {
let colors_lsp_data = &mut lsp_data.buffer_lsp_data.entry(server_id).or_default().entry(task_abs_path.clone()).or_default().colors;
*colors_lsp_data = Some(colors.clone());
lsp_colors.extend(colors);
}
Ok(lsp_colors)
});
match lsp_colors {
Ok(Ok(lsp_colors)) => Ok(lsp_colors),
Ok(Err(e)) => Err(Arc::new(e)),
Err(e) => Err(Arc::new(e)),
}
})
.shared();
let lsp_data = self.lsp_data.as_mut()?;
lsp_data
.colors_update
.insert(abs_path.clone(), new_task.clone());
lsp_data
.last_version_queried
.insert(abs_path, buffer_version);
Some(new_task)
} else {
Some(Task::ready(Ok(buffer_lsp_data)).shared())
}
}
fn fetch_document_colors(
&mut self,
buffer: Entity<Buffer>,
cx: &mut Context<Self>,
) -> Task<anyhow::Result<Vec<(LanguageServerId, Vec<DocumentColor>)>>> {
if let Some((client, project_id)) = self.upstream_client() {
let request_task = client.request(proto::MultiLspQuery {
project_id,
buffer_id: buffer.read(cx).remote_id().to_proto(),
version: serialize_version(&buffer.read(cx).version()),
strategy: Some(proto::multi_lsp_query::Strategy::All(
proto::AllLanguageServers {},
)),
request: Some(proto::multi_lsp_query::Request::GetDocumentColor(
GetDocumentColor {}.to_proto(project_id, buffer.read(cx)),
)),
});
cx.spawn(async move |project, cx| {
let Some(project) = project.upgrade() else {
return Ok(Vec::new());
};
let colors = join_all(
request_task
.await
.log_err()
.map(|response| response.responses)
.unwrap_or_default()
.into_iter()
.filter_map(|lsp_response| match lsp_response.response? {
proto::lsp_response::Response::GetDocumentColorResponse(response) => {
Some((
LanguageServerId::from_proto(lsp_response.server_id),
response,
))
}
unexpected => {
debug_panic!("Unexpected response: {unexpected:?}");
None
}
})
.map(|(server_id, color_response)| {
let response = GetDocumentColor {}.response_from_proto(
color_response,
project.clone(),
buffer.clone(),
cx.clone(),
);
async move { (server_id, response.await.log_err().unwrap_or_default()) }
}),
)
.await
.into_iter()
.fold(HashMap::default(), |mut acc, (server_id, colors)| {
acc.entry(server_id).or_insert_with(Vec::new).extend(colors);
acc
})
.into_iter()
.collect();
Ok(colors)
})
} else {
let document_colors_task =
self.request_multiple_lsp_locally(&buffer, None::<usize>, GetDocumentColor, cx);
cx.spawn(async move |_, _| {
Ok(document_colors_task
.await
.into_iter()
.fold(HashMap::default(), |mut acc, (server_id, colors)| {
acc.entry(server_id).or_insert_with(Vec::new).extend(colors);
acc
})
.into_iter()
.collect())
})
}
}
pub fn signature_help<T: ToPointUtf16>(
&mut self,
buffer: &Entity<Buffer>,
@ -5937,7 +6357,7 @@ impl LspStore {
all_actions_task
.await
.into_iter()
.flatten()
.flat_map(|(_, actions)| actions)
.filter(|help| !help.label.is_empty())
.collect::<Vec<_>>()
})
@ -6015,7 +6435,7 @@ impl LspStore {
all_actions_task
.await
.into_iter()
.filter_map(|hover| remove_empty_hover_blocks(hover?))
.filter_map(|(_, hover)| remove_empty_hover_blocks(hover?))
.collect::<Vec<Hover>>()
})
}
@ -6948,7 +7368,7 @@ impl LspStore {
position: Option<P>,
request: R,
cx: &mut Context<Self>,
) -> Task<Vec<R::Response>>
) -> Task<Vec<(LanguageServerId, R::Response)>>
where
P: ToOffset,
R: LspCommand + Clone,
@ -6978,20 +7398,21 @@ impl LspStore {
let mut response_results = server_ids
.into_iter()
.map(|server_id| {
self.request_lsp(
let task = self.request_lsp(
buffer.clone(),
LanguageServerToQuery::Other(server_id),
request.clone(),
cx,
)
);
async move { (server_id, task.await) }
})
.collect::<FuturesUnordered<_>>();
cx.spawn(async move |_, _| {
let mut responses = Vec::with_capacity(response_results.len());
while let Some(response_result) = response_results.next().await {
while let Some((server_id, response_result)) = response_results.next().await {
if let Some(response) = response_result.log_err() {
responses.push(response);
responses.push((server_id, response));
}
}
responses
@ -7079,9 +7500,14 @@ impl LspStore {
}
}
match envelope.payload.request {
Some(proto::multi_lsp_query::Request::GetHover(get_hover)) => {
Some(proto::multi_lsp_query::Request::GetHover(message)) => {
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version))
})?
.await?;
let get_hover =
GetHover::from_proto(get_hover, lsp_store.clone(), buffer.clone(), cx.clone())
GetHover::from_proto(message, lsp_store.clone(), buffer.clone(), cx.clone())
.await?;
let all_hovers = lsp_store
.update(&mut cx, |this, cx| {
@ -7094,10 +7520,13 @@ impl LspStore {
})?
.await
.into_iter()
.filter_map(|hover| remove_empty_hover_blocks(hover?));
.filter_map(|(server_id, hover)| {
Some((server_id, remove_empty_hover_blocks(hover?)?))
});
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: all_hovers
.map(|hover| proto::LspResponse {
.map(|(server_id, hover)| proto::LspResponse {
server_id: server_id.to_proto(),
response: Some(proto::lsp_response::Response::GetHoverResponse(
GetHover::response_to_proto(
Some(hover),
@ -7111,9 +7540,14 @@ impl LspStore {
.collect(),
})
}
Some(proto::multi_lsp_query::Request::GetCodeActions(get_code_actions)) => {
Some(proto::multi_lsp_query::Request::GetCodeActions(message)) => {
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version))
})?
.await?;
let get_code_actions = GetCodeActions::from_proto(
get_code_actions,
message,
lsp_store.clone(),
buffer.clone(),
cx.clone(),
@ -7134,7 +7568,8 @@ impl LspStore {
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: all_actions
.map(|code_actions| proto::LspResponse {
.map(|(server_id, code_actions)| proto::LspResponse {
server_id: server_id.to_proto(),
response: Some(proto::lsp_response::Response::GetCodeActionsResponse(
GetCodeActions::response_to_proto(
code_actions,
@ -7148,9 +7583,14 @@ impl LspStore {
.collect(),
})
}
Some(proto::multi_lsp_query::Request::GetSignatureHelp(get_signature_help)) => {
Some(proto::multi_lsp_query::Request::GetSignatureHelp(message)) => {
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version))
})?
.await?;
let get_signature_help = GetSignatureHelp::from_proto(
get_signature_help,
message,
lsp_store.clone(),
buffer.clone(),
cx.clone(),
@ -7171,7 +7611,8 @@ impl LspStore {
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: all_signatures
.map(|signature_help| proto::LspResponse {
.map(|(server_id, signature_help)| proto::LspResponse {
server_id: server_id.to_proto(),
response: Some(
proto::lsp_response::Response::GetSignatureHelpResponse(
GetSignatureHelp::response_to_proto(
@ -7187,14 +7628,15 @@ impl LspStore {
.collect(),
})
}
Some(proto::multi_lsp_query::Request::GetCodeLens(get_code_lens)) => {
let get_code_lens = GetCodeLens::from_proto(
get_code_lens,
lsp_store.clone(),
buffer.clone(),
cx.clone(),
)
.await?;
Some(proto::multi_lsp_query::Request::GetCodeLens(message)) => {
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version))
})?
.await?;
let get_code_lens =
GetCodeLens::from_proto(message, lsp_store.clone(), buffer.clone(), cx.clone())
.await?;
let code_lens_actions = lsp_store
.update(&mut cx, |project, cx| {
@ -7210,7 +7652,8 @@ impl LspStore {
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: code_lens_actions
.map(|actions| proto::LspResponse {
.map(|(server_id, actions)| proto::LspResponse {
server_id: server_id.to_proto(),
response: Some(proto::lsp_response::Response::GetCodeLensResponse(
GetCodeLens::response_to_proto(
actions,
@ -7242,29 +7685,30 @@ impl LspStore {
.into_iter()
.map(|server_id| {
let result_id = lsp_store.result_id(server_id, buffer_id, cx);
lsp_store.request_lsp(
let task = lsp_store.request_lsp(
buffer.clone(),
LanguageServerToQuery::Other(server_id),
GetDocumentDiagnostics {
previous_result_id: result_id,
},
cx,
)
);
async move { (server_id, task.await) }
})
.collect::<Vec<_>>()
})?;
let all_diagnostics_responses = join_all(pull_diagnostics).await;
let mut all_diagnostics = Vec::new();
for response in all_diagnostics_responses {
let response = response?;
all_diagnostics.push(response);
for (server_id, response) in all_diagnostics_responses {
all_diagnostics.push((server_id, response?));
}
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: all_diagnostics
.into_iter()
.map(|lsp_diagnostic| proto::LspResponse {
.map(|(server_id, lsp_diagnostic)| proto::LspResponse {
server_id: server_id.to_proto(),
response: Some(
proto::lsp_response::Response::GetDocumentDiagnosticsResponse(
GetDocumentDiagnostics::response_to_proto(
@ -7280,6 +7724,51 @@ impl LspStore {
.collect(),
})
}
Some(proto::multi_lsp_query::Request::GetDocumentColor(message)) => {
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version))
})?
.await?;
let get_document_color = GetDocumentColor::from_proto(
message,
lsp_store.clone(),
buffer.clone(),
cx.clone(),
)
.await?;
let all_colors = lsp_store
.update(&mut cx, |project, cx| {
project.request_multiple_lsp_locally(
&buffer,
None::<usize>,
get_document_color,
cx,
)
})?
.await
.into_iter();
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: all_colors
.map(|(server_id, colors)| proto::LspResponse {
server_id: server_id.to_proto(),
response: Some(
proto::lsp_response::Response::GetDocumentColorResponse(
GetDocumentColor::response_to_proto(
colors,
project,
sender_id,
&buffer_version,
cx,
),
),
),
})
.collect(),
})
}
None => anyhow::bail!("empty multi lsp query request"),
}
}
@ -8263,6 +8752,70 @@ impl LspStore {
})
}
async fn handle_get_color_presentation(
lsp_store: Entity<Self>,
envelope: TypedEnvelope<proto::GetColorPresentation>,
mut cx: AsyncApp,
) -> Result<proto::GetColorPresentationResponse> {
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
let buffer = lsp_store.update(&mut cx, |lsp_store, cx| {
lsp_store.buffer_store.read(cx).get_existing(buffer_id)
})??;
let color = envelope
.payload
.color
.context("invalid color resolve request")?;
let start = color
.lsp_range_start
.context("invalid color resolve request")?;
let end = color
.lsp_range_end
.context("invalid color resolve request")?;
let color = DocumentColor {
lsp_range: lsp::Range {
start: point_to_lsp(PointUtf16::new(start.row, start.column)),
end: point_to_lsp(PointUtf16::new(end.row, end.column)),
},
color: lsp::Color {
red: color.red,
green: color.green,
blue: color.blue,
alpha: color.alpha,
},
resolved: false,
color_presentations: Vec::new(),
};
let resolved_color = lsp_store
.update(&mut cx, |lsp_store, cx| {
lsp_store.resolve_color_presentation(
color,
buffer.clone(),
LanguageServerId(envelope.payload.server_id as usize),
cx,
)
})?
.await
.context("resolving color presentation")?;
Ok(proto::GetColorPresentationResponse {
presentations: resolved_color
.color_presentations
.into_iter()
.map(|presentation| proto::ColorPresentation {
label: presentation.label,
text_edit: presentation.text_edit.map(serialize_lsp_edit),
additional_text_edits: presentation
.additional_text_edits
.into_iter()
.map(serialize_lsp_edit)
.collect(),
})
.collect(),
})
}
async fn handle_resolve_inlay_hint(
this: Entity<Self>,
envelope: TypedEnvelope<proto::ResolveInlayHint>,
@ -8829,7 +9382,7 @@ impl LspStore {
local.language_server_watched_paths.remove(&server_id);
let server_state = local.language_servers.remove(&server_id);
cx.notify();
self.remove_result_ids(server_id);
self.cleanup_lsp_data(server_id);
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id));
cx.spawn(async move |_, cx| {
Self::shutdown_language_server(server_state, name, cx).await;
@ -9718,7 +10271,10 @@ impl LspStore {
}
}
fn remove_result_ids(&mut self, for_server: LanguageServerId) {
fn cleanup_lsp_data(&mut self, for_server: LanguageServerId) {
if let Some(lsp_data) = &mut self.lsp_data {
lsp_data.buffer_lsp_data.remove(&for_server);
}
if let Some(local) = self.as_local_mut() {
local.buffer_pull_diagnostics_result_ids.remove(&for_server);
}

View file

@ -768,6 +768,21 @@ pub struct DirectoryItem {
pub is_dir: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DocumentColor {
pub lsp_range: lsp::Range,
pub color: lsp::Color,
pub resolved: bool,
pub color_presentations: Vec<ColorPresentation>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ColorPresentation {
pub label: String,
pub text_edit: Option<lsp::TextEdit>,
pub additional_text_edits: Vec<lsp::TextEdit>,
}
#[derive(Clone)]
pub enum DirectoryLister {
Project(Entity<Project>),
@ -3721,16 +3736,6 @@ impl Project {
})
}
pub fn document_diagnostics(
&mut self,
buffer_handle: Entity<Buffer>,
cx: &mut Context<Self>,
) -> Task<Result<Vec<LspPullDiagnostics>>> {
self.lsp_store.update(cx, |lsp_store, cx| {
lsp_store.pull_diagnostics(buffer_handle, cx)
})
}
pub fn update_diagnostics(
&mut self,
language_server_id: LanguageServerId,