LSP: Gracefully handle partial failures in multi-server LSP requests

This is the same change as 1c9a5bd366 but for:

* document symbols
* workspace symbols
* goto definition/declaration/.../references
* hover

Instead of bailing when one server fails, we log an error and continue
gathering items from the other responses.
pull/13170/head
Michael Davis 2025-03-21 10:26:18 -04:00
parent 3a63e85b6a
commit 14cab4ba62
No known key found for this signature in database
1 changed files with 55 additions and 43 deletions

View File

@ -392,9 +392,11 @@ pub fn symbol_picker(cx: &mut Context) {
cx.jobs.callback(async move { cx.jobs.callback(async move {
let mut symbols = Vec::new(); let mut symbols = Vec::new();
// TODO if one symbol request errors, all other requests are discarded (even if they're valid) while let Some(response) = futures.next().await {
while let Some(mut lsp_items) = futures.try_next().await? { match response {
symbols.append(&mut lsp_items); Ok(mut items) => symbols.append(&mut items),
Err(err) => log::error!("Error requesting document symbols: {err}"),
}
} }
let call = move |_editor: &mut Editor, compositor: &mut Compositor| { let call = move |_editor: &mut Editor, compositor: &mut Compositor| {
let columns = [ let columns = [
@ -497,12 +499,16 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
let injector = injector.clone(); let injector = injector.clone();
async move { async move {
// TODO if one symbol request errors, all other requests are discarded (even if they're valid) while let Some(response) = futures.next().await {
while let Some(lsp_items) = futures.try_next().await? { match response {
for item in lsp_items { Ok(items) => {
for item in items {
injector.push(item)?; injector.push(item)?;
} }
} }
Err(err) => log::error!("Error requesting workspace symbols: {err}"),
}
}
Ok(()) Ok(())
} }
.boxed() .boxed()
@ -901,17 +907,16 @@ where
cx.jobs.callback(async move { cx.jobs.callback(async move {
let mut locations = Vec::new(); let mut locations = Vec::new();
while let Some((response, offset_encoding)) = futures.try_next().await? { while let Some(response) = futures.next().await {
match response { match response {
Ok((response, offset_encoding)) => match response {
Some(lsp::GotoDefinitionResponse::Scalar(lsp_location)) => { Some(lsp::GotoDefinitionResponse::Scalar(lsp_location)) => {
locations.extend(lsp_location_to_location(lsp_location, offset_encoding)); locations.extend(lsp_location_to_location(lsp_location, offset_encoding));
} }
Some(lsp::GotoDefinitionResponse::Array(lsp_locations)) => { Some(lsp::GotoDefinitionResponse::Array(lsp_locations)) => {
locations.extend( locations.extend(lsp_locations.into_iter().flat_map(|location| {
lsp_locations.into_iter().flat_map(|location| {
lsp_location_to_location(location, offset_encoding) lsp_location_to_location(location, offset_encoding)
}), }));
);
} }
Some(lsp::GotoDefinitionResponse::Link(lsp_locations)) => { Some(lsp::GotoDefinitionResponse::Link(lsp_locations)) => {
locations.extend( locations.extend(
@ -929,6 +934,8 @@ where
); );
} }
None => (), None => (),
},
Err(err) => log::error!("Error requesting locations: {err}"),
} }
} }
let call = move |editor: &mut Editor, compositor: &mut Compositor| { let call = move |editor: &mut Editor, compositor: &mut Compositor| {
@ -1001,13 +1008,16 @@ pub fn goto_reference(cx: &mut Context) {
cx.jobs.callback(async move { cx.jobs.callback(async move {
let mut locations = Vec::new(); let mut locations = Vec::new();
while let Some((lsp_locations, offset_encoding)) = futures.try_next().await? { while let Some(response) = futures.next().await {
locations.extend( match response {
Ok((lsp_locations, offset_encoding)) => locations.extend(
lsp_locations lsp_locations
.into_iter() .into_iter()
.flatten() .flatten()
.flat_map(|location| lsp_location_to_location(location, offset_encoding)), .flat_map(|location| lsp_location_to_location(location, offset_encoding)),
); ),
Err(err) => log::error!("Error requesting references: {err}"),
}
} }
let call = move |editor: &mut Editor, compositor: &mut Compositor| { let call = move |editor: &mut Editor, compositor: &mut Compositor| {
if locations.is_empty() { if locations.is_empty() {
@ -1059,9 +1069,11 @@ pub fn hover(cx: &mut Context) {
cx.jobs.callback(async move { cx.jobs.callback(async move {
let mut hovers: Vec<(String, lsp::Hover)> = Vec::new(); let mut hovers: Vec<(String, lsp::Hover)> = Vec::new();
while let Some((server_name, hover)) = futures.try_next().await? { while let Some(response) = futures.next().await {
if let Some(hover) = hover { match response {
hovers.push((server_name, hover)); Ok((server_name, Some(hover))) => hovers.push((server_name, hover)),
Ok(_) => (),
Err(err) => log::error!("Error requesting hover: {err}"),
} }
} }