diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 4 | ||||
-rw-r--r-- | crates/ra_assists/src/auto_import.rs | 203 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir/src/name.rs | 13 | ||||
-rw-r--r-- | crates/ra_hir/src/source_binder.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide_api/src/completion/complete_scope.rs | 120 | ||||
-rw-r--r-- | crates/ra_ide_api/src/completion/completion_context.rs | 4 |
7 files changed, 271 insertions, 77 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index f46de61eb..e744e82d0 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -144,6 +144,10 @@ impl AssistBuilder { | |||
144 | self.replace(node.range(), replace_with) | 144 | self.replace(node.range(), replace_with) |
145 | } | 145 | } |
146 | 146 | ||
147 | pub(crate) fn set_edit_builder(&mut self, edit: TextEditBuilder) { | ||
148 | self.edit = edit; | ||
149 | } | ||
150 | |||
147 | #[allow(unused)] | 151 | #[allow(unused)] |
148 | pub(crate) fn delete(&mut self, range: TextRange) { | 152 | pub(crate) fn delete(&mut self, range: TextRange) { |
149 | self.edit.delete(range) | 153 | self.edit.delete(range) |
diff --git a/crates/ra_assists/src/auto_import.rs b/crates/ra_assists/src/auto_import.rs index 3fdf6b0d9..7c856c19b 100644 --- a/crates/ra_assists/src/auto_import.rs +++ b/crates/ra_assists/src/auto_import.rs | |||
@@ -1,20 +1,15 @@ | |||
1 | use hir::db::HirDatabase; | 1 | use ra_text_edit::TextEditBuilder; |
2 | use hir::{ self, db::HirDatabase}; | ||
2 | 3 | ||
3 | use ra_syntax::{ | 4 | use ra_syntax::{ |
4 | ast::{ self, NameOwner }, AstNode, SyntaxNode, Direction, TextRange, | 5 | ast::{ self, NameOwner }, AstNode, SyntaxNode, Direction, TextRange, SmolStr, |
5 | SyntaxKind::{ PATH, PATH_SEGMENT, COLONCOLON, COMMA } | 6 | SyntaxKind::{ PATH, PATH_SEGMENT, COLONCOLON, COMMA } |
6 | }; | 7 | }; |
7 | use crate::{ | 8 | use crate::{ |
8 | AssistId, | 9 | AssistId, |
9 | assist_ctx::{AssistCtx, Assist, AssistBuilder}, | 10 | assist_ctx::{AssistCtx, Assist}, |
10 | }; | 11 | }; |
11 | 12 | ||
12 | fn collect_path_segments(path: &ast::Path) -> Option<Vec<&ast::PathSegment>> { | ||
13 | let mut v = Vec::new(); | ||
14 | collect_path_segments_raw(&mut v, path)?; | ||
15 | return Some(v); | ||
16 | } | ||
17 | |||
18 | fn collect_path_segments_raw<'a>( | 13 | fn collect_path_segments_raw<'a>( |
19 | segments: &mut Vec<&'a ast::PathSegment>, | 14 | segments: &mut Vec<&'a ast::PathSegment>, |
20 | mut path: &'a ast::Path, | 15 | mut path: &'a ast::Path, |
@@ -45,59 +40,43 @@ fn collect_path_segments_raw<'a>( | |||
45 | return Some(segments.len() - oldlen); | 40 | return Some(segments.len() - oldlen); |
46 | } | 41 | } |
47 | 42 | ||
48 | fn fmt_segments(segments: &[&ast::PathSegment]) -> String { | 43 | fn fmt_segments(segments: &[SmolStr]) -> String { |
49 | let mut buf = String::new(); | 44 | let mut buf = String::new(); |
50 | fmt_segments_raw(segments, &mut buf); | 45 | fmt_segments_raw(segments, &mut buf); |
51 | return buf; | 46 | return buf; |
52 | } | 47 | } |
53 | 48 | ||
54 | fn fmt_segments_raw(segments: &[&ast::PathSegment], buf: &mut String) { | 49 | fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { |
55 | let mut first = true; | 50 | let mut iter = segments.iter(); |
56 | for s in segments { | 51 | if let Some(s) = iter.next() { |
57 | if !first { | 52 | buf.push_str(s); |
58 | buf.push_str("::"); | 53 | } |
59 | } | 54 | for s in iter { |
60 | match s.kind() { | 55 | buf.push_str("::"); |
61 | Some(ast::PathSegmentKind::Name(nameref)) => buf.push_str(nameref.text()), | 56 | buf.push_str(s); |
62 | Some(ast::PathSegmentKind::SelfKw) => buf.push_str("self"), | ||
63 | Some(ast::PathSegmentKind::SuperKw) => buf.push_str("super"), | ||
64 | Some(ast::PathSegmentKind::CrateKw) => buf.push_str("crate"), | ||
65 | None => {} | ||
66 | } | ||
67 | first = false; | ||
68 | } | 57 | } |
69 | } | 58 | } |
70 | 59 | ||
71 | // Returns the numeber of common segments. | 60 | // Returns the numeber of common segments. |
72 | fn compare_path_segments(left: &[&ast::PathSegment], right: &[&ast::PathSegment]) -> usize { | 61 | fn compare_path_segments(left: &[SmolStr], right: &[&ast::PathSegment]) -> usize { |
73 | return left.iter().zip(right).filter(|(l, r)| compare_path_segment(l, r)).count(); | 62 | return left.iter().zip(right).filter(|(l, r)| compare_path_segment(l, r)).count(); |
74 | } | 63 | } |
75 | 64 | ||
76 | fn compare_path_segment(a: &ast::PathSegment, b: &ast::PathSegment) -> bool { | 65 | fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { |
77 | if let (Some(ka), Some(kb)) = (a.kind(), b.kind()) { | 66 | if let Some(kb) = b.kind() { |
78 | match (ka, kb) { | 67 | match kb { |
79 | (ast::PathSegmentKind::Name(nameref_a), ast::PathSegmentKind::Name(nameref_b)) => { | 68 | ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(), |
80 | nameref_a.text() == nameref_b.text() | 69 | ast::PathSegmentKind::SelfKw => a == "self", |
81 | } | 70 | ast::PathSegmentKind::SuperKw => a == "super", |
82 | (ast::PathSegmentKind::SelfKw, ast::PathSegmentKind::SelfKw) => true, | 71 | ast::PathSegmentKind::CrateKw => a == "crate", |
83 | (ast::PathSegmentKind::SuperKw, ast::PathSegmentKind::SuperKw) => true, | ||
84 | (ast::PathSegmentKind::CrateKw, ast::PathSegmentKind::CrateKw) => true, | ||
85 | (_, _) => false, | ||
86 | } | 72 | } |
87 | } else { | 73 | } else { |
88 | false | 74 | false |
89 | } | 75 | } |
90 | } | 76 | } |
91 | 77 | ||
92 | fn compare_path_segment_with_name(a: &ast::PathSegment, b: &ast::Name) -> bool { | 78 | fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool { |
93 | if let Some(ka) = a.kind() { | 79 | a == b.text() |
94 | return match (ka, b) { | ||
95 | (ast::PathSegmentKind::Name(nameref_a), _) => nameref_a.text() == b.text(), | ||
96 | (_, _) => false, | ||
97 | }; | ||
98 | } else { | ||
99 | false | ||
100 | } | ||
101 | } | 80 | } |
102 | 81 | ||
103 | #[derive(Copy, Clone)] | 82 | #[derive(Copy, Clone)] |
@@ -189,7 +168,7 @@ fn walk_use_tree_for_best_action<'a>( | |||
189 | current_path_segments: &mut Vec<&'a ast::PathSegment>, // buffer containing path segments | 168 | current_path_segments: &mut Vec<&'a ast::PathSegment>, // buffer containing path segments |
190 | current_parent_use_tree_list: Option<&'a ast::UseTreeList>, // will be Some value if we are in a nested import | 169 | current_parent_use_tree_list: Option<&'a ast::UseTreeList>, // will be Some value if we are in a nested import |
191 | current_use_tree: &'a ast::UseTree, // the use tree we are currently examinating | 170 | current_use_tree: &'a ast::UseTree, // the use tree we are currently examinating |
192 | target: &[&'a ast::PathSegment], // the path we want to import | 171 | target: &[SmolStr], // the path we want to import |
193 | ) -> ImportAction<'a> { | 172 | ) -> ImportAction<'a> { |
194 | // We save the number of segments in the buffer so we can restore the correct segments | 173 | // We save the number of segments in the buffer so we can restore the correct segments |
195 | // before returning. Recursive call will add segments so we need to delete them. | 174 | // before returning. Recursive call will add segments so we need to delete them. |
@@ -215,7 +194,7 @@ fn walk_use_tree_for_best_action<'a>( | |||
215 | 194 | ||
216 | // This can happen only if current_use_tree is a direct child of a UseItem | 195 | // This can happen only if current_use_tree is a direct child of a UseItem |
217 | if let Some(name) = alias.and_then(ast::NameOwner::name) { | 196 | if let Some(name) = alias.and_then(ast::NameOwner::name) { |
218 | if compare_path_segment_with_name(target[0], name) { | 197 | if compare_path_segment_with_name(&target[0], name) { |
219 | return ImportAction::Nothing; | 198 | return ImportAction::Nothing; |
220 | } | 199 | } |
221 | } | 200 | } |
@@ -344,8 +323,8 @@ fn walk_use_tree_for_best_action<'a>( | |||
344 | 323 | ||
345 | fn best_action_for_target<'b, 'a: 'b>( | 324 | fn best_action_for_target<'b, 'a: 'b>( |
346 | container: &'a SyntaxNode, | 325 | container: &'a SyntaxNode, |
347 | path: &'a ast::Path, | 326 | anchor: &'a SyntaxNode, |
348 | target: &'b [&'a ast::PathSegment], | 327 | target: &'b [SmolStr], |
349 | ) -> ImportAction<'a> { | 328 | ) -> ImportAction<'a> { |
350 | let mut storage = Vec::with_capacity(16); // this should be the only allocation | 329 | let mut storage = Vec::with_capacity(16); // this should be the only allocation |
351 | let best_action = container | 330 | let best_action = container |
@@ -362,19 +341,19 @@ fn best_action_for_target<'b, 'a: 'b>( | |||
362 | None => { | 341 | None => { |
363 | // We have no action and no UseItem was found in container so we find | 342 | // We have no action and no UseItem was found in container so we find |
364 | // another item and we use it as anchor. | 343 | // another item and we use it as anchor. |
365 | // If there are no items, we choose the target path itself as anchor. | 344 | // If there are no items above, we choose the target path itself as anchor. |
345 | // todo: we should include even whitespace blocks as anchor candidates | ||
366 | let anchor = container | 346 | let anchor = container |
367 | .children() | 347 | .children() |
368 | .find_map(ast::ModuleItem::cast) | 348 | .find(|n| n.range().start() < anchor.range().start()) |
369 | .map(AstNode::syntax) | 349 | .or(Some(anchor)); |
370 | .or(Some(path.syntax())); | ||
371 | 350 | ||
372 | return ImportAction::add_new_use(anchor, false); | 351 | return ImportAction::add_new_use(anchor, false); |
373 | } | 352 | } |
374 | } | 353 | } |
375 | } | 354 | } |
376 | 355 | ||
377 | fn make_assist(action: &ImportAction, target: &[&ast::PathSegment], edit: &mut AssistBuilder) { | 356 | fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) { |
378 | match action { | 357 | match action { |
379 | ImportAction::AddNewUse { anchor, add_after_anchor } => { | 358 | ImportAction::AddNewUse { anchor, add_after_anchor } => { |
380 | make_assist_add_new_use(anchor, *add_after_anchor, target, edit) | 359 | make_assist_add_new_use(anchor, *add_after_anchor, target, edit) |
@@ -407,8 +386,8 @@ fn make_assist(action: &ImportAction, target: &[&ast::PathSegment], edit: &mut A | |||
407 | fn make_assist_add_new_use( | 386 | fn make_assist_add_new_use( |
408 | anchor: &Option<&SyntaxNode>, | 387 | anchor: &Option<&SyntaxNode>, |
409 | after: bool, | 388 | after: bool, |
410 | target: &[&ast::PathSegment], | 389 | target: &[SmolStr], |
411 | edit: &mut AssistBuilder, | 390 | edit: &mut TextEditBuilder, |
412 | ) { | 391 | ) { |
413 | if let Some(anchor) = anchor { | 392 | if let Some(anchor) = anchor { |
414 | let indent = ra_fmt::leading_indent(anchor); | 393 | let indent = ra_fmt::leading_indent(anchor); |
@@ -435,9 +414,9 @@ fn make_assist_add_new_use( | |||
435 | 414 | ||
436 | fn make_assist_add_in_tree_list( | 415 | fn make_assist_add_in_tree_list( |
437 | tree_list: &ast::UseTreeList, | 416 | tree_list: &ast::UseTreeList, |
438 | target: &[&ast::PathSegment], | 417 | target: &[SmolStr], |
439 | add_self: bool, | 418 | add_self: bool, |
440 | edit: &mut AssistBuilder, | 419 | edit: &mut TextEditBuilder, |
441 | ) { | 420 | ) { |
442 | let last = tree_list.use_trees().last(); | 421 | let last = tree_list.use_trees().last(); |
443 | if let Some(last) = last { | 422 | if let Some(last) = last { |
@@ -464,9 +443,9 @@ fn make_assist_add_in_tree_list( | |||
464 | fn make_assist_add_nested_import( | 443 | fn make_assist_add_nested_import( |
465 | path: &ast::Path, | 444 | path: &ast::Path, |
466 | first_segment_to_split: &Option<&ast::PathSegment>, | 445 | first_segment_to_split: &Option<&ast::PathSegment>, |
467 | target: &[&ast::PathSegment], | 446 | target: &[SmolStr], |
468 | add_self: bool, | 447 | add_self: bool, |
469 | edit: &mut AssistBuilder, | 448 | edit: &mut TextEditBuilder, |
470 | ) { | 449 | ) { |
471 | let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); | 450 | let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); |
472 | if let Some(use_tree) = use_tree { | 451 | if let Some(use_tree) = use_tree { |
@@ -491,28 +470,68 @@ fn make_assist_add_nested_import( | |||
491 | buf.push_str(", "); | 470 | buf.push_str(", "); |
492 | } | 471 | } |
493 | edit.insert(start, buf); | 472 | edit.insert(start, buf); |
494 | edit.insert(end, "}"); | 473 | edit.insert(end, "}".to_string()); |
495 | } | 474 | } |
496 | } | 475 | } |
497 | 476 | ||
498 | fn apply_auto_import<'a>( | 477 | fn apply_auto_import( |
499 | container: &SyntaxNode, | 478 | container: &SyntaxNode, |
500 | path: &ast::Path, | 479 | path: &ast::Path, |
501 | target: &[&'a ast::PathSegment], | 480 | target: &[SmolStr], |
502 | edit: &mut AssistBuilder, | 481 | edit: &mut TextEditBuilder, |
503 | ) { | 482 | ) { |
504 | let action = best_action_for_target(container, path, target); | 483 | let action = best_action_for_target(container, path.syntax(), target); |
505 | make_assist(&action, target, edit); | 484 | make_assist(&action, target, edit); |
506 | if let (Some(first), Some(last)) = (target.first(), target.last()) { | 485 | if let Some(last) = path.segment() { |
507 | // Here we are assuming the assist will provide a correct use statement | 486 | // Here we are assuming the assist will provide a correct use statement |
508 | // so we can delete the path qualifier | 487 | // so we can delete the path qualifier |
509 | edit.delete(TextRange::from_to( | 488 | edit.delete(TextRange::from_to( |
510 | first.syntax().range().start(), | 489 | path.syntax().range().start(), |
511 | last.syntax().range().start(), | 490 | last.syntax().range().start(), |
512 | )); | 491 | )); |
513 | } | 492 | } |
514 | } | 493 | } |
515 | 494 | ||
495 | pub fn collect_hir_path_segments(path: &hir::Path) -> Vec<SmolStr> { | ||
496 | let mut ps = Vec::<SmolStr>::with_capacity(10); | ||
497 | match path.kind { | ||
498 | hir::PathKind::Abs => ps.push("".into()), | ||
499 | hir::PathKind::Crate => ps.push("crate".into()), | ||
500 | hir::PathKind::Plain => {} | ||
501 | hir::PathKind::Self_ => ps.push("self".into()), | ||
502 | hir::PathKind::Super => ps.push("super".into()), | ||
503 | } | ||
504 | for s in path.segments.iter() { | ||
505 | ps.push(s.name.to_string().into()); | ||
506 | } | ||
507 | ps | ||
508 | } | ||
509 | |||
510 | // This function produces sequence of text edits into edit | ||
511 | // to import the target path in the most appropriate scope given | ||
512 | // the cursor position | ||
513 | pub fn auto_import_text_edit( | ||
514 | // Ideally the position of the cursor, used to | ||
515 | position: &SyntaxNode, | ||
516 | // The statement to use as anchor (last resort) | ||
517 | anchor: &SyntaxNode, | ||
518 | // The path to import as a sequence of strings | ||
519 | target: &[SmolStr], | ||
520 | edit: &mut TextEditBuilder, | ||
521 | ) { | ||
522 | let container = position.ancestors().find_map(|n| { | ||
523 | if let Some(module) = ast::Module::cast(n) { | ||
524 | return module.item_list().map(ast::AstNode::syntax); | ||
525 | } | ||
526 | ast::SourceFile::cast(n).map(ast::AstNode::syntax) | ||
527 | }); | ||
528 | |||
529 | if let Some(container) = container { | ||
530 | let action = best_action_for_target(container, anchor, target); | ||
531 | make_assist(&action, target, edit); | ||
532 | } | ||
533 | } | ||
534 | |||
516 | pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 535 | pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
517 | let path: &ast::Path = ctx.node_at_offset()?; | 536 | let path: &ast::Path = ctx.node_at_offset()?; |
518 | // We don't want to mess with use statements | 537 | // We don't want to mess with use statements |
@@ -520,7 +539,8 @@ pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist | |||
520 | return None; | 539 | return None; |
521 | } | 540 | } |
522 | 541 | ||
523 | let segments = collect_path_segments(path)?; | 542 | let hir_path = hir::Path::from_ast(path)?; |
543 | let segments = collect_hir_path_segments(&hir_path); | ||
524 | if segments.len() < 2 { | 544 | if segments.len() < 2 { |
525 | return None; | 545 | return None; |
526 | } | 546 | } |
@@ -531,7 +551,9 @@ pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist | |||
531 | AssistId("auto_import"), | 551 | AssistId("auto_import"), |
532 | format!("import {} in mod {}", fmt_segments(&segments), name.text()), | 552 | format!("import {} in mod {}", fmt_segments(&segments), name.text()), |
533 | |edit| { | 553 | |edit| { |
534 | apply_auto_import(item_list.syntax(), path, &segments, edit); | 554 | let mut text_edit = TextEditBuilder::default(); |
555 | apply_auto_import(item_list.syntax(), path, &segments, &mut text_edit); | ||
556 | edit.set_edit_builder(text_edit); | ||
535 | }, | 557 | }, |
536 | ); | 558 | ); |
537 | } | 559 | } |
@@ -541,7 +563,9 @@ pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist | |||
541 | AssistId("auto_import"), | 563 | AssistId("auto_import"), |
542 | format!("import {} in the current file", fmt_segments(&segments)), | 564 | format!("import {} in the current file", fmt_segments(&segments)), |
543 | |edit| { | 565 | |edit| { |
544 | apply_auto_import(current_file.syntax(), path, &segments, edit); | 566 | let mut text_edit = TextEditBuilder::default(); |
567 | apply_auto_import(current_file.syntax(), path, &segments, &mut text_edit); | ||
568 | edit.set_edit_builder(text_edit); | ||
545 | }, | 569 | }, |
546 | ); | 570 | ); |
547 | } | 571 | } |
@@ -568,6 +592,47 @@ Debug<|> | |||
568 | ", | 592 | ", |
569 | ); | 593 | ); |
570 | } | 594 | } |
595 | #[test] | ||
596 | fn test_auto_import_add_use_no_anchor_with_item_below() { | ||
597 | check_assist( | ||
598 | auto_import, | ||
599 | " | ||
600 | std::fmt::Debug<|> | ||
601 | |||
602 | fn main() { | ||
603 | } | ||
604 | ", | ||
605 | " | ||
606 | use std::fmt::Debug; | ||
607 | |||
608 | Debug<|> | ||
609 | |||
610 | fn main() { | ||
611 | } | ||
612 | ", | ||
613 | ); | ||
614 | } | ||
615 | |||
616 | #[test] | ||
617 | fn test_auto_import_add_use_no_anchor_with_item_above() { | ||
618 | check_assist( | ||
619 | auto_import, | ||
620 | " | ||
621 | fn main() { | ||
622 | } | ||
623 | |||
624 | std::fmt::Debug<|> | ||
625 | ", | ||
626 | " | ||
627 | use std::fmt::Debug; | ||
628 | |||
629 | fn main() { | ||
630 | } | ||
631 | |||
632 | Debug<|> | ||
633 | ", | ||
634 | ); | ||
635 | } | ||
571 | 636 | ||
572 | #[test] | 637 | #[test] |
573 | fn test_auto_import_add_use_no_anchor_2seg() { | 638 | fn test_auto_import_add_use_no_anchor_2seg() { |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 60b4d5c63..4c330c907 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -99,7 +99,7 @@ mod inline_local_variable; | |||
99 | mod replace_if_let_with_match; | 99 | mod replace_if_let_with_match; |
100 | mod split_import; | 100 | mod split_import; |
101 | mod remove_dbg; | 101 | mod remove_dbg; |
102 | mod auto_import; | 102 | pub mod auto_import; |
103 | mod add_missing_impl_members; | 103 | mod add_missing_impl_members; |
104 | 104 | ||
105 | fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { | 105 | fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { |
diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir/src/name.rs index 283f37845..9a999e66c 100644 --- a/crates/ra_hir/src/name.rs +++ b/crates/ra_hir/src/name.rs | |||
@@ -46,6 +46,19 @@ impl Name { | |||
46 | Name::new(idx.to_string().into()) | 46 | Name::new(idx.to_string().into()) |
47 | } | 47 | } |
48 | 48 | ||
49 | // There's should be no way to extract a string out of `Name`: `Name` in the | ||
50 | // future, `Name` will include hygiene information, and you can't encode | ||
51 | // hygiene into a String. | ||
52 | // | ||
53 | // If you need to compare something with `Name`, compare `Name`s directly. | ||
54 | // | ||
55 | // If you need to render `Name` for the user, use the `Display` impl, but be | ||
56 | // aware that it strips hygiene info. | ||
57 | #[deprecated(note = "use to_string instead")] | ||
58 | pub fn as_smolstr(&self) -> &SmolStr { | ||
59 | &self.text | ||
60 | } | ||
61 | |||
49 | pub(crate) fn as_known_name(&self) -> Option<KnownName> { | 62 | pub(crate) fn as_known_name(&self) -> Option<KnownName> { |
50 | let name = match self.text.as_str() { | 63 | let name = match self.text.as_str() { |
51 | "isize" => KnownName::Isize, | 64 | "isize" => KnownName::Isize, |
diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index f1bb13bc6..2959e3eca 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs | |||
@@ -21,7 +21,7 @@ use crate::{ | |||
21 | AsName, Module, HirFileId, Crate, Trait, Resolver, Ty, | 21 | AsName, Module, HirFileId, Crate, Trait, Resolver, Ty, |
22 | expr::{BodySourceMap, scope::{ScopeId, ExprScopes}}, | 22 | expr::{BodySourceMap, scope::{ScopeId, ExprScopes}}, |
23 | ids::LocationCtx, | 23 | ids::LocationCtx, |
24 | expr, AstId | 24 | expr, AstId, |
25 | }; | 25 | }; |
26 | 26 | ||
27 | /// Locates the module by `FileId`. Picks topmost module in the file. | 27 | /// Locates the module by `FileId`. Picks topmost module in the file. |
diff --git a/crates/ra_ide_api/src/completion/complete_scope.rs b/crates/ra_ide_api/src/completion/complete_scope.rs index fd256fc3b..2473e58b4 100644 --- a/crates/ra_ide_api/src/completion/complete_scope.rs +++ b/crates/ra_ide_api/src/completion/complete_scope.rs | |||
@@ -1,12 +1,122 @@ | |||
1 | use crate::completion::{Completions, CompletionContext}; | 1 | use rustc_hash::FxHashMap; |
2 | use ra_text_edit::TextEditBuilder; | ||
3 | use ra_syntax::{SmolStr, ast, AstNode}; | ||
4 | use ra_assists::auto_import; | ||
5 | |||
6 | use crate::completion::{CompletionItem, Completions, CompletionKind, CompletionContext}; | ||
2 | 7 | ||
3 | pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { | 8 | pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { |
4 | if !ctx.is_trivial_path { | 9 | if ctx.is_trivial_path { |
5 | return; | 10 | let names = ctx.analyzer.all_names(ctx.db); |
11 | names.into_iter().for_each(|(name, res)| acc.add_resolution(ctx, name.to_string(), &res)); | ||
12 | |||
13 | // auto-import | ||
14 | // We fetch ident from the original file, because we need to pre-filter auto-imports | ||
15 | if ast::NameRef::cast(ctx.token.parent()).is_some() { | ||
16 | let import_resolver = ImportResolver::new(); | ||
17 | let import_names = import_resolver.all_names(ctx.token.text()); | ||
18 | import_names.into_iter().for_each(|(name, path)| { | ||
19 | let edit = { | ||
20 | let mut builder = TextEditBuilder::default(); | ||
21 | builder.replace(ctx.source_range(), name.to_string()); | ||
22 | auto_import::auto_import_text_edit( | ||
23 | ctx.token.parent(), | ||
24 | ctx.token.parent(), | ||
25 | &path, | ||
26 | &mut builder, | ||
27 | ); | ||
28 | builder.finish() | ||
29 | }; | ||
30 | |||
31 | // Hack: copied this check form conv.rs beacause auto import can produce edits | ||
32 | // that invalidate assert in conv_with. | ||
33 | if edit | ||
34 | .as_atoms() | ||
35 | .iter() | ||
36 | .filter(|atom| !ctx.source_range().is_subrange(&atom.delete)) | ||
37 | .all(|atom| ctx.source_range().intersection(&atom.delete).is_none()) | ||
38 | { | ||
39 | CompletionItem::new( | ||
40 | CompletionKind::Reference, | ||
41 | ctx.source_range(), | ||
42 | build_import_label(&name, &path), | ||
43 | ) | ||
44 | .text_edit(edit) | ||
45 | .add_to(acc); | ||
46 | } | ||
47 | }); | ||
48 | } | ||
6 | } | 49 | } |
7 | let names = ctx.analyzer.all_names(ctx.db); | 50 | } |
8 | 51 | ||
9 | names.into_iter().for_each(|(name, res)| acc.add_resolution(ctx, name.to_string(), &res)); | 52 | fn build_import_label(name: &str, path: &Vec<SmolStr>) -> String { |
53 | let mut buf = String::with_capacity(64); | ||
54 | buf.push_str(name); | ||
55 | buf.push_str(" ("); | ||
56 | fmt_import_path(path, &mut buf); | ||
57 | buf.push_str(")"); | ||
58 | buf | ||
59 | } | ||
60 | |||
61 | fn fmt_import_path(path: &Vec<SmolStr>, buf: &mut String) { | ||
62 | let mut segments = path.iter(); | ||
63 | if let Some(s) = segments.next() { | ||
64 | buf.push_str(&s); | ||
65 | } | ||
66 | for s in segments { | ||
67 | buf.push_str("::"); | ||
68 | buf.push_str(&s); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | #[derive(Debug, Clone, Default)] | ||
73 | pub(crate) struct ImportResolver { | ||
74 | // todo: use fst crate or something like that | ||
75 | dummy_names: Vec<(SmolStr, Vec<SmolStr>)>, | ||
76 | } | ||
77 | |||
78 | impl ImportResolver { | ||
79 | pub(crate) fn new() -> Self { | ||
80 | let dummy_names = vec![ | ||
81 | (SmolStr::new("fmt"), vec![SmolStr::new("std"), SmolStr::new("fmt")]), | ||
82 | (SmolStr::new("io"), vec![SmolStr::new("std"), SmolStr::new("io")]), | ||
83 | (SmolStr::new("iter"), vec![SmolStr::new("std"), SmolStr::new("iter")]), | ||
84 | (SmolStr::new("hash"), vec![SmolStr::new("std"), SmolStr::new("hash")]), | ||
85 | ( | ||
86 | SmolStr::new("Debug"), | ||
87 | vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Debug")], | ||
88 | ), | ||
89 | ( | ||
90 | SmolStr::new("Display"), | ||
91 | vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Display")], | ||
92 | ), | ||
93 | ( | ||
94 | SmolStr::new("Hash"), | ||
95 | vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hash")], | ||
96 | ), | ||
97 | ( | ||
98 | SmolStr::new("Hasher"), | ||
99 | vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hasher")], | ||
100 | ), | ||
101 | ( | ||
102 | SmolStr::new("Iterator"), | ||
103 | vec![SmolStr::new("std"), SmolStr::new("iter"), SmolStr::new("Iterator")], | ||
104 | ), | ||
105 | ]; | ||
106 | |||
107 | ImportResolver { dummy_names } | ||
108 | } | ||
109 | |||
110 | // Returns a map of importable items filtered by name. | ||
111 | // The map associates item name with its full path. | ||
112 | // todo: should return Resolutions | ||
113 | pub(crate) fn all_names(&self, name: &str) -> FxHashMap<SmolStr, Vec<SmolStr>> { | ||
114 | if name.len() > 1 { | ||
115 | self.dummy_names.iter().filter(|(n, _)| n.contains(name)).cloned().collect() | ||
116 | } else { | ||
117 | FxHashMap::default() | ||
118 | } | ||
119 | } | ||
10 | } | 120 | } |
11 | 121 | ||
12 | #[cfg(test)] | 122 | #[cfg(test)] |
diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs index 359f2cffa..a8c8cc7b0 100644 --- a/crates/ra_ide_api/src/completion/completion_context.rs +++ b/crates/ra_ide_api/src/completion/completion_context.rs | |||
@@ -27,7 +27,7 @@ pub(crate) struct CompletionContext<'a> { | |||
27 | pub(super) is_pat_binding: bool, | 27 | pub(super) is_pat_binding: bool, |
28 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. | 28 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. |
29 | pub(super) is_trivial_path: bool, | 29 | pub(super) is_trivial_path: bool, |
30 | /// If not a trivial, path, the prefix (qualifier). | 30 | /// If not a trivial path, the prefix (qualifier). |
31 | pub(super) path_prefix: Option<hir::Path>, | 31 | pub(super) path_prefix: Option<hir::Path>, |
32 | pub(super) after_if: bool, | 32 | pub(super) after_if: bool, |
33 | /// `true` if we are a statement or a last expr in the block. | 33 | /// `true` if we are a statement or a last expr in the block. |
@@ -151,6 +151,7 @@ impl<'a> CompletionContext<'a> { | |||
151 | Some(it) => it, | 151 | Some(it) => it, |
152 | None => return, | 152 | None => return, |
153 | }; | 153 | }; |
154 | |||
154 | if let Some(segment) = ast::PathSegment::cast(parent) { | 155 | if let Some(segment) = ast::PathSegment::cast(parent) { |
155 | let path = segment.parent_path(); | 156 | let path = segment.parent_path(); |
156 | self.is_call = path | 157 | self.is_call = path |
@@ -167,6 +168,7 @@ impl<'a> CompletionContext<'a> { | |||
167 | return; | 168 | return; |
168 | } | 169 | } |
169 | } | 170 | } |
171 | |||
170 | if path.qualifier().is_none() { | 172 | if path.qualifier().is_none() { |
171 | self.is_trivial_path = true; | 173 | self.is_trivial_path = true; |
172 | 174 | ||