1use 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
11pub 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
40fn 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}