aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/utils')
-rw-r--r--crates/assists/src/utils/insert_use.rs138
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 @@
1use std::iter::{self, successors}; 1use std::iter::{self, successors};
2 2
3use algo::skip_trivia_token; 3use algo::skip_trivia_token;
4use ast::{edit::AstNodeEdit, PathSegmentKind, VisibilityOwner}; 4use ast::{
5use either::Either; 5 edit::{AstNodeEdit, IndentLevel},
6 PathSegmentKind, VisibilityOwner,
7};
6use syntax::{ 8use syntax::{
7 algo, 9 algo,
8 ast::{self, make, AstNode}, 10 ast::{self, make, AstNode},
@@ -11,56 +13,121 @@ use syntax::{
11 13
12use test_utils::mark; 14use test_utils::mark;
13 15
14/// Determines the containing syntax node in which to insert a `use` statement affecting `position`. 16#[derive(Debug)]
15pub(crate) fn find_insert_use_container( 17pub 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()) { 22impl 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.
29pub fn insert_use( 88pub(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
74fn try_merge_imports( 141fn try_merge_imports(
@@ -218,16 +285,18 @@ fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Cl
218enum AddBlankLine { 285enum AddBlankLine {
219 Before, 286 Before,
220 BeforeTwice, 287 BeforeTwice,
288 Around,
221 After, 289 After,
222 AfterTwice, 290 AfterTwice,
223} 291}
224 292
225fn find_insert_position( 293fn 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()