diff options
Diffstat (limited to 'crates/libeditor')
-rw-r--r-- | crates/libeditor/src/completion.rs | 123 |
1 files changed, 105 insertions, 18 deletions
diff --git a/crates/libeditor/src/completion.rs b/crates/libeditor/src/completion.rs index 7e8669822..65527db62 100644 --- a/crates/libeditor/src/completion.rs +++ b/crates/libeditor/src/completion.rs | |||
@@ -12,7 +12,8 @@ use { | |||
12 | }; | 12 | }; |
13 | 13 | ||
14 | #[derive(Debug)] | 14 | #[derive(Debug)] |
15 | pub struct CompletionItem { | 15 | pub struct |
16 | CompletionItem { | ||
16 | pub name: String, | 17 | pub name: String, |
17 | pub snippet: Option<String> | 18 | pub snippet: Option<String> |
18 | } | 19 | } |
@@ -25,10 +26,17 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI | |||
25 | file.incremental_reparse(&edit)? | 26 | file.incremental_reparse(&edit)? |
26 | }; | 27 | }; |
27 | let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), offset)?; | 28 | let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), offset)?; |
29 | if !is_ident_expr(name_ref) { | ||
30 | return None; | ||
31 | } | ||
32 | |||
28 | let mut res = Vec::new(); | 33 | let mut res = Vec::new(); |
29 | if let Some(fn_def) = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next() { | 34 | if let Some(fn_def) = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next() { |
35 | complete_keywords(&file, Some(fn_def), name_ref, &mut res); | ||
30 | let scopes = FnScopes::new(fn_def); | 36 | let scopes = FnScopes::new(fn_def); |
31 | complete_fn(name_ref, &scopes, &mut res); | 37 | complete_fn(name_ref, &scopes, &mut res); |
38 | } else { | ||
39 | complete_keywords(&file, None, name_ref, &mut res); | ||
32 | } | 40 | } |
33 | if let Some(root) = ancestors(name_ref.syntax()).filter_map(ast::Root::cast).next() { | 41 | if let Some(root) = ancestors(name_ref.syntax()).filter_map(ast::Root::cast).next() { |
34 | let scope = ModuleScope::new(root); | 42 | let scope = ModuleScope::new(root); |
@@ -43,6 +51,42 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI | |||
43 | Some(res) | 51 | Some(res) |
44 | } | 52 | } |
45 | 53 | ||
54 | fn is_ident_expr(name_ref: ast::NameRef) -> bool { | ||
55 | match ancestors(name_ref.syntax()).filter_map(ast::Expr::cast).next() { | ||
56 | None => false, | ||
57 | Some(expr) => { | ||
58 | expr.syntax().range() == name_ref.syntax().range() | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | |||
63 | fn complete_keywords(file: &File, fn_def: Option<ast::FnDef>, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) { | ||
64 | acc.push(keyword("if", "if $0 { }")); | ||
65 | acc.push(keyword("match", "match $0 { }")); | ||
66 | acc.push(keyword("while", "while $0 { }")); | ||
67 | acc.push(keyword("loop", "loop {$0}")); | ||
68 | |||
69 | if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { | ||
70 | if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) { | ||
71 | if if_expr.syntax().range().end() < name_ref.syntax().range().start() { | ||
72 | acc.push(keyword("else", "else {$0}")); | ||
73 | acc.push(keyword("else if", "else if $0 { }")); | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | // if let Some(fn_def) = fn_def { | ||
79 | // acc.push(keyword("return", "")) | ||
80 | // } | ||
81 | |||
82 | fn keyword(kw: &str, snip: &str) -> CompletionItem { | ||
83 | CompletionItem { | ||
84 | name: kw.to_string(), | ||
85 | snippet: Some(snip.to_string()), | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | |||
46 | fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) { | 90 | fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) { |
47 | acc.extend( | 91 | acc.extend( |
48 | scopes.scope_chain(name_ref.syntax()) | 92 | scopes.scope_chain(name_ref.syntax()) |
@@ -59,29 +103,44 @@ mod tests { | |||
59 | use super::*; | 103 | use super::*; |
60 | use test_utils::{assert_eq_dbg, extract_offset}; | 104 | use test_utils::{assert_eq_dbg, extract_offset}; |
61 | 105 | ||
62 | fn do_check(code: &str, expected_completions: &str) { | 106 | fn check_scope_completion(code: &str, expected_completions: &str) { |
63 | let (off, code) = extract_offset(&code); | 107 | let (off, code) = extract_offset(&code); |
64 | let file = File::parse(&code); | 108 | let file = File::parse(&code); |
65 | let completions = scope_completion(&file, off).unwrap(); | 109 | let completions = scope_completion(&file, off) |
110 | .unwrap() | ||
111 | .into_iter() | ||
112 | .filter(|c| c.snippet.is_none()) | ||
113 | .collect::<Vec<_>>(); | ||
114 | assert_eq_dbg(expected_completions, &completions); | ||
115 | } | ||
116 | |||
117 | fn check_snippet_completion(code: &str, expected_completions: &str) { | ||
118 | let (off, code) = extract_offset(&code); | ||
119 | let file = File::parse(&code); | ||
120 | let completions = scope_completion(&file, off) | ||
121 | .unwrap() | ||
122 | .into_iter() | ||
123 | .filter(|c| c.snippet.is_some()) | ||
124 | .collect::<Vec<_>>(); | ||
66 | assert_eq_dbg(expected_completions, &completions); | 125 | assert_eq_dbg(expected_completions, &completions); |
67 | } | 126 | } |
68 | 127 | ||
69 | #[test] | 128 | #[test] |
70 | fn test_completion_let_scope() { | 129 | fn test_completion_let_scope() { |
71 | do_check(r" | 130 | check_scope_completion(r" |
72 | fn quux(x: i32) { | 131 | fn quux(x: i32) { |
73 | let y = 92; | 132 | let y = 92; |
74 | 1 + <|>; | 133 | 1 + <|>; |
75 | let z = (); | 134 | let z = (); |
76 | } | 135 | } |
77 | ", r#"[CompletionItem { name: "y" }, | 136 | ", r#"[CompletionItem { name: "y", snippet: None }, |
78 | CompletionItem { name: "x" }, | 137 | CompletionItem { name: "x", snippet: None }, |
79 | CompletionItem { name: "quux" }]"#); | 138 | CompletionItem { name: "quux", snippet: None }]"#); |
80 | } | 139 | } |
81 | 140 | ||
82 | #[test] | 141 | #[test] |
83 | fn test_completion_if_let_scope() { | 142 | fn test_completion_if_let_scope() { |
84 | do_check(r" | 143 | check_scope_completion(r" |
85 | fn quux() { | 144 | fn quux() { |
86 | if let Some(x) = foo() { | 145 | if let Some(x) = foo() { |
87 | let y = 92; | 146 | let y = 92; |
@@ -91,33 +150,61 @@ mod tests { | |||
91 | 1 + <|> | 150 | 1 + <|> |
92 | } | 151 | } |
93 | } | 152 | } |
94 | ", r#"[CompletionItem { name: "b" }, | 153 | ", r#"[CompletionItem { name: "b", snippet: None }, |
95 | CompletionItem { name: "a" }, | 154 | CompletionItem { name: "a", snippet: None }, |
96 | CompletionItem { name: "quux" }]"#); | 155 | CompletionItem { name: "quux", snippet: None }]"#); |
97 | } | 156 | } |
98 | 157 | ||
99 | #[test] | 158 | #[test] |
100 | fn test_completion_for_scope() { | 159 | fn test_completion_for_scope() { |
101 | do_check(r" | 160 | check_scope_completion(r" |
102 | fn quux() { | 161 | fn quux() { |
103 | for x in &[1, 2, 3] { | 162 | for x in &[1, 2, 3] { |
104 | <|> | 163 | <|> |
105 | } | 164 | } |
106 | } | 165 | } |
107 | ", r#"[CompletionItem { name: "x" }, | 166 | ", r#"[CompletionItem { name: "x", snippet: None }, |
108 | CompletionItem { name: "quux" }]"#); | 167 | CompletionItem { name: "quux", snippet: None }]"#); |
109 | } | 168 | } |
110 | 169 | ||
111 | #[test] | 170 | #[test] |
112 | fn test_completion_mod_scope() { | 171 | fn test_completion_mod_scope() { |
113 | do_check(r" | 172 | check_scope_completion(r" |
114 | struct Foo; | 173 | struct Foo; |
115 | enum Baz {} | 174 | enum Baz {} |
116 | fn quux() { | 175 | fn quux() { |
117 | <|> | 176 | <|> |
118 | } | 177 | } |
119 | ", r#"[CompletionItem { name: "Foo" }, | 178 | ", r#"[CompletionItem { name: "Foo", snippet: None }, |
120 | CompletionItem { name: "Baz" }, | 179 | CompletionItem { name: "Baz", snippet: None }, |
121 | CompletionItem { name: "quux" }]"#); | 180 | CompletionItem { name: "quux", snippet: None }]"#); |
181 | } | ||
182 | |||
183 | #[test] | ||
184 | fn test_completion_kewords() { | ||
185 | check_snippet_completion(r" | ||
186 | fn quux() { | ||
187 | <|> | ||
188 | } | ||
189 | ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") }, | ||
190 | CompletionItem { name: "match", snippet: Some("match $0 { }") }, | ||
191 | CompletionItem { name: "while", snippet: Some("while $0 { }") }, | ||
192 | CompletionItem { name: "loop", snippet: Some("loop {$0}") }]"#); | ||
193 | } | ||
194 | |||
195 | #[test] | ||
196 | fn test_completion_else() { | ||
197 | check_snippet_completion(r" | ||
198 | fn quux() { | ||
199 | if true { | ||
200 | () | ||
201 | } <|> | ||
202 | } | ||
203 | ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") }, | ||
204 | CompletionItem { name: "match", snippet: Some("match $0 { }") }, | ||
205 | CompletionItem { name: "while", snippet: Some("while $0 { }") }, | ||
206 | CompletionItem { name: "loop", snippet: Some("loop {$0}") }, | ||
207 | CompletionItem { name: "else", snippet: Some("else {$0}") }, | ||
208 | CompletionItem { name: "else if", snippet: Some("else if $0 { }") }]"#); | ||
122 | } | 209 | } |
123 | } | 210 | } |