aboutsummaryrefslogtreecommitdiff
path: root/crates/libeditor/src/completion.rs
blob: c6ce62661530110a6352f74361df55e4fe219bcc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use libsyntax2::{
    File, TextUnit, AstNode,
    ast::self,
    algo::{
        ancestors,
    },
};

use {
    AtomEdit, find_node_at_offset,
    scope::FnScopes,
};

#[derive(Debug)]
pub struct CompletionItem {
    pub name: String,
}

pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionItem>> {
    // Insert a fake ident to get a valid parse tree
    let file = {
        let edit = AtomEdit::insert(offset, "intellijRulezz".to_string());
        // Don't bother with completion if incremental reparse fails
        file.incremental_reparse(&edit)?
    };
    let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), offset)?;
    let fn_def = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next()?;
    let scopes = FnScopes::new(fn_def);
    Some(complete(name_ref, &scopes))
}

fn complete(name_ref: ast::NameRef, scopes: &FnScopes) -> Vec<CompletionItem> {
    scopes.scope_chain(name_ref.syntax())
        .flat_map(|scope| scopes.entries(scope).iter())
        .map(|entry| CompletionItem {
            name: entry.name().to_string()
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use test_utils::{assert_eq_dbg, extract_offset};

    fn do_check(code: &str, expected_completions: &str) {
        let (off, code) = extract_offset(&code);
        let file = File::parse(&code);
        let completions = scope_completion(&file, off).unwrap();
        assert_eq_dbg(expected_completions, &completions);
    }

    #[test]
    fn test_completion_let_scope() {
        do_check(r"
            fn quux(x: i32) {
                let y = 92;
                1 + <|>;
                let z = ();
            }
            ", r#"[CompletionItem { name: "y" },
                   CompletionItem { name: "x" }]"#);
    }

    #[test]
    fn test_completion_if_let_scope() {
        do_check(r"
            fn quux() {
                if let Some(x) = foo() {
                    let y = 92;
                };
                if let Some(a) = bar() {
                    let b = 62;
                    1 + <|>
                }
            }
            ", r#"[CompletionItem { name: "b" },
                   CompletionItem { name: "a" }]"#);
    }

    #[test]
    fn test_completion_for_scope() {
        do_check(r"
            fn quux() {
                for x in &[1, 2, 3] {
                    <|>
                }
            }
            ", r#"[CompletionItem { name: "x" }]"#);
    }
}