diff options
-rw-r--r-- | crates/ra_assists/src/auto_import.rs | 138 |
1 files changed, 29 insertions, 109 deletions
diff --git a/crates/ra_assists/src/auto_import.rs b/crates/ra_assists/src/auto_import.rs index 77380b816..6a0c351f1 100644 --- a/crates/ra_assists/src/auto_import.rs +++ b/crates/ra_assists/src/auto_import.rs | |||
@@ -5,42 +5,6 @@ use ra_syntax::{ | |||
5 | }; | 5 | }; |
6 | use crate::assist_ctx::{AssistCtx, Assist, AssistBuilder}; | 6 | use crate::assist_ctx::{AssistCtx, Assist, AssistBuilder}; |
7 | 7 | ||
8 | // TODO: refactor this before merge | ||
9 | mod formatting { | ||
10 | use ra_syntax::{ | ||
11 | AstNode, SyntaxNode, | ||
12 | ast::{self, AstToken}, | ||
13 | algo::generate, | ||
14 | }; | ||
15 | |||
16 | /// If the node is on the beginning of the line, calculate indent. | ||
17 | pub fn leading_indent(node: &SyntaxNode) -> Option<&str> { | ||
18 | for leaf in prev_leaves(node) { | ||
19 | if let Some(ws) = ast::Whitespace::cast(leaf) { | ||
20 | let ws_text = ws.text(); | ||
21 | if let Some(pos) = ws_text.rfind('\n') { | ||
22 | return Some(&ws_text[pos + 1..]); | ||
23 | } | ||
24 | } | ||
25 | if leaf.leaf_text().unwrap().contains('\n') { | ||
26 | break; | ||
27 | } | ||
28 | } | ||
29 | None | ||
30 | } | ||
31 | |||
32 | fn prev_leaves(node: &SyntaxNode) -> impl Iterator<Item = &SyntaxNode> { | ||
33 | generate(prev_leaf(node), |&node| prev_leaf(node)) | ||
34 | } | ||
35 | |||
36 | fn prev_leaf(node: &SyntaxNode) -> Option<&SyntaxNode> { | ||
37 | generate(node.ancestors().find_map(SyntaxNode::prev_sibling), |it| { | ||
38 | it.last_child() | ||
39 | }) | ||
40 | .last() | ||
41 | } | ||
42 | } | ||
43 | |||
44 | fn collect_path_segments(path: &ast::Path) -> Option<Vec<&ast::PathSegment>> { | 8 | fn collect_path_segments(path: &ast::Path) -> Option<Vec<&ast::PathSegment>> { |
45 | let mut v = Vec::new(); | 9 | let mut v = Vec::new(); |
46 | collect_path_segments_raw(&mut v, path)?; | 10 | collect_path_segments_raw(&mut v, path)?; |
@@ -102,11 +66,7 @@ fn fmt_segments_raw(segments: &[&ast::PathSegment], buf: &mut String) { | |||
102 | 66 | ||
103 | // Returns the numeber of common segments. | 67 | // Returns the numeber of common segments. |
104 | fn compare_path_segments(left: &[&ast::PathSegment], right: &[&ast::PathSegment]) -> usize { | 68 | fn compare_path_segments(left: &[&ast::PathSegment], right: &[&ast::PathSegment]) -> usize { |
105 | return left | 69 | return left.iter().zip(right).filter(|(l, r)| compare_path_segment(l, r)).count(); |
106 | .iter() | ||
107 | .zip(right) | ||
108 | .filter(|(l, r)| compare_path_segment(l, r)) | ||
109 | .count(); | ||
110 | } | 70 | } |
111 | 71 | ||
112 | fn compare_path_segment(a: &ast::PathSegment, b: &ast::PathSegment) -> bool { | 72 | fn compare_path_segment(a: &ast::PathSegment, b: &ast::PathSegment) -> bool { |
@@ -166,10 +126,7 @@ enum ImportAction<'a> { | |||
166 | 126 | ||
167 | impl<'a> ImportAction<'a> { | 127 | impl<'a> ImportAction<'a> { |
168 | fn add_new_use(anchor: Option<&'a SyntaxNode>, add_after_anchor: bool) -> Self { | 128 | fn add_new_use(anchor: Option<&'a SyntaxNode>, add_after_anchor: bool) -> Self { |
169 | ImportAction::AddNewUse { | 129 | ImportAction::AddNewUse { anchor, add_after_anchor } |
170 | anchor, | ||
171 | add_after_anchor, | ||
172 | } | ||
173 | } | 130 | } |
174 | 131 | ||
175 | fn add_nested_import( | 132 | fn add_nested_import( |
@@ -191,11 +148,7 @@ impl<'a> ImportAction<'a> { | |||
191 | tree_list: &'a ast::UseTreeList, | 148 | tree_list: &'a ast::UseTreeList, |
192 | add_self: bool, | 149 | add_self: bool, |
193 | ) -> Self { | 150 | ) -> Self { |
194 | ImportAction::AddInTreeList { | 151 | ImportAction::AddInTreeList { common_segments, tree_list, add_self } |
195 | common_segments, | ||
196 | tree_list, | ||
197 | add_self, | ||
198 | } | ||
199 | } | 152 | } |
200 | 153 | ||
201 | fn better<'b>(left: &'b ImportAction<'a>, right: &'b ImportAction<'a>) -> &'b ImportAction<'a> { | 154 | fn better<'b>(left: &'b ImportAction<'a>, right: &'b ImportAction<'a>) -> &'b ImportAction<'a> { |
@@ -211,20 +164,12 @@ impl<'a> ImportAction<'a> { | |||
211 | (ImportAction::Nothing, _) => true, | 164 | (ImportAction::Nothing, _) => true, |
212 | (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, | 165 | (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, |
213 | ( | 166 | ( |
214 | ImportAction::AddNestedImport { | 167 | ImportAction::AddNestedImport { common_segments: n, .. }, |
215 | common_segments: n, .. | 168 | ImportAction::AddInTreeList { common_segments: m, .. }, |
216 | }, | ||
217 | ImportAction::AddInTreeList { | ||
218 | common_segments: m, .. | ||
219 | }, | ||
220 | ) => n > m, | 169 | ) => n > m, |
221 | ( | 170 | ( |
222 | ImportAction::AddInTreeList { | 171 | ImportAction::AddInTreeList { common_segments: n, .. }, |
223 | common_segments: n, .. | 172 | ImportAction::AddNestedImport { common_segments: m, .. }, |
224 | }, | ||
225 | ImportAction::AddNestedImport { | ||
226 | common_segments: m, .. | ||
227 | }, | ||
228 | ) => n > m, | 173 | ) => n > m, |
229 | (ImportAction::AddInTreeList { .. }, _) => true, | 174 | (ImportAction::AddInTreeList { .. }, _) => true, |
230 | (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false, | 175 | (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false, |
@@ -283,11 +228,7 @@ fn walk_use_tree_for_best_action<'a>( | |||
283 | // e.g: target is std::fmt and we can have | 228 | // e.g: target is std::fmt and we can have |
284 | // use foo::bar | 229 | // use foo::bar |
285 | // We add a brand new use statement | 230 | // We add a brand new use statement |
286 | current_use_tree | 231 | current_use_tree.syntax().ancestors().find_map(ast::UseItem::cast).map(AstNode::syntax), |
287 | .syntax() | ||
288 | .ancestors() | ||
289 | .find_map(ast::UseItem::cast) | ||
290 | .map(AstNode::syntax), | ||
291 | true, | 232 | true, |
292 | ), | 233 | ), |
293 | common if common == left.len() && left.len() == right.len() => { | 234 | common if common == left.len() && left.len() == right.len() => { |
@@ -398,8 +339,7 @@ fn best_action_for_target<'b, 'a: 'b>( | |||
398 | .filter_map(ast::UseItem::use_tree) | 339 | .filter_map(ast::UseItem::use_tree) |
399 | .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) | 340 | .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) |
400 | .fold(None, |best, a| { | 341 | .fold(None, |best, a| { |
401 | best.and_then(|best| Some(*ImportAction::better(&best, &a))) | 342 | best.and_then(|best| Some(*ImportAction::better(&best, &a))).or(Some(a)) |
402 | .or(Some(a)) | ||
403 | }); | 343 | }); |
404 | 344 | ||
405 | match best_action { | 345 | match best_action { |
@@ -421,15 +361,10 @@ fn best_action_for_target<'b, 'a: 'b>( | |||
421 | 361 | ||
422 | fn make_assist(action: &ImportAction, target: &[&ast::PathSegment], edit: &mut AssistBuilder) { | 362 | fn make_assist(action: &ImportAction, target: &[&ast::PathSegment], edit: &mut AssistBuilder) { |
423 | match action { | 363 | match action { |
424 | ImportAction::AddNewUse { | 364 | ImportAction::AddNewUse { anchor, add_after_anchor } => { |
425 | anchor, | 365 | make_assist_add_new_use(anchor, *add_after_anchor, target, edit) |
426 | add_after_anchor, | 366 | } |
427 | } => make_assist_add_new_use(anchor, *add_after_anchor, target, edit), | 367 | ImportAction::AddInTreeList { common_segments, tree_list, add_self } => { |
428 | ImportAction::AddInTreeList { | ||
429 | common_segments, | ||
430 | tree_list, | ||
431 | add_self, | ||
432 | } => { | ||
433 | // We know that the fist n segments already exists in the use statement we want | 368 | // We know that the fist n segments already exists in the use statement we want |
434 | // to modify, so we want to add only the last target.len() - n segments. | 369 | // to modify, so we want to add only the last target.len() - n segments. |
435 | let segments_to_add = target.split_at(*common_segments).1; | 370 | let segments_to_add = target.split_at(*common_segments).1; |
@@ -461,7 +396,7 @@ fn make_assist_add_new_use( | |||
461 | edit: &mut AssistBuilder, | 396 | edit: &mut AssistBuilder, |
462 | ) { | 397 | ) { |
463 | if let Some(anchor) = anchor { | 398 | if let Some(anchor) = anchor { |
464 | let indent = formatting::leading_indent(anchor); | 399 | let indent = ra_fmt::leading_indent(anchor); |
465 | let mut buf = String::new(); | 400 | let mut buf = String::new(); |
466 | if after { | 401 | if after { |
467 | buf.push_str("\n"); | 402 | buf.push_str("\n"); |
@@ -478,11 +413,7 @@ fn make_assist_add_new_use( | |||
478 | buf.push_str(spaces); | 413 | buf.push_str(spaces); |
479 | } | 414 | } |
480 | } | 415 | } |
481 | let position = if after { | 416 | let position = if after { anchor.range().end() } else { anchor.range().start() }; |
482 | anchor.range().end() | ||
483 | } else { | ||
484 | anchor.range().start() | ||
485 | }; | ||
486 | edit.insert(position, buf); | 417 | edit.insert(position, buf); |
487 | } | 418 | } |
488 | } | 419 | } |
@@ -496,10 +427,7 @@ fn make_assist_add_in_tree_list( | |||
496 | let last = tree_list.use_trees().last(); | 427 | let last = tree_list.use_trees().last(); |
497 | if let Some(last) = last { | 428 | if let Some(last) = last { |
498 | let mut buf = String::new(); | 429 | let mut buf = String::new(); |
499 | let comma = last | 430 | let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == COMMA); |
500 | .syntax() | ||
501 | .siblings(Direction::Next) | ||
502 | .find(|n| n.kind() == COMMA); | ||
503 | let offset = if let Some(comma) = comma { | 431 | let offset = if let Some(comma) = comma { |
504 | comma.range().end() | 432 | comma.range().end() |
505 | } else { | 433 | } else { |
@@ -558,12 +486,7 @@ pub(crate) fn auto_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
558 | 486 | ||
559 | let path = node.ancestors().find_map(ast::Path::cast)?; | 487 | let path = node.ancestors().find_map(ast::Path::cast)?; |
560 | // We don't want to mess with use statements | 488 | // We don't want to mess with use statements |
561 | if path | 489 | if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { |
562 | .syntax() | ||
563 | .ancestors() | ||
564 | .find_map(ast::UseItem::cast) | ||
565 | .is_some() | ||
566 | { | ||
567 | return None; | 490 | return None; |
568 | } | 491 | } |
569 | 492 | ||
@@ -572,21 +495,18 @@ pub(crate) fn auto_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
572 | return None; | 495 | return None; |
573 | } | 496 | } |
574 | 497 | ||
575 | ctx.build( | 498 | ctx.build(format!("import {} in the current file", fmt_segments(&segments)), |edit| { |
576 | format!("import {} in the current file", fmt_segments(&segments)), | 499 | let action = best_action_for_target(current_file.syntax(), path, &segments); |
577 | |edit| { | 500 | make_assist(&action, segments.as_slice(), edit); |
578 | let action = best_action_for_target(current_file.syntax(), path, &segments); | 501 | if let Some(last_segment) = path.segment() { |
579 | make_assist(&action, segments.as_slice(), edit); | 502 | // Here we are assuming the assist will provide a correct use statement |
580 | if let Some(last_segment) = path.segment() { | 503 | // so we can delete the path qualifier |
581 | // Here we are assuming the assist will provide a correct use statement | 504 | edit.delete(TextRange::from_to( |
582 | // so we can delete the path qualifier | 505 | path.syntax().range().start(), |
583 | edit.delete(TextRange::from_to( | 506 | last_segment.syntax().range().start(), |
584 | path.syntax().range().start(), | 507 | )); |
585 | last_segment.syntax().range().start(), | 508 | } |
586 | )); | 509 | }) |
587 | } | ||
588 | }, | ||
589 | ) | ||
590 | } | 510 | } |
591 | 511 | ||
592 | #[cfg(test)] | 512 | #[cfg(test)] |