nifty_lsp/features/
hover.rs1use lsp_types::{Hover, HoverContents, MarkupContent, MarkupKind};
5use nifty_check::methods;
6
7use crate::document::Encoding;
8use crate::index::{Role, SymbolKind, Unresolved};
9use crate::project::{Cursor, Project};
10use crate::signature;
11
12pub(crate) fn hover(cursor: Cursor, encoding: Encoding) -> Option<Hover> {
13 let project = cursor.project;
14 let occurrence = project.index.occurrence_at(cursor.file, cursor.offset)?;
15 let name = project.text(occurrence.span);
16 let (code, summary) = match occurrence.role {
17 Role::Unresolved(Unresolved::Method) => {
18 let method = methods::method(name)?;
19 let code = format!("{}({})", method.name, method.params.join(", "));
20 (code, Some(method.summary))
21 }
22 Role::Unresolved(Unresolved::Function) => callable(project, name)?,
23 Role::Declaration(id) | Role::Reference(id)
24 if matches!(
25 project.index.symbol(id).kind,
26 SymbolKind::Function | SymbolKind::View | SymbolKind::Struct
27 ) =>
28 {
29 callable(project, name)?
30 }
31 _ => return None,
32 };
33 let mut markdown = format!("```nifty\n{code}\n```");
34 if let Some(summary) = summary {
35 markdown.push_str("\n\n");
36 markdown.push_str(summary);
37 }
38 Some(Hover {
39 contents: HoverContents::Markup(MarkupContent {
40 kind: MarkupKind::Markdown,
41 value: markdown,
42 }),
43 range: Some(cursor.document().range(occurrence.span, encoding)),
44 })
45}
46
47fn callable(project: &Project, name: &str) -> Option<(String, Option<&'static str>)> {
48 let signature = signature::of(project, name)?;
49 Some((signature.label().0, signature.summary))
50}
51
52#[cfg(test)]
53mod tests {
54 use nifty_check::elements;
55 use nifty_check::functions::FUNCTIONS;
56 use nifty_check::methods::METHODS;
57
58 use super::*;
59
60 fn text(source: &str) -> Option<String> {
62 let (project, offset) = Project::with_cursor(source);
63 let hover = hover(project.cursor(offset), Encoding::Utf16)?;
64 let HoverContents::Markup(markup) = hover.contents else {
65 panic!("hover is markdown");
66 };
67 Some(markup.value)
68 }
69
70 #[test]
71 fn built_ins_show_their_signature_and_summary() {
72 let label = elements::element("label").expect("label is an element");
73 let shown = text("window(\"A\") {\n la|bel(\"hi\")\n}").expect("a hover");
74 assert!(shown.starts_with("```nifty\nlabel(text: string"), "{shown}");
75 assert!(shown.ends_with(label.summary), "{shown}");
76
77 let download = &FUNCTIONS[0];
78 let shown = text(&format!("fn f() {{\n {}|(\"u\")\n}}", download.name)).expect("a hover");
79 assert!(shown.ends_with(download.summary), "{shown}");
80 }
81
82 #[test]
83 fn methods_show_their_parameters_and_summary() {
84 let push = METHODS
85 .iter()
86 .find(|method| method.name == "push")
87 .expect("push is a method");
88 let shown = text("state xs: [int] = []\nfn f() {\n xs.pu|sh(1)\n}").expect("a hover");
89 assert_eq!(
90 shown,
91 format!("```nifty\npush(item)\n```\n\n{}", push.summary)
92 );
93 }
94
95 #[test]
96 fn declared_callables_show_their_signature() {
97 let shown = text("fn add(a: int) -> int { return a }\nlet x = ad|d(1)");
98 assert_eq!(shown.as_deref(), Some("```nifty\nadd(a: int)\n```"));
99 }
100
101 #[test]
102 fn values_and_unknown_names_show_nothing() {
103 assert_eq!(text("state count = 0\nlet x = cou|nt"), None);
104 assert_eq!(text("fn f() {\n nope|()\n}"), None);
105 }
106}