aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-04-22 14:19:47 +0100
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-04-22 14:19:47 +0100
commitc416caeda2a09e84dc9cd201eff0bf04b701ae34 (patch)
tree81e9db6106e5201955bbf58a54aa51503e144ade /crates/ra_assists
parent6162278075df3d9e1d5ff77cf023dc2acaa6ee83 (diff)
parentaa1ef6ae9a9a61ab6ddc42d7987753df3aa67263 (diff)
Merge #1194
1194: Pr 1190 r=matklad a=matklad Co-authored-by: Andrea Pretto <[email protected]> Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/src/assist_ctx.rs4
-rw-r--r--crates/ra_assists/src/auto_import.rs203
-rw-r--r--crates/ra_assists/src/lib.rs2
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 @@
1use hir::db::HirDatabase; 1use ra_text_edit::TextEditBuilder;
2use hir::{ self, db::HirDatabase};
2 3
3use ra_syntax::{ 4use 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};
7use crate::{ 8use crate::{
8 AssistId, 9 AssistId,
9 assist_ctx::{AssistCtx, Assist, AssistBuilder}, 10 assist_ctx::{AssistCtx, Assist},
10}; 11};
11 12
12fn 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
18fn collect_path_segments_raw<'a>( 13fn 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
48fn fmt_segments(segments: &[&ast::PathSegment]) -> String { 43fn 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
54fn fmt_segments_raw(segments: &[&ast::PathSegment], buf: &mut String) { 49fn 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.
72fn compare_path_segments(left: &[&ast::PathSegment], right: &[&ast::PathSegment]) -> usize { 61fn 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
76fn compare_path_segment(a: &ast::PathSegment, b: &ast::PathSegment) -> bool { 65fn 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
92fn compare_path_segment_with_name(a: &ast::PathSegment, b: &ast::Name) -> bool { 78fn 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
345fn best_action_for_target<'b, 'a: 'b>( 324fn 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
377fn make_assist(action: &ImportAction, target: &[&ast::PathSegment], edit: &mut AssistBuilder) { 356fn 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
407fn make_assist_add_new_use( 386fn 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
436fn make_assist_add_in_tree_list( 415fn 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(
464fn make_assist_add_nested_import( 443fn 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
498fn apply_auto_import<'a>( 477fn 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
495pub 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
513pub 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
516pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 535pub(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 "
600std::fmt::Debug<|>
601
602fn main() {
603}
604 ",
605 "
606use std::fmt::Debug;
607
608Debug<|>
609
610fn 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 "
621fn main() {
622}
623
624std::fmt::Debug<|>
625 ",
626 "
627use std::fmt::Debug;
628
629fn main() {
630}
631
632Debug<|>
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;
99mod replace_if_let_with_match; 99mod replace_if_let_with_match;
100mod split_import; 100mod split_import;
101mod remove_dbg; 101mod remove_dbg;
102mod auto_import; 102pub mod auto_import;
103mod add_missing_impl_members; 103mod add_missing_impl_members;
104 104
105fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { 105fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] {