Fix remote server completions not being queried from all LSP servers (#42723)

Closes #41294

Release Notes:

- Fixed remote LSPs not being queried
This commit is contained in:
localcc
2025-11-17 18:07:49 +00:00
committed by GitHub
parent faa1136651
commit fd1494c31a
5 changed files with 303 additions and 31 deletions
+5
View File
@@ -218,6 +218,7 @@ pub(crate) struct GetHover {
pub(crate) struct GetCompletions {
pub position: PointUtf16,
pub context: CompletionContext,
pub server_id: Option<lsp::LanguageServerId>,
}
#[derive(Clone, Debug)]
@@ -2395,6 +2396,7 @@ impl LspCommand for GetCompletions {
buffer_id: buffer.remote_id().into(),
position: Some(language::proto::serialize_anchor(&anchor)),
version: serialize_version(&buffer.version()),
server_id: self.server_id.map(|id| id.to_proto()),
}
}
@@ -2423,6 +2425,9 @@ impl LspCommand for GetCompletions {
trigger_kind: CompletionTriggerKind::INVOKED,
trigger_character: None,
},
server_id: message
.server_id
.map(|id| lsp::LanguageServerId::from_proto(id)),
})
}
+140 -23
View File
@@ -3754,7 +3754,7 @@ impl LspStore {
client.add_entity_request_handler(Self::handle_register_buffer_with_language_servers);
client.add_entity_request_handler(Self::handle_rename_project_entry);
client.add_entity_request_handler(Self::handle_pull_workspace_diagnostics);
client.add_entity_request_handler(Self::handle_lsp_command::<GetCompletions>);
client.add_entity_request_handler(Self::handle_lsp_get_completions);
client.add_entity_request_handler(Self::handle_lsp_command::<GetDocumentHighlights>);
client.add_entity_request_handler(Self::handle_lsp_command::<GetDocumentSymbols>);
client.add_entity_request_handler(Self::handle_lsp_command::<PrepareRename>);
@@ -4463,6 +4463,41 @@ impl LspStore {
.any(check)
}
fn all_capable_for_proto_request<F>(
&self,
buffer: &Entity<Buffer>,
mut check: F,
cx: &App,
) -> Vec<lsp::LanguageServerId>
where
F: FnMut(&lsp::LanguageServerName, &lsp::ServerCapabilities) -> bool,
{
let Some(language) = buffer.read(cx).language().cloned() else {
return Vec::default();
};
let relevant_language_servers = self
.languages
.lsp_adapters(&language.name())
.into_iter()
.map(|lsp_adapter| lsp_adapter.name())
.collect::<HashSet<_>>();
self.language_server_statuses
.iter()
.filter_map(|(server_id, server_status)| {
relevant_language_servers
.contains(&server_status.name)
.then_some((server_id, &server_status.name))
})
.filter_map(|(server_id, server_name)| {
self.lsp_server_capabilities
.get(server_id)
.map(|c| (server_id, server_name, c))
})
.filter(|(_, server_name, capabilities)| check(server_name, capabilities))
.map(|(server_id, _, _)| *server_id)
.collect()
}
pub fn request_lsp<R>(
&mut self,
buffer: Entity<Buffer>,
@@ -5902,17 +5937,24 @@ impl LspStore {
let language_registry = self.languages.clone();
if let Some((upstream_client, project_id)) = self.upstream_client() {
let request = GetCompletions { position, context };
if !self.is_capable_for_proto_request(buffer, &request, cx) {
return Task::ready(Ok(Vec::new()));
}
let task = self.send_lsp_proto_request(
buffer.clone(),
upstream_client,
project_id,
request,
let snapshot = buffer.read(cx).snapshot();
let offset = position.to_offset(&snapshot);
let scope = snapshot.language_scope_at(offset);
let capable_lsps = self.all_capable_for_proto_request(
buffer,
|server_name, capabilities| {
capabilities.completion_provider.is_some()
&& scope
.as_ref()
.map(|scope| scope.language_allowed(server_name))
.unwrap_or(true)
},
cx,
);
if capable_lsps.is_empty() {
return Task::ready(Ok(Vec::new()));
}
let language = buffer.read(cx).language().cloned();
// In the future, we should provide project guests with the names of LSP adapters,
@@ -5925,19 +5967,53 @@ impl LspStore {
.cloned()
});
cx.foreground_executor().spawn(async move {
let completion_response = task.await?;
let completions = populate_labels_for_completions(
completion_response.completions,
language,
lsp_adapter,
)
.await;
Ok(vec![CompletionResponse {
completions,
display_options: CompletionDisplayOptions::default(),
is_incomplete: completion_response.is_incomplete,
}])
let buffer = buffer.clone();
cx.spawn(async move |this, cx| {
let requests = join_all(
capable_lsps
.into_iter()
.map(|id| {
let request = GetCompletions {
position,
context: context.clone(),
server_id: Some(id),
};
let buffer = buffer.clone();
let language = language.clone();
let lsp_adapter = lsp_adapter.clone();
let upstream_client = upstream_client.clone();
let response = this
.update(cx, |this, cx| {
this.send_lsp_proto_request(
buffer,
upstream_client,
project_id,
request,
cx,
)
})
.log_err();
async move {
let response = response?.await.log_err()?;
let completions = populate_labels_for_completions(
response.completions,
language,
lsp_adapter,
)
.await;
Some(CompletionResponse {
completions,
display_options: CompletionDisplayOptions::default(),
is_incomplete: response.is_incomplete,
})
}
})
.collect::<Vec<_>>(),
);
Ok(requests.await.into_iter().flatten().collect::<Vec<_>>())
})
} else if let Some(local) = self.as_local() {
let snapshot = buffer.read(cx).snapshot();
@@ -5998,6 +6074,7 @@ impl LspStore {
GetCompletions {
position,
context: context.clone(),
server_id: Some(server_id),
},
cx,
).fuse();
@@ -8461,6 +8538,46 @@ impl LspStore {
})
}
async fn handle_lsp_get_completions(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GetCompletions>,
mut cx: AsyncApp,
) -> Result<proto::GetCompletionsResponse> {
let sender_id = envelope.original_sender_id().unwrap_or_default();
let buffer_id = GetCompletions::buffer_id_from_proto(&envelope.payload)?;
let buffer_handle = this.update(&mut cx, |this, cx| {
this.buffer_store.read(cx).get_existing(buffer_id)
})??;
let request = GetCompletions::from_proto(
envelope.payload,
this.clone(),
buffer_handle.clone(),
cx.clone(),
)
.await?;
let server_to_query = match request.server_id {
Some(server_id) => LanguageServerToQuery::Other(server_id),
None => LanguageServerToQuery::FirstCapable,
};
let response = this
.update(&mut cx, |this, cx| {
this.request_lsp(buffer_handle.clone(), server_to_query, request, cx)
})?
.await?;
this.update(&mut cx, |this, cx| {
Ok(GetCompletions::response_to_proto(
response,
this,
sender_id,
&buffer_handle.read(cx).version(),
cx,
))
})?
}
async fn handle_lsp_command<T: LspCommand>(
this: Entity<Self>,
envelope: TypedEnvelope<T::ProtoRequest>,