diff options
Diffstat (limited to 'crates/ra_assists')
-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 |
3 files changed, 139 insertions, 70 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>] { |