Store result_ids per language server (#32631)

Follow-up of https://github.com/zed-industries/zed/pull/32403


Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov 2025-06-12 22:36:08 +03:00 committed by GitHub
parent 1e244f4aff
commit e56a027bea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 186 additions and 151 deletions

View file

@ -21942,6 +21942,7 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
.downcast::<Editor>() .downcast::<Editor>()
.unwrap(); .unwrap();
let fake_server = fake_servers.next().await.unwrap(); let fake_server = fake_servers.next().await.unwrap();
let server_id = fake_server.server.server_id();
let mut first_request = fake_server let mut first_request = fake_server
.set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| { .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1; let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
@ -21973,7 +21974,10 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
.expect("created a singleton buffer") .expect("created a singleton buffer")
.read(cx) .read(cx)
.remote_id(); .remote_id();
let buffer_result_id = project.lsp_store().read(cx).result_id(buffer_id, cx); let buffer_result_id = project
.lsp_store()
.read(cx)
.result_id(server_id, buffer_id, cx);
assert_eq!(expected, buffer_result_id); assert_eq!(expected, buffer_result_id);
}); });
}; };

View file

@ -3668,6 +3668,39 @@ impl LspCommand for LinkedEditingRange {
} }
impl GetDocumentDiagnostics { impl GetDocumentDiagnostics {
pub fn diagnostics_from_proto(
response: proto::GetDocumentDiagnosticsResponse,
) -> Vec<LspPullDiagnostics> {
response
.pulled_diagnostics
.into_iter()
.filter_map(|diagnostics| {
Some(LspPullDiagnostics::Response {
server_id: LanguageServerId::from_proto(diagnostics.server_id),
uri: lsp::Url::from_str(diagnostics.uri.as_str()).log_err()?,
diagnostics: if diagnostics.changed {
PulledDiagnostics::Unchanged {
result_id: diagnostics.result_id?,
}
} else {
PulledDiagnostics::Changed {
result_id: diagnostics.result_id,
diagnostics: diagnostics
.diagnostics
.into_iter()
.filter_map(|diagnostic| {
GetDocumentDiagnostics::deserialize_lsp_diagnostic(diagnostic)
.context("deserializing diagnostics")
.log_err()
})
.collect(),
}
},
})
})
.collect()
}
fn deserialize_lsp_diagnostic(diagnostic: proto::LspDiagnostic) -> Result<lsp::Diagnostic> { fn deserialize_lsp_diagnostic(diagnostic: proto::LspDiagnostic) -> Result<lsp::Diagnostic> {
let start = diagnostic.start.context("invalid start range")?; let start = diagnostic.start.context("invalid start range")?;
let end = diagnostic.end.context("invalid end range")?; let end = diagnostic.end.context("invalid end range")?;
@ -4037,21 +4070,14 @@ impl LspCommand for GetDocumentDiagnostics {
} }
async fn from_proto( async fn from_proto(
message: proto::GetDocumentDiagnostics, _: proto::GetDocumentDiagnostics,
lsp_store: Entity<LspStore>, _: Entity<LspStore>,
buffer: Entity<Buffer>, _: Entity<Buffer>,
mut cx: AsyncApp, _: AsyncApp,
) -> Result<Self> { ) -> Result<Self> {
buffer anyhow::bail!(
.update(&mut cx, |buffer, _| { "proto::GetDocumentDiagnostics is not expected to be converted from proto directly, as it needs `previous_result_id` fetched first"
buffer.wait_for_version(deserialize_version(&message.version)) )
})?
.await?;
let buffer_id = buffer.update(&mut cx, |buffer, _| buffer.remote_id())?;
Ok(Self {
previous_result_id: lsp_store
.update(&mut cx, |lsp_store, cx| lsp_store.result_id(buffer_id, cx))?,
})
} }
fn response_to_proto( fn response_to_proto(
@ -4109,36 +4135,7 @@ impl LspCommand for GetDocumentDiagnostics {
_: Entity<Buffer>, _: Entity<Buffer>,
_: AsyncApp, _: AsyncApp,
) -> Result<Self::Response> { ) -> Result<Self::Response> {
let pulled_diagnostics = response Ok(Self::diagnostics_from_proto(response))
.pulled_diagnostics
.into_iter()
.filter_map(|diagnostics| {
Some(LspPullDiagnostics::Response {
server_id: LanguageServerId::from_proto(diagnostics.server_id),
uri: lsp::Url::from_str(diagnostics.uri.as_str()).log_err()?,
diagnostics: if diagnostics.changed {
PulledDiagnostics::Unchanged {
result_id: diagnostics.result_id?,
}
} else {
PulledDiagnostics::Changed {
result_id: diagnostics.result_id,
diagnostics: diagnostics
.diagnostics
.into_iter()
.filter_map(|diagnostic| {
GetDocumentDiagnostics::deserialize_lsp_diagnostic(diagnostic)
.context("deserializing diagnostics")
.log_err()
})
.collect(),
}
},
})
})
.collect();
Ok(pulled_diagnostics)
} }
fn buffer_id_from_proto(message: &proto::GetDocumentDiagnostics) -> Result<BufferId> { fn buffer_id_from_proto(message: &proto::GetDocumentDiagnostics) -> Result<BufferId> {

View file

@ -166,7 +166,7 @@ pub struct LocalLspStore {
_subscription: gpui::Subscription, _subscription: gpui::Subscription,
lsp_tree: Entity<LanguageServerTree>, lsp_tree: Entity<LanguageServerTree>,
registered_buffers: HashMap<BufferId, usize>, registered_buffers: HashMap<BufferId, usize>,
buffer_pull_diagnostics_result_ids: HashMap<PathBuf, Option<String>>, buffer_pull_diagnostics_result_ids: HashMap<PathBuf, HashMap<LanguageServerId, Option<String>>>,
} }
impl LocalLspStore { impl LocalLspStore {
@ -316,11 +316,13 @@ impl LocalLspStore {
})? })?
.await .await
.inspect_err(|_| { .inspect_err(|_| {
if let Some(this) = this.upgrade() { if let Some(lsp_store) = this.upgrade() {
this.update(cx, |_, cx| { lsp_store
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id)) .update(cx, |lsp_store, cx| {
}) lsp_store.remove_result_ids(server_id);
.ok(); cx.emit(LspStoreEvent::LanguageServerRemoved(server_id))
})
.ok();
} }
})?; })?;
@ -2297,7 +2299,9 @@ impl LocalLspStore {
buffer.update(cx, |buffer, cx| { buffer.update(cx, |buffer, cx| {
if let Some(abs_path) = File::from_dyn(buffer.file()).map(|f| f.abs_path(cx)) { if let Some(abs_path) = File::from_dyn(buffer.file()).map(|f| f.abs_path(cx)) {
self.buffer_pull_diagnostics_result_ids self.buffer_pull_diagnostics_result_ids
.insert(abs_path, result_id); .entry(abs_path)
.or_default()
.insert(server_id, result_id);
} }
buffer.update_diagnostics(server_id, set, cx) buffer.update_diagnostics(server_id, set, cx)
@ -3134,12 +3138,15 @@ impl LocalLspStore {
server_ids.remove(server_id_to_remove); server_ids.remove(server_id_to_remove);
}); });
self.language_server_watched_paths self.language_server_watched_paths
.remove(&server_id_to_remove); .remove(server_id_to_remove);
self.language_server_paths_watched_for_rename self.language_server_paths_watched_for_rename
.remove(&server_id_to_remove); .remove(server_id_to_remove);
self.last_workspace_edits_by_language_server self.last_workspace_edits_by_language_server
.remove(&server_id_to_remove); .remove(server_id_to_remove);
self.language_servers.remove(&server_id_to_remove); self.language_servers.remove(server_id_to_remove);
for values_per_server in self.buffer_pull_diagnostics_result_ids.values_mut() {
values_per_server.remove(server_id_to_remove);
}
cx.emit(LspStoreEvent::LanguageServerRemoved(*server_id_to_remove)); cx.emit(LspStoreEvent::LanguageServerRemoved(*server_id_to_remove));
} }
servers_to_remove.into_keys().collect() servers_to_remove.into_keys().collect()
@ -5757,72 +5764,68 @@ impl LspStore {
) -> Task<Result<Vec<LspPullDiagnostics>>> { ) -> Task<Result<Vec<LspPullDiagnostics>>> {
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
let buffer_id = buffer.remote_id(); let buffer_id = buffer.remote_id();
let result_id = self.result_id(buffer_id, cx);
if let Some((client, upstream_project_id)) = self.upstream_client() { if let Some((client, upstream_project_id)) = self.upstream_client() {
let request_task = client.request(proto::MultiLspQuery { let request_task = client.request(proto::MultiLspQuery {
buffer_id: buffer_id.into(), buffer_id: buffer_id.to_proto(),
version: serialize_version(&buffer_handle.read(cx).version()), version: serialize_version(&buffer_handle.read(cx).version()),
project_id: upstream_project_id, project_id: upstream_project_id,
strategy: Some(proto::multi_lsp_query::Strategy::All( strategy: Some(proto::multi_lsp_query::Strategy::All(
proto::AllLanguageServers {}, proto::AllLanguageServers {},
)), )),
request: Some(proto::multi_lsp_query::Request::GetDocumentDiagnostics( request: Some(proto::multi_lsp_query::Request::GetDocumentDiagnostics(
GetDocumentDiagnostics { proto::GetDocumentDiagnostics {
previous_result_id: result_id.clone(), project_id: upstream_project_id,
} buffer_id: buffer_id.to_proto(),
.to_proto(upstream_project_id, buffer_handle.read(cx)), version: serialize_version(&buffer_handle.read(cx).version()),
},
)), )),
}); });
let buffer = buffer_handle.clone(); cx.background_spawn(async move {
cx.spawn(async move |weak_project, cx| { Ok(request_task
let Some(project) = weak_project.upgrade() else { .await?
return Ok(Vec::new()); .responses
};
let responses = request_task.await?.responses;
let diagnostics = join_all(
responses
.into_iter()
.filter_map(|lsp_response| match lsp_response.response? {
proto::lsp_response::Response::GetDocumentDiagnosticsResponse(
response,
) => Some(response),
unexpected => {
debug_panic!("Unexpected response: {unexpected:?}");
None
}
})
.map(|diagnostics_response| {
GetDocumentDiagnostics {
previous_result_id: result_id.clone(),
}
.response_from_proto(
diagnostics_response,
project.clone(),
buffer.clone(),
cx.clone(),
)
}),
)
.await;
Ok(diagnostics
.into_iter() .into_iter()
.collect::<Result<Vec<_>>>()? .filter_map(|lsp_response| match lsp_response.response? {
.into_iter() proto::lsp_response::Response::GetDocumentDiagnosticsResponse(response) => {
.flatten() Some(response)
}
unexpected => {
debug_panic!("Unexpected response: {unexpected:?}");
None
}
})
.flat_map(GetDocumentDiagnostics::diagnostics_from_proto)
.collect()) .collect())
}) })
} else { } else {
let all_actions_task = self.request_multiple_lsp_locally( let server_ids = buffer_handle.update(cx, |buffer, cx| {
&buffer_handle, self.language_servers_for_local_buffer(buffer, cx)
None::<PointUtf16>, .map(|(_, server)| server.server_id())
GetDocumentDiagnostics { .collect::<Vec<_>>()
previous_result_id: result_id, });
}, let pull_diagnostics = server_ids
cx, .into_iter()
); .map(|server_id| {
cx.spawn(async move |_, _| Ok(all_actions_task.await.into_iter().flatten().collect())) let result_id = self.result_id(server_id, buffer_id, cx);
self.request_lsp(
buffer_handle.clone(),
LanguageServerToQuery::Other(server_id),
GetDocumentDiagnostics {
previous_result_id: result_id,
},
cx,
)
})
.collect::<Vec<_>>();
cx.background_spawn(async move {
let mut responses = Vec::new();
for diagnostics in join_all(pull_diagnostics).await {
responses.extend(diagnostics?);
}
Ok(responses)
})
} }
} }
@ -7055,11 +7058,11 @@ impl LspStore {
} }
async fn handle_multi_lsp_query( async fn handle_multi_lsp_query(
this: Entity<Self>, lsp_store: Entity<Self>,
envelope: TypedEnvelope<proto::MultiLspQuery>, envelope: TypedEnvelope<proto::MultiLspQuery>,
mut cx: AsyncApp, mut cx: AsyncApp,
) -> Result<proto::MultiLspQueryResponse> { ) -> Result<proto::MultiLspQueryResponse> {
let response_from_ssh = this.read_with(&mut cx, |this, _| { let response_from_ssh = lsp_store.read_with(&mut cx, |this, _| {
let (upstream_client, project_id) = this.upstream_client()?; let (upstream_client, project_id) = this.upstream_client()?;
let mut payload = envelope.payload.clone(); let mut payload = envelope.payload.clone();
payload.project_id = project_id; payload.project_id = project_id;
@ -7073,7 +7076,7 @@ impl LspStore {
let sender_id = envelope.original_sender_id().unwrap_or_default(); let sender_id = envelope.original_sender_id().unwrap_or_default();
let buffer_id = BufferId::new(envelope.payload.buffer_id)?; let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
let version = deserialize_version(&envelope.payload.version); let version = deserialize_version(&envelope.payload.version);
let buffer = this.update(&mut cx, |this, cx| { let buffer = lsp_store.update(&mut cx, |this, cx| {
this.buffer_store.read(cx).get_existing(buffer_id) this.buffer_store.read(cx).get_existing(buffer_id)
})??; })??;
buffer buffer
@ -7095,9 +7098,9 @@ impl LspStore {
match envelope.payload.request { match envelope.payload.request {
Some(proto::multi_lsp_query::Request::GetHover(get_hover)) => { Some(proto::multi_lsp_query::Request::GetHover(get_hover)) => {
let get_hover = let get_hover =
GetHover::from_proto(get_hover, this.clone(), buffer.clone(), cx.clone()) GetHover::from_proto(get_hover, lsp_store.clone(), buffer.clone(), cx.clone())
.await?; .await?;
let all_hovers = this let all_hovers = lsp_store
.update(&mut cx, |this, cx| { .update(&mut cx, |this, cx| {
this.request_multiple_lsp_locally( this.request_multiple_lsp_locally(
&buffer, &buffer,
@ -7109,7 +7112,7 @@ impl LspStore {
.await .await
.into_iter() .into_iter()
.filter_map(|hover| remove_empty_hover_blocks(hover?)); .filter_map(|hover| remove_empty_hover_blocks(hover?));
this.update(&mut cx, |project, cx| proto::MultiLspQueryResponse { lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: all_hovers responses: all_hovers
.map(|hover| proto::LspResponse { .map(|hover| proto::LspResponse {
response: Some(proto::lsp_response::Response::GetHoverResponse( response: Some(proto::lsp_response::Response::GetHoverResponse(
@ -7128,13 +7131,13 @@ impl LspStore {
Some(proto::multi_lsp_query::Request::GetCodeActions(get_code_actions)) => { Some(proto::multi_lsp_query::Request::GetCodeActions(get_code_actions)) => {
let get_code_actions = GetCodeActions::from_proto( let get_code_actions = GetCodeActions::from_proto(
get_code_actions, get_code_actions,
this.clone(), lsp_store.clone(),
buffer.clone(), buffer.clone(),
cx.clone(), cx.clone(),
) )
.await?; .await?;
let all_actions = this let all_actions = lsp_store
.update(&mut cx, |project, cx| { .update(&mut cx, |project, cx| {
project.request_multiple_lsp_locally( project.request_multiple_lsp_locally(
&buffer, &buffer,
@ -7146,7 +7149,7 @@ impl LspStore {
.await .await
.into_iter(); .into_iter();
this.update(&mut cx, |project, cx| proto::MultiLspQueryResponse { lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: all_actions responses: all_actions
.map(|code_actions| proto::LspResponse { .map(|code_actions| proto::LspResponse {
response: Some(proto::lsp_response::Response::GetCodeActionsResponse( response: Some(proto::lsp_response::Response::GetCodeActionsResponse(
@ -7165,13 +7168,13 @@ impl LspStore {
Some(proto::multi_lsp_query::Request::GetSignatureHelp(get_signature_help)) => { Some(proto::multi_lsp_query::Request::GetSignatureHelp(get_signature_help)) => {
let get_signature_help = GetSignatureHelp::from_proto( let get_signature_help = GetSignatureHelp::from_proto(
get_signature_help, get_signature_help,
this.clone(), lsp_store.clone(),
buffer.clone(), buffer.clone(),
cx.clone(), cx.clone(),
) )
.await?; .await?;
let all_signatures = this let all_signatures = lsp_store
.update(&mut cx, |project, cx| { .update(&mut cx, |project, cx| {
project.request_multiple_lsp_locally( project.request_multiple_lsp_locally(
&buffer, &buffer,
@ -7183,7 +7186,7 @@ impl LspStore {
.await .await
.into_iter(); .into_iter();
this.update(&mut cx, |project, cx| proto::MultiLspQueryResponse { lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: all_signatures responses: all_signatures
.map(|signature_help| proto::LspResponse { .map(|signature_help| proto::LspResponse {
response: Some( response: Some(
@ -7204,13 +7207,13 @@ impl LspStore {
Some(proto::multi_lsp_query::Request::GetCodeLens(get_code_lens)) => { Some(proto::multi_lsp_query::Request::GetCodeLens(get_code_lens)) => {
let get_code_lens = GetCodeLens::from_proto( let get_code_lens = GetCodeLens::from_proto(
get_code_lens, get_code_lens,
this.clone(), lsp_store.clone(),
buffer.clone(), buffer.clone(),
cx.clone(), cx.clone(),
) )
.await?; .await?;
let code_lens_actions = this let code_lens_actions = lsp_store
.update(&mut cx, |project, cx| { .update(&mut cx, |project, cx| {
project.request_multiple_lsp_locally( project.request_multiple_lsp_locally(
&buffer, &buffer,
@ -7222,7 +7225,7 @@ impl LspStore {
.await .await
.into_iter(); .into_iter();
this.update(&mut cx, |project, cx| proto::MultiLspQueryResponse { lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: code_lens_actions responses: code_lens_actions
.map(|actions| proto::LspResponse { .map(|actions| proto::LspResponse {
response: Some(proto::lsp_response::Response::GetCodeLensResponse( response: Some(proto::lsp_response::Response::GetCodeLensResponse(
@ -7238,31 +7241,46 @@ impl LspStore {
.collect(), .collect(),
}) })
} }
Some(proto::multi_lsp_query::Request::GetDocumentDiagnostics( Some(proto::multi_lsp_query::Request::GetDocumentDiagnostics(message)) => {
get_document_diagnostics, buffer
)) => { .update(&mut cx, |buffer, _| {
let get_document_diagnostics = GetDocumentDiagnostics::from_proto( buffer.wait_for_version(deserialize_version(&message.version))
get_document_diagnostics,
this.clone(),
buffer.clone(),
cx.clone(),
)
.await?;
let all_diagnostics = this
.update(&mut cx, |project, cx| {
project.request_multiple_lsp_locally(
&buffer,
None::<PointUtf16>,
get_document_diagnostics,
cx,
)
})? })?
.await .await?;
.into_iter(); let pull_diagnostics = lsp_store.update(&mut cx, |lsp_store, cx| {
let server_ids = buffer.update(cx, |buffer, cx| {
lsp_store
.language_servers_for_local_buffer(buffer, cx)
.map(|(_, server)| server.server_id())
.collect::<Vec<_>>()
});
this.update(&mut cx, |project, cx| proto::MultiLspQueryResponse { server_ids
.into_iter()
.map(|server_id| {
let result_id = lsp_store.result_id(server_id, buffer_id, cx);
lsp_store.request_lsp(
buffer.clone(),
LanguageServerToQuery::Other(server_id),
GetDocumentDiagnostics {
previous_result_id: result_id,
},
cx,
)
})
.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);
}
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: all_diagnostics responses: all_diagnostics
.into_iter()
.map(|lsp_diagnostic| proto::LspResponse { .map(|lsp_diagnostic| proto::LspResponse {
response: Some( response: Some(
proto::lsp_response::Response::GetDocumentDiagnosticsResponse( proto::lsp_response::Response::GetDocumentDiagnosticsResponse(
@ -8828,6 +8846,7 @@ impl LspStore {
local.language_server_watched_paths.remove(&server_id); local.language_server_watched_paths.remove(&server_id);
let server_state = local.language_servers.remove(&server_id); let server_state = local.language_servers.remove(&server_id);
cx.notify(); cx.notify();
self.remove_result_ids(server_id);
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id)); cx.emit(LspStoreEvent::LanguageServerRemoved(server_id));
cx.spawn(async move |_, cx| { cx.spawn(async move |_, cx| {
Self::shutdown_language_server(server_state, name, cx).await; Self::shutdown_language_server(server_state, name, cx).await;
@ -9716,7 +9735,20 @@ impl LspStore {
} }
} }
pub fn result_id(&self, buffer_id: BufferId, cx: &App) -> Option<String> { fn remove_result_ids(&mut self, for_server: LanguageServerId) {
if let Some(local) = self.as_local_mut() {
for values_per_server in local.buffer_pull_diagnostics_result_ids.values_mut() {
values_per_server.remove(&for_server);
}
}
}
pub fn result_id(
&self,
server_id: LanguageServerId,
buffer_id: BufferId,
cx: &App,
) -> Option<String> {
let abs_path = self let abs_path = self
.buffer_store .buffer_store
.read(cx) .read(cx)
@ -9725,19 +9757,21 @@ impl LspStore {
.map(|f| f.abs_path(cx))?; .map(|f| f.abs_path(cx))?;
self.as_local()? self.as_local()?
.buffer_pull_diagnostics_result_ids .buffer_pull_diagnostics_result_ids
.get(&abs_path) .get(&abs_path)?
.cloned() .get(&server_id)?
.flatten() .clone()
} }
pub fn all_result_ids(&self) -> HashMap<PathBuf, String> { pub fn all_result_ids(&self, server_id: LanguageServerId) -> HashMap<PathBuf, String> {
let Some(local) = self.as_local() else { let Some(local) = self.as_local() else {
return HashMap::default(); return HashMap::default();
}; };
local local
.buffer_pull_diagnostics_result_ids .buffer_pull_diagnostics_result_ids
.iter() .iter()
.filter_map(|(file_path, result_id)| Some((file_path.clone(), result_id.clone()?))) .filter_map(|(file_path, result_ids)| {
Some((file_path.clone(), result_ids.get(&server_id)?.clone()?))
})
.collect() .collect()
} }
@ -9822,7 +9856,7 @@ fn lsp_workspace_diagnostics_refresh(
let Ok(previous_result_ids) = lsp_store.update(cx, |lsp_store, _| { let Ok(previous_result_ids) = lsp_store.update(cx, |lsp_store, _| {
lsp_store lsp_store
.all_result_ids() .all_result_ids(server.server_id())
.into_iter() .into_iter()
.filter_map(|(abs_path, result_id)| { .filter_map(|(abs_path, result_id)| {
let uri = file_path_to_lsp_url(&abs_path).ok()?; let uri = file_path_to_lsp_url(&abs_path).ok()?;