diff options
Diffstat (limited to 'crates/libeditor/src/completion.rs')
-rw-r--r-- | crates/libeditor/src/completion.rs | 222 |
1 files changed, 146 insertions, 76 deletions
diff --git a/crates/libeditor/src/completion.rs b/crates/libeditor/src/completion.rs index b296f6fd5..6c3775127 100644 --- a/crates/libeditor/src/completion.rs +++ b/crates/libeditor/src/completion.rs | |||
@@ -1,11 +1,11 @@ | |||
1 | use std::collections::HashSet; | 1 | use std::collections::{HashSet, HashMap}; |
2 | 2 | ||
3 | use libsyntax2::{ | 3 | use libsyntax2::{ |
4 | File, TextUnit, AstNode, SyntaxKind::*, | 4 | File, TextUnit, AstNode, SyntaxNodeRef, SyntaxKind::*, |
5 | ast::{self, LoopBodyOwner}, | 5 | ast::{self, LoopBodyOwner}, |
6 | algo::{ | 6 | algo::{ |
7 | ancestors, | 7 | ancestors, |
8 | visit::{visitor, Visitor}, | 8 | visit::{visitor, Visitor, visitor_ctx, VisitorCtx}, |
9 | }, | 9 | }, |
10 | text_utils::is_subrange, | 10 | text_utils::is_subrange, |
11 | }; | 11 | }; |
@@ -17,7 +17,11 @@ use { | |||
17 | 17 | ||
18 | #[derive(Debug)] | 18 | #[derive(Debug)] |
19 | pub struct CompletionItem { | 19 | pub struct CompletionItem { |
20 | pub name: String, | 20 | /// What user sees in pop-up |
21 | pub label: String, | ||
22 | /// What string is used for filtering, defaults to label | ||
23 | pub lookup: Option<String>, | ||
24 | /// What is inserted, defaults to label | ||
21 | pub snippet: Option<String> | 25 | pub snippet: Option<String> |
22 | } | 26 | } |
23 | 27 | ||
@@ -27,40 +31,89 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI | |||
27 | let edit = AtomEdit::insert(offset, "intellijRulezz".to_string()); | 31 | let edit = AtomEdit::insert(offset, "intellijRulezz".to_string()); |
28 | file.reparse(&edit) | 32 | file.reparse(&edit) |
29 | }; | 33 | }; |
30 | let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), offset)?; | 34 | let mut has_completions = false; |
31 | if !is_single_segment(name_ref) { | 35 | let mut res = Vec::new(); |
32 | return None; | 36 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) { |
37 | has_completions = true; | ||
38 | complete_name_ref(&file, name_ref, &mut res) | ||
39 | } | ||
40 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) { | ||
41 | has_completions = true; | ||
42 | complete_name(&file, name, &mut res) | ||
43 | } | ||
44 | if has_completions { | ||
45 | Some(res) | ||
46 | } else { | ||
47 | None | ||
33 | } | 48 | } |
49 | } | ||
34 | 50 | ||
35 | let mut res = Vec::new(); | 51 | fn complete_name_ref(file: &File, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) { |
52 | if !is_node::<ast::Path>(name_ref.syntax()) { | ||
53 | return; | ||
54 | } | ||
36 | if let Some(fn_def) = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next() { | 55 | if let Some(fn_def) = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next() { |
37 | complete_expr_keywords(&file, fn_def, name_ref, &mut res); | 56 | complete_expr_keywords(&file, fn_def, name_ref, acc); |
38 | let scopes = FnScopes::new(fn_def); | 57 | let scopes = FnScopes::new(fn_def); |
39 | complete_fn(name_ref, &scopes, &mut res); | 58 | complete_fn(name_ref, &scopes, acc); |
40 | } | 59 | } |
41 | if let Some(root) = ancestors(name_ref.syntax()).filter_map(ast::Root::cast).next() { | 60 | if let Some(root) = ancestors(name_ref.syntax()).filter_map(ast::Root::cast).next() { |
42 | let scope = ModuleScope::new(root); | 61 | let scope = ModuleScope::new(root); |
43 | res.extend( | 62 | acc.extend( |
44 | scope.entries().iter() | 63 | scope.entries().iter() |
45 | .filter(|entry| entry.syntax() != name_ref.syntax()) | 64 | .filter(|entry| entry.syntax() != name_ref.syntax()) |
46 | .map(|entry| CompletionItem { | 65 | .map(|entry| CompletionItem { |
47 | name: entry.name().to_string(), | 66 | label: entry.name().to_string(), |
67 | lookup: None, | ||
48 | snippet: None, | 68 | snippet: None, |
49 | }) | 69 | }) |
50 | ); | 70 | ); |
51 | } | 71 | } |
52 | Some(res) | ||
53 | } | 72 | } |
54 | 73 | ||
55 | fn is_single_segment(name_ref: ast::NameRef) -> bool { | 74 | fn complete_name(_file: &File, name: ast::Name, acc: &mut Vec<CompletionItem>) { |
56 | match ancestors(name_ref.syntax()).filter_map(ast::Path::cast).next() { | 75 | if !is_node::<ast::Param>(name.syntax()) { |
76 | return; | ||
77 | } | ||
78 | |||
79 | let mut params = HashMap::new(); | ||
80 | for node in ancestors(name.syntax()) { | ||
81 | let _ = visitor_ctx(&mut params) | ||
82 | .visit::<ast::Root, _>(process) | ||
83 | .accept(node); | ||
84 | } | ||
85 | params.into_iter() | ||
86 | .filter_map(|(label, (count, param))| { | ||
87 | let lookup = param.pat()?.syntax().text().to_string(); | ||
88 | if count < 2 { None } else { Some((label, lookup)) } | ||
89 | }) | ||
90 | .for_each(|(label, lookup)| { | ||
91 | acc.push(CompletionItem { | ||
92 | label, lookup: Some(lookup), snippet: None | ||
93 | }) | ||
94 | }); | ||
95 | |||
96 | fn process<'a, N: ast::FnDefOwner<'a>>(node: N, params: &mut HashMap<String, (u32, ast::Param<'a>)>) { | ||
97 | node.functions() | ||
98 | .filter_map(|it| it.param_list()) | ||
99 | .flat_map(|it| it.params()) | ||
100 | .for_each(|param| { | ||
101 | let text = param.syntax().text().to_string(); | ||
102 | params.entry(text) | ||
103 | .or_insert((0, param)) | ||
104 | .0 += 1; | ||
105 | }) | ||
106 | } | ||
107 | } | ||
108 | |||
109 | fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { | ||
110 | match ancestors(node).filter_map(N::cast).next() { | ||
57 | None => false, | 111 | None => false, |
58 | Some(path) => { | 112 | Some(n) => n.syntax().range() == node.range(), |
59 | path.syntax().range() == name_ref.syntax().range() | ||
60 | } | ||
61 | } | 113 | } |
62 | } | 114 | } |
63 | 115 | ||
116 | |||
64 | fn complete_expr_keywords(file: &File, fn_def: ast::FnDef, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) { | 117 | fn complete_expr_keywords(file: &File, fn_def: ast::FnDef, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) { |
65 | acc.push(keyword("if", "if $0 {}")); | 118 | acc.push(keyword("if", "if $0 {}")); |
66 | acc.push(keyword("match", "match $0 {}")); | 119 | acc.push(keyword("match", "match $0 {}")); |
@@ -127,7 +180,8 @@ fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<Complet | |||
127 | 180 | ||
128 | fn keyword(kw: &str, snip: &str) -> CompletionItem { | 181 | fn keyword(kw: &str, snip: &str) -> CompletionItem { |
129 | CompletionItem { | 182 | CompletionItem { |
130 | name: kw.to_string(), | 183 | label: kw.to_string(), |
184 | lookup: None, | ||
131 | snippet: Some(snip.to_string()), | 185 | snippet: Some(snip.to_string()), |
132 | } | 186 | } |
133 | } | 187 | } |
@@ -139,13 +193,15 @@ fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<Completi | |||
139 | .flat_map(|scope| scopes.entries(scope).iter()) | 193 | .flat_map(|scope| scopes.entries(scope).iter()) |
140 | .filter(|entry| shadowed.insert(entry.name())) | 194 | .filter(|entry| shadowed.insert(entry.name())) |
141 | .map(|entry| CompletionItem { | 195 | .map(|entry| CompletionItem { |
142 | name: entry.name().to_string(), | 196 | label: entry.name().to_string(), |
197 | lookup: None, | ||
143 | snippet: None, | 198 | snippet: None, |
144 | }) | 199 | }) |
145 | ); | 200 | ); |
146 | if scopes.self_param.is_some() { | 201 | if scopes.self_param.is_some() { |
147 | acc.push(CompletionItem { | 202 | acc.push(CompletionItem { |
148 | name: "self".to_string(), | 203 | label: "self".to_string(), |
204 | lookup: None, | ||
149 | snippet: None, | 205 | snippet: None, |
150 | }) | 206 | }) |
151 | } | 207 | } |
@@ -186,9 +242,9 @@ mod tests { | |||
186 | 1 + <|>; | 242 | 1 + <|>; |
187 | let z = (); | 243 | let z = (); |
188 | } | 244 | } |
189 | ", r#"[CompletionItem { name: "y", snippet: None }, | 245 | ", r#"[CompletionItem { label: "y", lookup: None, snippet: None }, |
190 | CompletionItem { name: "x", snippet: None }, | 246 | CompletionItem { label: "x", lookup: None, snippet: None }, |
191 | CompletionItem { name: "quux", snippet: None }]"#); | 247 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#); |
192 | } | 248 | } |
193 | 249 | ||
194 | #[test] | 250 | #[test] |
@@ -203,9 +259,9 @@ mod tests { | |||
203 | 1 + <|> | 259 | 1 + <|> |
204 | } | 260 | } |
205 | } | 261 | } |
206 | ", r#"[CompletionItem { name: "b", snippet: None }, | 262 | ", r#"[CompletionItem { label: "b", lookup: None, snippet: None }, |
207 | CompletionItem { name: "a", snippet: None }, | 263 | CompletionItem { label: "a", lookup: None, snippet: None }, |
208 | CompletionItem { name: "quux", snippet: None }]"#); | 264 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#); |
209 | } | 265 | } |
210 | 266 | ||
211 | #[test] | 267 | #[test] |
@@ -216,8 +272,8 @@ mod tests { | |||
216 | <|> | 272 | <|> |
217 | } | 273 | } |
218 | } | 274 | } |
219 | ", r#"[CompletionItem { name: "x", snippet: None }, | 275 | ", r#"[CompletionItem { label: "x", lookup: None, snippet: None }, |
220 | CompletionItem { name: "quux", snippet: None }]"#); | 276 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#); |
221 | } | 277 | } |
222 | 278 | ||
223 | #[test] | 279 | #[test] |
@@ -228,9 +284,9 @@ mod tests { | |||
228 | fn quux() { | 284 | fn quux() { |
229 | <|> | 285 | <|> |
230 | } | 286 | } |
231 | ", r#"[CompletionItem { name: "Foo", snippet: None }, | 287 | ", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, |
232 | CompletionItem { name: "Baz", snippet: None }, | 288 | CompletionItem { label: "Baz", lookup: None, snippet: None }, |
233 | CompletionItem { name: "quux", snippet: None }]"#); | 289 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#); |
234 | } | 290 | } |
235 | 291 | ||
236 | #[test] | 292 | #[test] |
@@ -245,8 +301,8 @@ mod tests { | |||
245 | check_scope_completion(r" | 301 | check_scope_completion(r" |
246 | struct Foo; | 302 | struct Foo; |
247 | fn x() -> <|> | 303 | fn x() -> <|> |
248 | ", r#"[CompletionItem { name: "Foo", snippet: None }, | 304 | ", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, |
249 | CompletionItem { name: "x", snippet: None }]"#) | 305 | CompletionItem { label: "x", lookup: None, snippet: None }]"#) |
250 | } | 306 | } |
251 | 307 | ||
252 | #[test] | 308 | #[test] |
@@ -259,15 +315,15 @@ mod tests { | |||
259 | <|> | 315 | <|> |
260 | } | 316 | } |
261 | } | 317 | } |
262 | ", r#"[CompletionItem { name: "bar", snippet: None }, | 318 | ", r#"[CompletionItem { label: "bar", lookup: None, snippet: None }, |
263 | CompletionItem { name: "foo", snippet: None }]"#) | 319 | CompletionItem { label: "foo", lookup: None, snippet: None }]"#) |
264 | } | 320 | } |
265 | 321 | ||
266 | #[test] | 322 | #[test] |
267 | fn test_complete_self() { | 323 | fn test_complete_self() { |
268 | check_scope_completion(r" | 324 | check_scope_completion(r" |
269 | impl S { fn foo(&self) { <|> } } | 325 | impl S { fn foo(&self) { <|> } } |
270 | ", r#"[CompletionItem { name: "self", snippet: None }]"#) | 326 | ", r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#) |
271 | } | 327 | } |
272 | 328 | ||
273 | #[test] | 329 | #[test] |
@@ -276,11 +332,11 @@ mod tests { | |||
276 | fn quux() { | 332 | fn quux() { |
277 | <|> | 333 | <|> |
278 | } | 334 | } |
279 | ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") }, | 335 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, |
280 | CompletionItem { name: "match", snippet: Some("match $0 {}") }, | 336 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, |
281 | CompletionItem { name: "while", snippet: Some("while $0 {}") }, | 337 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, |
282 | CompletionItem { name: "loop", snippet: Some("loop {$0}") }, | 338 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, |
283 | CompletionItem { name: "return", snippet: Some("return") }]"#); | 339 | CompletionItem { label: "return", lookup: None, snippet: Some("return") }]"#); |
284 | } | 340 | } |
285 | 341 | ||
286 | #[test] | 342 | #[test] |
@@ -291,13 +347,13 @@ mod tests { | |||
291 | () | 347 | () |
292 | } <|> | 348 | } <|> |
293 | } | 349 | } |
294 | ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") }, | 350 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, |
295 | CompletionItem { name: "match", snippet: Some("match $0 {}") }, | 351 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, |
296 | CompletionItem { name: "while", snippet: Some("while $0 {}") }, | 352 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, |
297 | CompletionItem { name: "loop", snippet: Some("loop {$0}") }, | 353 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, |
298 | CompletionItem { name: "else", snippet: Some("else {$0}") }, | 354 | CompletionItem { label: "else", lookup: None, snippet: Some("else {$0}") }, |
299 | CompletionItem { name: "else if", snippet: Some("else if $0 {}") }, | 355 | CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") }, |
300 | CompletionItem { name: "return", snippet: Some("return") }]"#); | 356 | CompletionItem { label: "return", lookup: None, snippet: Some("return") }]"#); |
301 | } | 357 | } |
302 | 358 | ||
303 | #[test] | 359 | #[test] |
@@ -307,21 +363,21 @@ mod tests { | |||
307 | <|> | 363 | <|> |
308 | 92 | 364 | 92 |
309 | } | 365 | } |
310 | ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") }, | 366 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, |
311 | CompletionItem { name: "match", snippet: Some("match $0 {}") }, | 367 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, |
312 | CompletionItem { name: "while", snippet: Some("while $0 {}") }, | 368 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, |
313 | CompletionItem { name: "loop", snippet: Some("loop {$0}") }, | 369 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, |
314 | CompletionItem { name: "return", snippet: Some("return $0;") }]"#); | 370 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") }]"#); |
315 | check_snippet_completion(r" | 371 | check_snippet_completion(r" |
316 | fn quux() { | 372 | fn quux() { |
317 | <|> | 373 | <|> |
318 | 92 | 374 | 92 |
319 | } | 375 | } |
320 | ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") }, | 376 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, |
321 | CompletionItem { name: "match", snippet: Some("match $0 {}") }, | 377 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, |
322 | CompletionItem { name: "while", snippet: Some("while $0 {}") }, | 378 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, |
323 | CompletionItem { name: "loop", snippet: Some("loop {$0}") }, | 379 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, |
324 | CompletionItem { name: "return", snippet: Some("return;") }]"#); | 380 | CompletionItem { label: "return", lookup: None, snippet: Some("return;") }]"#); |
325 | } | 381 | } |
326 | 382 | ||
327 | #[test] | 383 | #[test] |
@@ -332,11 +388,11 @@ mod tests { | |||
332 | () => <|> | 388 | () => <|> |
333 | } | 389 | } |
334 | } | 390 | } |
335 | ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") }, | 391 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, |
336 | CompletionItem { name: "match", snippet: Some("match $0 {}") }, | 392 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, |
337 | CompletionItem { name: "while", snippet: Some("while $0 {}") }, | 393 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, |
338 | CompletionItem { name: "loop", snippet: Some("loop {$0}") }, | 394 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, |
339 | CompletionItem { name: "return", snippet: Some("return $0") }]"#); | 395 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }]"#); |
340 | } | 396 | } |
341 | 397 | ||
342 | #[test] | 398 | #[test] |
@@ -345,21 +401,35 @@ mod tests { | |||
345 | fn quux() -> i32 { | 401 | fn quux() -> i32 { |
346 | loop { <|> } | 402 | loop { <|> } |
347 | } | 403 | } |
348 | ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") }, | 404 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, |
349 | CompletionItem { name: "match", snippet: Some("match $0 {}") }, | 405 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, |
350 | CompletionItem { name: "while", snippet: Some("while $0 {}") }, | 406 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, |
351 | CompletionItem { name: "loop", snippet: Some("loop {$0}") }, | 407 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, |
352 | CompletionItem { name: "continue", snippet: Some("continue") }, | 408 | CompletionItem { label: "continue", lookup: None, snippet: Some("continue") }, |
353 | CompletionItem { name: "break", snippet: Some("break") }, | 409 | CompletionItem { label: "break", lookup: None, snippet: Some("break") }, |
354 | CompletionItem { name: "return", snippet: Some("return $0") }]"#); | 410 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }]"#); |
355 | check_snippet_completion(r" | 411 | check_snippet_completion(r" |
356 | fn quux() -> i32 { | 412 | fn quux() -> i32 { |
357 | loop { || { <|> } } | 413 | loop { || { <|> } } |
358 | } | 414 | } |
359 | ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") }, | 415 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, |
360 | CompletionItem { name: "match", snippet: Some("match $0 {}") }, | 416 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, |
361 | CompletionItem { name: "while", snippet: Some("while $0 {}") }, | 417 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, |
362 | CompletionItem { name: "loop", snippet: Some("loop {$0}") }, | 418 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, |
363 | CompletionItem { name: "return", snippet: Some("return $0") }]"#); | 419 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }]"#); |
420 | } | ||
421 | |||
422 | #[test] | ||
423 | fn test_param_completion() { | ||
424 | check_scope_completion(r" | ||
425 | fn foo(file_id: FileId) {} | ||
426 | fn bar(file_id: FileId) {} | ||
427 | fn baz(file<|>) {} | ||
428 | ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | ||
429 | check_scope_completion(r" | ||
430 | fn foo(file_id: FileId) {} | ||
431 | fn bar(file_id: FileId) {} | ||
432 | fn baz(file<|>, x: i32) {} | ||
433 | ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | ||
364 | } | 434 | } |
365 | } | 435 | } |