diff options
Diffstat (limited to 'crates/assists/src/utils')
-rw-r--r-- | crates/assists/src/utils/insert_use.rs | 138 |
1 files changed, 95 insertions, 43 deletions
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs index 1ddd72197..40ff31075 100644 --- a/crates/assists/src/utils/insert_use.rs +++ b/crates/assists/src/utils/insert_use.rs | |||
@@ -1,8 +1,10 @@ | |||
1 | use std::iter::{self, successors}; | 1 | use std::iter::{self, successors}; |
2 | 2 | ||
3 | use algo::skip_trivia_token; | 3 | use algo::skip_trivia_token; |
4 | use ast::{edit::AstNodeEdit, PathSegmentKind, VisibilityOwner}; | 4 | use ast::{ |
5 | use either::Either; | 5 | edit::{AstNodeEdit, IndentLevel}, |
6 | PathSegmentKind, VisibilityOwner, | ||
7 | }; | ||
6 | use syntax::{ | 8 | use syntax::{ |
7 | algo, | 9 | algo, |
8 | ast::{self, make, AstNode}, | 10 | ast::{self, make, AstNode}, |
@@ -11,56 +13,121 @@ use syntax::{ | |||
11 | 13 | ||
12 | use test_utils::mark; | 14 | use test_utils::mark; |
13 | 15 | ||
14 | /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. | 16 | #[derive(Debug)] |
15 | pub(crate) fn find_insert_use_container( | 17 | pub enum ImportScope { |
16 | position: &SyntaxNode, | 18 | File(ast::SourceFile), |
17 | ctx: &crate::assist_context::AssistContext, | 19 | Module(ast::ItemList), |
18 | ) -> Option<Either<ast::ItemList, ast::SourceFile>> { | 20 | } |
19 | ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { | 21 | |
20 | if let Some(module) = ast::Module::cast(n.clone()) { | 22 | impl ImportScope { |
21 | module.item_list().map(Either::Left) | 23 | pub fn from(syntax: SyntaxNode) -> Option<Self> { |
24 | if let Some(module) = ast::Module::cast(syntax.clone()) { | ||
25 | module.item_list().map(ImportScope::Module) | ||
26 | } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) { | ||
27 | this.map(ImportScope::File) | ||
22 | } else { | 28 | } else { |
23 | Some(Either::Right(ast::SourceFile::cast(n)?)) | 29 | ast::ItemList::cast(syntax).map(ImportScope::Module) |
24 | } | 30 | } |
25 | }) | 31 | } |
32 | |||
33 | /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. | ||
34 | pub(crate) fn find_insert_use_container( | ||
35 | position: &SyntaxNode, | ||
36 | ctx: &crate::assist_context::AssistContext, | ||
37 | ) -> Option<Self> { | ||
38 | ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from) | ||
39 | } | ||
40 | |||
41 | pub(crate) fn as_syntax_node(&self) -> &SyntaxNode { | ||
42 | match self { | ||
43 | ImportScope::File(file) => file.syntax(), | ||
44 | ImportScope::Module(item_list) => item_list.syntax(), | ||
45 | } | ||
46 | } | ||
47 | |||
48 | fn indent_level(&self) -> IndentLevel { | ||
49 | match self { | ||
50 | ImportScope::File(file) => file.indent_level(), | ||
51 | ImportScope::Module(item_list) => item_list.indent_level() + 1, | ||
52 | } | ||
53 | } | ||
54 | |||
55 | fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) { | ||
56 | match self { | ||
57 | ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice), | ||
58 | // don't insert the impotrs before the item lists curly brace | ||
59 | ImportScope::Module(item_list) => item_list | ||
60 | .l_curly_token() | ||
61 | .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around)) | ||
62 | .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)), | ||
63 | } | ||
64 | } | ||
65 | |||
66 | fn insert_pos_after_inner_attribute(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) { | ||
67 | // check if the scope has a inner attributes, we dont want to insert in front of it | ||
68 | match self | ||
69 | .as_syntax_node() | ||
70 | .children() | ||
71 | // no flat_map here cause we want to short circuit the iterator | ||
72 | .map(ast::Attr::cast) | ||
73 | .take_while(|attr| { | ||
74 | attr.as_ref().map(|attr| attr.kind() == ast::AttrKind::Inner).unwrap_or(false) | ||
75 | }) | ||
76 | .last() | ||
77 | .flatten() | ||
78 | { | ||
79 | Some(attr) => { | ||
80 | (InsertPosition::After(attr.syntax().clone().into()), AddBlankLine::BeforeTwice) | ||
81 | } | ||
82 | None => self.first_insert_pos(), | ||
83 | } | ||
84 | } | ||
26 | } | 85 | } |
27 | 86 | ||
28 | /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. | 87 | /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. |
29 | pub fn insert_use( | 88 | pub(crate) fn insert_use( |
30 | where_: &SyntaxNode, | 89 | scope: &ImportScope, |
31 | path: ast::Path, | 90 | path: ast::Path, |
32 | merge: Option<MergeBehaviour>, | 91 | merge: Option<MergeBehaviour>, |
33 | ) -> SyntaxNode { | 92 | ) -> SyntaxNode { |
34 | let use_item = make::use_(make::use_tree(path.clone(), None, None, false)); | 93 | let use_item = make::use_(make::use_tree(path.clone(), None, None, false)); |
35 | // merge into existing imports if possible | 94 | // merge into existing imports if possible |
36 | if let Some(mb) = merge { | 95 | if let Some(mb) = merge { |
37 | for existing_use in where_.children().filter_map(ast::Use::cast) { | 96 | for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { |
38 | if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { | 97 | if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { |
39 | let to_delete: SyntaxElement = existing_use.syntax().clone().into(); | 98 | let to_delete: SyntaxElement = existing_use.syntax().clone().into(); |
40 | let to_delete = to_delete.clone()..=to_delete; | 99 | let to_delete = to_delete.clone()..=to_delete; |
41 | let to_insert = iter::once(merged.syntax().clone().into()); | 100 | let to_insert = iter::once(merged.syntax().clone().into()); |
42 | return algo::replace_children(where_, to_delete, to_insert); | 101 | return algo::replace_children(scope.as_syntax_node(), to_delete, to_insert); |
43 | } | 102 | } |
44 | } | 103 | } |
45 | } | 104 | } |
46 | 105 | ||
47 | // either we weren't allowed to merge or there is no import that fits the merge conditions | 106 | // either we weren't allowed to merge or there is no import that fits the merge conditions |
48 | // so look for the place we have to insert to | 107 | // so look for the place we have to insert to |
49 | let (insert_position, add_blank) = find_insert_position(where_, path); | 108 | let (insert_position, add_blank) = find_insert_position(scope, path); |
50 | 109 | ||
51 | let to_insert: Vec<SyntaxElement> = { | 110 | let to_insert: Vec<SyntaxElement> = { |
52 | let mut buf = Vec::new(); | 111 | let mut buf = Vec::new(); |
53 | 112 | ||
54 | match add_blank { | 113 | match add_blank { |
55 | AddBlankLine::Before => buf.push(make::tokens::single_newline().into()), | 114 | AddBlankLine::Before | AddBlankLine::Around => { |
115 | buf.push(make::tokens::single_newline().into()) | ||
116 | } | ||
56 | AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()), | 117 | AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()), |
57 | _ => (), | 118 | _ => (), |
58 | } | 119 | } |
59 | 120 | ||
121 | if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize { | ||
122 | // TODO: this alone doesnt properly re-align all cases | ||
123 | buf.push(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into()); | ||
124 | } | ||
60 | buf.push(use_item.syntax().clone().into()); | 125 | buf.push(use_item.syntax().clone().into()); |
61 | 126 | ||
62 | match add_blank { | 127 | match add_blank { |
63 | AddBlankLine::After => buf.push(make::tokens::single_newline().into()), | 128 | AddBlankLine::After | AddBlankLine::Around => { |
129 | buf.push(make::tokens::single_newline().into()) | ||
130 | } | ||
64 | AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()), | 131 | AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()), |
65 | _ => (), | 132 | _ => (), |
66 | } | 133 | } |
@@ -68,7 +135,7 @@ pub fn insert_use( | |||
68 | buf | 135 | buf |
69 | }; | 136 | }; |
70 | 137 | ||
71 | algo::insert_children(where_, insert_position, to_insert) | 138 | algo::insert_children(scope.as_syntax_node(), insert_position, to_insert) |
72 | } | 139 | } |
73 | 140 | ||
74 | fn try_merge_imports( | 141 | fn try_merge_imports( |
@@ -218,16 +285,18 @@ fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Cl | |||
218 | enum AddBlankLine { | 285 | enum AddBlankLine { |
219 | Before, | 286 | Before, |
220 | BeforeTwice, | 287 | BeforeTwice, |
288 | Around, | ||
221 | After, | 289 | After, |
222 | AfterTwice, | 290 | AfterTwice, |
223 | } | 291 | } |
224 | 292 | ||
225 | fn find_insert_position( | 293 | fn find_insert_position( |
226 | scope: &SyntaxNode, | 294 | scope: &ImportScope, |
227 | insert_path: ast::Path, | 295 | insert_path: ast::Path, |
228 | ) -> (InsertPosition<SyntaxElement>, AddBlankLine) { | 296 | ) -> (InsertPosition<SyntaxElement>, AddBlankLine) { |
229 | let group = ImportGroup::new(&insert_path); | 297 | let group = ImportGroup::new(&insert_path); |
230 | let path_node_iter = scope | 298 | let path_node_iter = scope |
299 | .as_syntax_node() | ||
231 | .children() | 300 | .children() |
232 | .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) | 301 | .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) |
233 | .flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node))); | 302 | .flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node))); |
@@ -275,27 +344,7 @@ fn find_insert_position( | |||
275 | (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice) | 344 | (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice) |
276 | } | 345 | } |
277 | // there are no imports in this file at all | 346 | // there are no imports in this file at all |
278 | None => { | 347 | None => scope.insert_pos_after_inner_attribute(), |
279 | // check if the scope has a inner attributes, we dont want to insert in front of it | ||
280 | match scope | ||
281 | .children() | ||
282 | // no flat_map here cause we want to short circuit the iterator | ||
283 | .map(ast::Attr::cast) | ||
284 | .take_while(|attr| { | ||
285 | attr.as_ref() | ||
286 | .map(|attr| attr.kind() == ast::AttrKind::Inner) | ||
287 | .unwrap_or(false) | ||
288 | }) | ||
289 | .last() | ||
290 | .flatten() | ||
291 | { | ||
292 | Some(attr) => ( | ||
293 | InsertPosition::After(attr.syntax().clone().into()), | ||
294 | AddBlankLine::BeforeTwice, | ||
295 | ), | ||
296 | None => (InsertPosition::First, AddBlankLine::AfterTwice), | ||
297 | } | ||
298 | } | ||
299 | }, | 348 | }, |
300 | } | 349 | } |
301 | } | 350 | } |
@@ -640,7 +689,10 @@ use foo::bar;", | |||
640 | ra_fixture_after: &str, | 689 | ra_fixture_after: &str, |
641 | mb: Option<MergeBehaviour>, | 690 | mb: Option<MergeBehaviour>, |
642 | ) { | 691 | ) { |
643 | let file = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(); | 692 | let file = super::ImportScope::from( |
693 | ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(), | ||
694 | ) | ||
695 | .unwrap(); | ||
644 | let path = ast::SourceFile::parse(&format!("use {};", path)) | 696 | let path = ast::SourceFile::parse(&format!("use {};", path)) |
645 | .tree() | 697 | .tree() |
646 | .syntax() | 698 | .syntax() |