From 19fa20f4a3ef65b12eeca34a0ba21851a05ca38f Mon Sep 17 00:00:00 2001 From: Viet Dinh <54ckb0y789@gmail.com> Date: Sat, 11 Nov 2023 13:32:13 -0500 Subject: [PATCH] feat: multi-lsp for hover --- helix-term/src/commands/lsp.rs | 103 ++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 41 deletions(-) diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 874004616c76d..47d1f8d1ab70c 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -1,4 +1,8 @@ -use futures_util::{future::BoxFuture, stream::FuturesUnordered, FutureExt}; +use futures_util::{ + future::{join_all, BoxFuture}, + stream::FuturesUnordered, + FutureExt, +}; use helix_lsp::{ block_on, lsp::{ @@ -1279,52 +1283,69 @@ pub fn signature_help_impl_with_future( pub fn hover(cx: &mut Context) { let (view, doc) = current!(cx.editor); - // TODO support multiple language servers (merge UI somehow) - let language_server = - language_server_with_feature!(cx.editor, doc, LanguageServerFeature::Hover); - // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier - let pos = doc.position(view.id, language_server.offset_encoding()); - let future = language_server - .text_document_hover(doc.identifier(), pos, None) - .unwrap(); + let language_servers = doc.language_servers_with_feature(LanguageServerFeature::Hover); + let mut future = vec![]; + for server in language_servers { + // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier + let pos = doc.position(view.id, server.offset_encoding()); + if let Some(resp) = server.text_document_hover(doc.identifier(), pos, None) { + future.push(resp); + } + } - cx.callback( - future, - move |editor, compositor, response: Option| { - if let Some(hover) = response { - // hover.contents / .range <- used for visualizing - - fn marked_string_to_markdown(contents: lsp::MarkedString) -> String { - match contents { - lsp::MarkedString::String(contents) => contents, - lsp::MarkedString::LanguageString(string) => { - if string.language == "markdown" { - string.value - } else { - format!("```{}\n{}\n```", string.language, string.value) - } - } + let future = join_all(future).map(|data| { + Ok(data + .into_iter() + .flat_map(|resp| { + let resp = resp.ok()?; + (!resp.is_null()).then_some(resp) + }) + .collect()) + }); + + cx.callback(future, move |editor, compositor, hover: Vec| { + // hover.contents / .range <- used for visualizing + fn marked_string_to_markdown(contents: lsp::MarkedString) -> String { + match contents { + lsp::MarkedString::String(contents) => contents, + lsp::MarkedString::LanguageString(string) => { + if string.language == "markdown" { + string.value + } else { + format!("```{}\n{}\n```", string.language, string.value) } } + } + } - let contents = match hover.contents { - lsp::HoverContents::Scalar(contents) => marked_string_to_markdown(contents), - lsp::HoverContents::Array(contents) => contents - .into_iter() - .map(marked_string_to_markdown) - .collect::>() - .join("\n\n"), - lsp::HoverContents::Markup(contents) => contents.value, - }; + let contents = hover + .into_iter() + .map(|hover| match hover.contents { + lsp::HoverContents::Scalar(contents) => marked_string_to_markdown(contents), + lsp::HoverContents::Array(contents) => contents + .into_iter() + .map(marked_string_to_markdown) + .collect::>() + .join("\n\n"), + lsp::HoverContents::Markup(contents) => contents.value, + }) + .fold(String::new(), |mut lhs, rhs| { + if lhs.is_empty() { + rhs + } else { + if !rhs.is_empty() { + _ = write!(&mut lhs, "\n---\n{rhs}"); + } + lhs + } + }); - // skip if contents empty + // skip if contents empty - let contents = ui::Markdown::new(contents, editor.syn_loader.clone()); - let popup = Popup::new("hover", contents).auto_close(true); - compositor.replace_or_push("hover", popup); - } - }, - ); + let contents = ui::Markdown::new(contents, editor.syn_loader.clone()); + let popup = Popup::new("hover", contents).auto_close(true); + compositor.replace_or_push("hover", popup); + }); } pub fn rename_symbol(cx: &mut Context) {