Skip to main content

nifty_lsp/features/
signature_help.rs

1//! The parameters of the call being written, with the one being filled in highlighted.
2
3use lsp_types::{
4    Documentation, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
5};
6
7use crate::context;
8use crate::document::to_u32;
9use crate::project::Cursor;
10use crate::signature;
11
12pub(crate) fn signature_help(cursor: Cursor) -> Option<SignatureHelp> {
13    let document = cursor.document();
14    let word_start = document.word_start(cursor.offset);
15    let call = context::at(document, word_start).call?;
16    let signature = signature::of(cursor.project, call.callee.as_deref()?)?;
17    let active = signature.active(call.label.as_deref(), call.positional);
18    let (label, offsets) = signature.label();
19    let parameters = signature
20        .params
21        .iter()
22        .zip(offsets)
23        .map(|(param, offset)| ParameterInformation {
24            label: ParameterLabel::LabelOffsets(offset),
25            documentation: (!param.positional)
26                .then(|| Documentation::String(format!("Named: `{}: …`", param.name))),
27        })
28        .collect();
29    Some(SignatureHelp {
30        signatures: vec![SignatureInformation {
31            label,
32            documentation: signature
33                .summary
34                .map(|summary| Documentation::String(summary.to_owned())),
35            parameters: Some(parameters),
36            active_parameter: active.map(to_u32),
37        }],
38        active_signature: Some(0),
39        active_parameter: active.map(to_u32),
40    })
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::project::Project;
47
48    /// The signature and active parameter where `|` stands in `source`.
49    fn help(source: &str) -> Option<(String, Option<u32>)> {
50        let (project, offset) = Project::with_cursor(source);
51        let help = signature_help(project.cursor(offset))?;
52        Some((help.signatures[0].label.clone(), help.active_parameter))
53    }
54
55    #[test]
56    fn shows_an_elements_parameters_and_the_one_being_filled() {
57        let window = "window(title: fixed string, width: number, height: number, backdrop: backdrop, open: bool)";
58        assert_eq!(help("window(|"), Some((window.to_owned(), Some(0))));
59        assert_eq!(
60            help("window settings(\"Settings\", |"),
61            Some((window.to_owned(), None)),
62            "a named window is still a window"
63        );
64        assert_eq!(help("window(\"App\", |"), Some((window.to_owned(), None)));
65        assert_eq!(
66            help("window(\"App\", height: |"),
67            Some((window.to_owned(), Some(2)))
68        );
69    }
70
71    #[test]
72    fn shows_declared_callables_and_stays_inside_nested_brackets() {
73        let source = "fn add(a: int, b: [int]) -> int { return a }\nlet x = add(1, [|";
74        assert_eq!(
75            help(source),
76            Some(("add(a: int, b: [int])".to_owned(), Some(1)))
77        );
78    }
79
80    #[test]
81    fn shows_nothing_outside_a_call() {
82        assert_eq!(help("state x = |"), None);
83    }
84}