diff options
Diffstat (limited to 'crates/ra_editor/src/completion.rs')
-rw-r--r-- | crates/ra_editor/src/completion.rs | 178 |
1 files changed, 114 insertions, 64 deletions
diff --git a/crates/ra_editor/src/completion.rs b/crates/ra_editor/src/completion.rs index b6095dca9..86ef46ebd 100644 --- a/crates/ra_editor/src/completion.rs +++ b/crates/ra_editor/src/completion.rs | |||
@@ -1,17 +1,18 @@ | |||
1 | use rustc_hash::{FxHashMap, FxHashSet}; | 1 | use rustc_hash::{FxHashMap, FxHashSet}; |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | File, TextUnit, AstNode, SyntaxNodeRef, SyntaxKind::*, | 4 | algo::visit::{visitor, visitor_ctx, Visitor, VisitorCtx}, |
5 | ast::{self, LoopBodyOwner, ModuleItemOwner}, | 5 | ast::{self, LoopBodyOwner, ModuleItemOwner}, |
6 | algo::{ | ||
7 | visit::{visitor, Visitor, visitor_ctx, VisitorCtx}, | ||
8 | }, | ||
9 | text_utils::is_subrange, | 6 | text_utils::is_subrange, |
7 | AstNode, File, | ||
8 | SyntaxKind::*, | ||
9 | SyntaxNodeRef, TextUnit, | ||
10 | }; | 10 | }; |
11 | 11 | ||
12 | use crate::{ | 12 | use crate::{ |
13 | AtomEdit, find_node_at_offset, | 13 | find_node_at_offset, |
14 | scope::{FnScopes, ModuleScope}, | 14 | scope::{FnScopes, ModuleScope}, |
15 | AtomEdit, | ||
15 | }; | 16 | }; |
16 | 17 | ||
17 | #[derive(Debug)] | 18 | #[derive(Debug)] |
@@ -21,7 +22,7 @@ pub struct CompletionItem { | |||
21 | /// What string is used for filtering, defaults to label | 22 | /// What string is used for filtering, defaults to label |
22 | pub lookup: Option<String>, | 23 | pub lookup: Option<String>, |
23 | /// What is inserted, defaults to label | 24 | /// What is inserted, defaults to label |
24 | pub snippet: Option<String> | 25 | pub snippet: Option<String>, |
25 | } | 26 | } |
26 | 27 | ||
27 | pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionItem>> { | 28 | pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionItem>> { |
@@ -40,7 +41,12 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI | |||
40 | param_completions(name_ref.syntax(), &mut res); | 41 | param_completions(name_ref.syntax(), &mut res); |
41 | } | 42 | } |
42 | let name_range = name_ref.syntax().range(); | 43 | let name_range = name_ref.syntax().range(); |
43 | let top_node = name_ref.syntax().ancestors().take_while(|it| it.range() == name_range).last().unwrap(); | 44 | let top_node = name_ref |
45 | .syntax() | ||
46 | .ancestors() | ||
47 | .take_while(|it| it.range() == name_range) | ||
48 | .last() | ||
49 | .unwrap(); | ||
44 | match top_node.parent().map(|it| it.kind()) { | 50 | match top_node.parent().map(|it| it.kind()) { |
45 | Some(ROOT) | Some(ITEM_LIST) => complete_mod_item_snippets(&mut res), | 51 | Some(ROOT) | Some(ITEM_LIST) => complete_mod_item_snippets(&mut res), |
46 | _ => (), | 52 | _ => (), |
@@ -68,21 +74,23 @@ fn complete_name_ref(file: &File, name_ref: ast::NameRef, acc: &mut Vec<Completi | |||
68 | if let Some(items) = visitor() | 74 | if let Some(items) = visitor() |
69 | .visit::<ast::Root, _>(|it| Some(it.items())) | 75 | .visit::<ast::Root, _>(|it| Some(it.items())) |
70 | .visit::<ast::Module, _>(|it| Some(it.item_list()?.items())) | 76 | .visit::<ast::Module, _>(|it| Some(it.item_list()?.items())) |
71 | .accept(node) { | 77 | .accept(node) |
78 | { | ||
72 | if let Some(items) = items { | 79 | if let Some(items) = items { |
73 | let scope = ModuleScope::new(items); | 80 | let scope = ModuleScope::new(items); |
74 | acc.extend( | 81 | acc.extend( |
75 | scope.entries().iter() | 82 | scope |
83 | .entries() | ||
84 | .iter() | ||
76 | .filter(|entry| entry.syntax() != name_ref.syntax()) | 85 | .filter(|entry| entry.syntax() != name_ref.syntax()) |
77 | .map(|entry| CompletionItem { | 86 | .map(|entry| CompletionItem { |
78 | label: entry.name().to_string(), | 87 | label: entry.name().to_string(), |
79 | lookup: None, | 88 | lookup: None, |
80 | snippet: None, | 89 | snippet: None, |
81 | }) | 90 | }), |
82 | ); | 91 | ); |
83 | } | 92 | } |
84 | break; | 93 | break; |
85 | |||
86 | } else if !visited_fn { | 94 | } else if !visited_fn { |
87 | if let Some(fn_def) = ast::FnDef::cast(node) { | 95 | if let Some(fn_def) = ast::FnDef::cast(node) { |
88 | visited_fn = true; | 96 | visited_fn = true; |
@@ -103,26 +111,34 @@ fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) { | |||
103 | .visit::<ast::ItemList, _>(process) | 111 | .visit::<ast::ItemList, _>(process) |
104 | .accept(node); | 112 | .accept(node); |
105 | } | 113 | } |
106 | params.into_iter() | 114 | params |
115 | .into_iter() | ||
107 | .filter_map(|(label, (count, param))| { | 116 | .filter_map(|(label, (count, param))| { |
108 | let lookup = param.pat()?.syntax().text().to_string(); | 117 | let lookup = param.pat()?.syntax().text().to_string(); |
109 | if count < 2 { None } else { Some((label, lookup)) } | 118 | if count < 2 { |
119 | None | ||
120 | } else { | ||
121 | Some((label, lookup)) | ||
122 | } | ||
110 | }) | 123 | }) |
111 | .for_each(|(label, lookup)| { | 124 | .for_each(|(label, lookup)| { |
112 | acc.push(CompletionItem { | 125 | acc.push(CompletionItem { |
113 | label, lookup: Some(lookup), snippet: None | 126 | label, |
127 | lookup: Some(lookup), | ||
128 | snippet: None, | ||
114 | }) | 129 | }) |
115 | }); | 130 | }); |
116 | 131 | ||
117 | fn process<'a, N: ast::FnDefOwner<'a>>(node: N, params: &mut FxHashMap<String, (u32, ast::Param<'a>)>) { | 132 | fn process<'a, N: ast::FnDefOwner<'a>>( |
133 | node: N, | ||
134 | params: &mut FxHashMap<String, (u32, ast::Param<'a>)>, | ||
135 | ) { | ||
118 | node.functions() | 136 | node.functions() |
119 | .filter_map(|it| it.param_list()) | 137 | .filter_map(|it| it.param_list()) |
120 | .flat_map(|it| it.params()) | 138 | .flat_map(|it| it.params()) |
121 | .for_each(|param| { | 139 | .for_each(|param| { |
122 | let text = param.syntax().text().to_string(); | 140 | let text = param.syntax().text().to_string(); |
123 | params.entry(text) | 141 | params.entry(text).or_insert((0, param)).0 += 1; |
124 | .or_insert((0, param)) | ||
125 | .0 += 1; | ||
126 | }) | 142 | }) |
127 | } | 143 | } |
128 | } | 144 | } |
@@ -134,8 +150,12 @@ fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { | |||
134 | } | 150 | } |
135 | } | 151 | } |
136 | 152 | ||
137 | 153 | fn complete_expr_keywords( | |
138 | fn complete_expr_keywords(file: &File, fn_def: ast::FnDef, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) { | 154 | file: &File, |
155 | fn_def: ast::FnDef, | ||
156 | name_ref: ast::NameRef, | ||
157 | acc: &mut Vec<CompletionItem>, | ||
158 | ) { | ||
139 | acc.push(keyword("if", "if $0 {}")); | 159 | acc.push(keyword("if", "if $0 {}")); |
140 | acc.push(keyword("match", "match $0 {}")); | 160 | acc.push(keyword("match", "match $0 {}")); |
141 | acc.push(keyword("while", "while $0 {}")); | 161 | acc.push(keyword("while", "while $0 {}")); |
@@ -186,9 +206,14 @@ fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<Complet | |||
186 | // return None; | 206 | // return None; |
187 | // } | 207 | // } |
188 | 208 | ||
189 | let is_stmt = match name_ref.syntax().ancestors().filter_map(ast::ExprStmt::cast).next() { | 209 | let is_stmt = match name_ref |
210 | .syntax() | ||
211 | .ancestors() | ||
212 | .filter_map(ast::ExprStmt::cast) | ||
213 | .next() | ||
214 | { | ||
190 | None => false, | 215 | None => false, |
191 | Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range() | 216 | Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(), |
192 | }; | 217 | }; |
193 | let snip = match (is_stmt, fn_def.ret_type().is_some()) { | 218 | let snip = match (is_stmt, fn_def.ret_type().is_some()) { |
194 | (true, true) => "return $0;", | 219 | (true, true) => "return $0;", |
@@ -209,39 +234,37 @@ fn keyword(kw: &str, snip: &str) -> CompletionItem { | |||
209 | 234 | ||
210 | fn complete_expr_snippets(acc: &mut Vec<CompletionItem>) { | 235 | fn complete_expr_snippets(acc: &mut Vec<CompletionItem>) { |
211 | acc.push(CompletionItem { | 236 | acc.push(CompletionItem { |
212 | label: "pd".to_string(), | 237 | label: "pd".to_string(), |
213 | lookup: None, | 238 | lookup: None, |
214 | snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()), | 239 | snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()), |
215 | } | 240 | }); |
216 | ); | ||
217 | acc.push(CompletionItem { | 241 | acc.push(CompletionItem { |
218 | label: "ppd".to_string(), | 242 | label: "ppd".to_string(), |
219 | lookup: None, | 243 | lookup: None, |
220 | snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()), | 244 | snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()), |
221 | } | 245 | }); |
222 | ); | ||
223 | } | 246 | } |
224 | 247 | ||
225 | fn complete_mod_item_snippets(acc: &mut Vec<CompletionItem>) { | 248 | fn complete_mod_item_snippets(acc: &mut Vec<CompletionItem>) { |
226 | acc.push(CompletionItem { | 249 | acc.push(CompletionItem { |
227 | label: "tfn".to_string(), | 250 | label: "tfn".to_string(), |
228 | lookup: None, | 251 | lookup: None, |
229 | snippet: Some("#[test]\nfn $1() {\n $0\n}".to_string()), | 252 | snippet: Some("#[test]\nfn $1() {\n $0\n}".to_string()), |
230 | } | 253 | }); |
231 | ); | ||
232 | } | 254 | } |
233 | 255 | ||
234 | fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) { | 256 | fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) { |
235 | let mut shadowed = FxHashSet::default(); | 257 | let mut shadowed = FxHashSet::default(); |
236 | acc.extend( | 258 | acc.extend( |
237 | scopes.scope_chain(name_ref.syntax()) | 259 | scopes |
260 | .scope_chain(name_ref.syntax()) | ||
238 | .flat_map(|scope| scopes.entries(scope).iter()) | 261 | .flat_map(|scope| scopes.entries(scope).iter()) |
239 | .filter(|entry| shadowed.insert(entry.name())) | 262 | .filter(|entry| shadowed.insert(entry.name())) |
240 | .map(|entry| CompletionItem { | 263 | .map(|entry| CompletionItem { |
241 | label: entry.name().to_string(), | 264 | label: entry.name().to_string(), |
242 | lookup: None, | 265 | lookup: None, |
243 | snippet: None, | 266 | snippet: None, |
244 | }) | 267 | }), |
245 | ); | 268 | ); |
246 | if scopes.self_param.is_some() { | 269 | if scopes.self_param.is_some() { |
247 | acc.push(CompletionItem { | 270 | acc.push(CompletionItem { |
@@ -281,20 +304,24 @@ mod tests { | |||
281 | 304 | ||
282 | #[test] | 305 | #[test] |
283 | fn test_completion_let_scope() { | 306 | fn test_completion_let_scope() { |
284 | check_scope_completion(r" | 307 | check_scope_completion( |
308 | r" | ||
285 | fn quux(x: i32) { | 309 | fn quux(x: i32) { |
286 | let y = 92; | 310 | let y = 92; |
287 | 1 + <|>; | 311 | 1 + <|>; |
288 | let z = (); | 312 | let z = (); |
289 | } | 313 | } |
290 | ", r#"[CompletionItem { label: "y", lookup: None, snippet: None }, | 314 | ", |
315 | r#"[CompletionItem { label: "y", lookup: None, snippet: None }, | ||
291 | CompletionItem { label: "x", lookup: None, snippet: None }, | 316 | CompletionItem { label: "x", lookup: None, snippet: None }, |
292 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#); | 317 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, |
318 | ); | ||
293 | } | 319 | } |
294 | 320 | ||
295 | #[test] | 321 | #[test] |
296 | fn test_completion_if_let_scope() { | 322 | fn test_completion_if_let_scope() { |
297 | check_scope_completion(r" | 323 | check_scope_completion( |
324 | r" | ||
298 | fn quux() { | 325 | fn quux() { |
299 | if let Some(x) = foo() { | 326 | if let Some(x) = foo() { |
300 | let y = 92; | 327 | let y = 92; |
@@ -304,67 +331,85 @@ mod tests { | |||
304 | 1 + <|> | 331 | 1 + <|> |
305 | } | 332 | } |
306 | } | 333 | } |
307 | ", r#"[CompletionItem { label: "b", lookup: None, snippet: None }, | 334 | ", |
335 | r#"[CompletionItem { label: "b", lookup: None, snippet: None }, | ||
308 | CompletionItem { label: "a", lookup: None, snippet: None }, | 336 | CompletionItem { label: "a", lookup: None, snippet: None }, |
309 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#); | 337 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, |
338 | ); | ||
310 | } | 339 | } |
311 | 340 | ||
312 | #[test] | 341 | #[test] |
313 | fn test_completion_for_scope() { | 342 | fn test_completion_for_scope() { |
314 | check_scope_completion(r" | 343 | check_scope_completion( |
344 | r" | ||
315 | fn quux() { | 345 | fn quux() { |
316 | for x in &[1, 2, 3] { | 346 | for x in &[1, 2, 3] { |
317 | <|> | 347 | <|> |
318 | } | 348 | } |
319 | } | 349 | } |
320 | ", r#"[CompletionItem { label: "x", lookup: None, snippet: None }, | 350 | ", |
321 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#); | 351 | r#"[CompletionItem { label: "x", lookup: None, snippet: None }, |
352 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
353 | ); | ||
322 | } | 354 | } |
323 | 355 | ||
324 | #[test] | 356 | #[test] |
325 | fn test_completion_mod_scope() { | 357 | fn test_completion_mod_scope() { |
326 | check_scope_completion(r" | 358 | check_scope_completion( |
359 | r" | ||
327 | struct Foo; | 360 | struct Foo; |
328 | enum Baz {} | 361 | enum Baz {} |
329 | fn quux() { | 362 | fn quux() { |
330 | <|> | 363 | <|> |
331 | } | 364 | } |
332 | ", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, | 365 | ", |
366 | r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, | ||
333 | CompletionItem { label: "Baz", lookup: None, snippet: None }, | 367 | CompletionItem { label: "Baz", lookup: None, snippet: None }, |
334 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#); | 368 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, |
369 | ); | ||
335 | } | 370 | } |
336 | 371 | ||
337 | #[test] | 372 | #[test] |
338 | fn test_completion_mod_scope_no_self_use() { | 373 | fn test_completion_mod_scope_no_self_use() { |
339 | check_scope_completion(r" | 374 | check_scope_completion( |
375 | r" | ||
340 | use foo<|>; | 376 | use foo<|>; |
341 | ", r#"[]"#); | 377 | ", |
378 | r#"[]"#, | ||
379 | ); | ||
342 | } | 380 | } |
343 | 381 | ||
344 | #[test] | 382 | #[test] |
345 | fn test_completion_mod_scope_nested() { | 383 | fn test_completion_mod_scope_nested() { |
346 | check_scope_completion(r" | 384 | check_scope_completion( |
385 | r" | ||
347 | struct Foo; | 386 | struct Foo; |
348 | mod m { | 387 | mod m { |
349 | struct Bar; | 388 | struct Bar; |
350 | fn quux() { <|> } | 389 | fn quux() { <|> } |
351 | } | 390 | } |
352 | ", r#"[CompletionItem { label: "Bar", lookup: None, snippet: None }, | 391 | ", |
353 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#); | 392 | r#"[CompletionItem { label: "Bar", lookup: None, snippet: None }, |
393 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
394 | ); | ||
354 | } | 395 | } |
355 | 396 | ||
356 | #[test] | 397 | #[test] |
357 | fn test_complete_type() { | 398 | fn test_complete_type() { |
358 | check_scope_completion(r" | 399 | check_scope_completion( |
400 | r" | ||
359 | struct Foo; | 401 | struct Foo; |
360 | fn x() -> <|> | 402 | fn x() -> <|> |
361 | ", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, | 403 | ", |
362 | CompletionItem { label: "x", lookup: None, snippet: None }]"#) | 404 | r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, |
405 | CompletionItem { label: "x", lookup: None, snippet: None }]"#, | ||
406 | ) | ||
363 | } | 407 | } |
364 | 408 | ||
365 | #[test] | 409 | #[test] |
366 | fn test_complete_shadowing() { | 410 | fn test_complete_shadowing() { |
367 | check_scope_completion(r" | 411 | check_scope_completion( |
412 | r" | ||
368 | fn foo() -> { | 413 | fn foo() -> { |
369 | let bar = 92; | 414 | let bar = 92; |
370 | { | 415 | { |
@@ -372,15 +417,20 @@ mod tests { | |||
372 | <|> | 417 | <|> |
373 | } | 418 | } |
374 | } | 419 | } |
375 | ", r#"[CompletionItem { label: "bar", lookup: None, snippet: None }, | 420 | ", |
376 | CompletionItem { label: "foo", lookup: None, snippet: None }]"#) | 421 | r#"[CompletionItem { label: "bar", lookup: None, snippet: None }, |
422 | CompletionItem { label: "foo", lookup: None, snippet: None }]"#, | ||
423 | ) | ||
377 | } | 424 | } |
378 | 425 | ||
379 | #[test] | 426 | #[test] |
380 | fn test_complete_self() { | 427 | fn test_complete_self() { |
381 | check_scope_completion(r" | 428 | check_scope_completion( |
429 | r" | ||
382 | impl S { fn foo(&self) { <|> } } | 430 | impl S { fn foo(&self) { <|> } } |
383 | ", r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#) | 431 | ", |
432 | r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#, | ||
433 | ) | ||
384 | } | 434 | } |
385 | 435 | ||
386 | #[test] | 436 | #[test] |