Skip to main content

cosh_tools/lsp/
hover.rs

1//! `lsp_hover` engine: type/signature documentation at a position.
2
3use cosh_sdk::lsp::lsp_types::{
4    Hover, HoverContents, MarkedString,
5    request::{HoverRequest, Request as _},
6};
7use serde_json::json;
8
9use super::{support, types::HoverInput, types::HoverOutput};
10
11/// Resolve `textDocument/hover` on the first capable server.
12pub async fn run_hover(
13    deps: &super::support::Deps<'_>,
14    input: &HoverInput,
15) -> Result<HoverOutput, String> {
16    let path = deps.manager.root().join(&input.file_path);
17    let (position, clients) =
18        support::resolve_target(deps, &path, &input.position, &input.symbol).await?;
19
20    let params = json!({
21        "textDocument": { "uri": support::uri(&path)? },
22        "position": position,
23    });
24
25    let response: Option<Hover> = super::support::first_answer(
26        &clients,
27        deps.request_timeout,
28        |caps| caps.hover_provider.as_ref().is_some(),
29        |_client| (HoverRequest::METHOD.to_owned(), params.clone()),
30    )
31    .await?;
32
33    let formatted = response
34        .and_then(|hover| render_contents(&hover))
35        .unwrap_or_else(|| "No hover information.".into());
36
37    Ok(HoverOutput { formatted })
38}
39
40/// Flatten any of the three hover content shapes into plain text/markdown.
41fn render_contents(hover: &Hover) -> Option<String> {
42    match &hover.contents {
43        HoverContents::Scalar(value) => render_marked_string(value),
44        HoverContents::Array(values) => {
45            let parts: Vec<String> = values.iter().filter_map(render_marked_string).collect();
46            if parts.is_empty() {
47                None
48            } else {
49                Some(parts.join("\n\n"))
50            }
51        }
52        HoverContents::Markup(markup) => Some(markup.value.clone()),
53    }
54}
55
56fn render_marked_string(value: &MarkedString) -> Option<String> {
57    match value {
58        MarkedString::String(text) => Some(text.clone()),
59        MarkedString::LanguageString(block) => {
60            Some(format!("```{}\n{}\n```", block.language, block.value))
61        }
62    }
63}