aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db/src/helpers/insert_use.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_db/src/helpers/insert_use.rs')
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs279
1 files changed, 113 insertions, 166 deletions
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index be3a22725..a43504a27 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -4,13 +4,9 @@ use std::{cmp::Ordering, iter::successors};
4use hir::Semantics; 4use hir::Semantics;
5use itertools::{EitherOrBoth, Itertools}; 5use itertools::{EitherOrBoth, Itertools};
6use syntax::{ 6use syntax::{
7 algo::SyntaxRewriter, 7 algo,
8 ast::{ 8 ast::{self, edit::AstNodeEdit, make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner},
9 self, 9 ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken,
10 edit::{AstNodeEdit, IndentLevel},
11 make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner,
12 },
13 AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
14}; 10};
15 11
16use crate::RootDatabase; 12use crate::RootDatabase;
@@ -42,13 +38,18 @@ impl ImportScope {
42 } 38 }
43 39
44 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. 40 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
45 pub fn find_insert_use_container( 41 pub fn find_insert_use_container_with_macros(
46 position: &SyntaxNode, 42 position: &SyntaxNode,
47 sema: &Semantics<'_, RootDatabase>, 43 sema: &Semantics<'_, RootDatabase>,
48 ) -> Option<Self> { 44 ) -> Option<Self> {
49 sema.ancestors_with_macros(position.clone()).find_map(Self::from) 45 sema.ancestors_with_macros(position.clone()).find_map(Self::from)
50 } 46 }
51 47
48 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
49 pub fn find_insert_use_container(position: &SyntaxNode) -> Option<Self> {
50 std::iter::successors(Some(position.clone()), SyntaxNode::parent).find_map(Self::from)
51 }
52
52 pub fn as_syntax_node(&self) -> &SyntaxNode { 53 pub fn as_syntax_node(&self) -> &SyntaxNode {
53 match self { 54 match self {
54 ImportScope::File(file) => file.syntax(), 55 ImportScope::File(file) => file.syntax(),
@@ -56,127 +57,32 @@ impl ImportScope {
56 } 57 }
57 } 58 }
58 59
59 fn indent_level(&self) -> IndentLevel { 60 pub fn clone_for_update(&self) -> Self {
60 match self {
61 ImportScope::File(file) => file.indent_level(),
62 ImportScope::Module(item_list) => item_list.indent_level() + 1,
63 }
64 }
65
66 fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
67 match self { 61 match self {
68 ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice), 62 ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
69 // don't insert the imports before the item list's opening curly brace 63 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
70 ImportScope::Module(item_list) => item_list
71 .l_curly_token()
72 .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around))
73 .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)),
74 } 64 }
75 } 65 }
76
77 fn insert_pos_after_last_inner_element(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
78 self.as_syntax_node()
79 .children_with_tokens()
80 .filter(|child| match child {
81 NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
82 NodeOrToken::Token(token) => is_inner_comment(token.clone()),
83 })
84 .last()
85 .map(|last_inner_element| {
86 (InsertPosition::After(last_inner_element), AddBlankLine::BeforeTwice)
87 })
88 .unwrap_or_else(|| self.first_insert_pos())
89 }
90}
91
92fn is_inner_attribute(node: SyntaxNode) -> bool {
93 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
94}
95
96fn is_inner_comment(token: SyntaxToken) -> bool {
97 ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
98 == Some(ast::CommentPlacement::Inner)
99} 66}
100 67
101/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 68/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
102pub fn insert_use<'a>( 69pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) {
103 scope: &ImportScope,
104 path: ast::Path,
105 cfg: InsertUseConfig,
106) -> SyntaxRewriter<'a> {
107 let _p = profile::span("insert_use"); 70 let _p = profile::span("insert_use");
108 let mut rewriter = SyntaxRewriter::default(); 71 let use_item =
109 let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false)); 72 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
110 // merge into existing imports if possible 73 // merge into existing imports if possible
111 if let Some(mb) = cfg.merge { 74 if let Some(mb) = cfg.merge {
112 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 75 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
113 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { 76 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
114 rewriter.replace(existing_use.syntax(), merged.syntax()); 77 ted::replace(existing_use.syntax(), merged.syntax());
115 return rewriter; 78 return;
116 } 79 }
117 } 80 }
118 } 81 }
119 82
120 // either we weren't allowed to merge or there is no import that fits the merge conditions 83 // either we weren't allowed to merge or there is no import that fits the merge conditions
121 // so look for the place we have to insert to 84 // so look for the place we have to insert to
122 let (insert_position, add_blank) = find_insert_position(scope, path, cfg.group); 85 insert_use_(scope, path, cfg.group, use_item);
123
124 let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
125 Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into())
126 } else {
127 None
128 };
129
130 let to_insert: Vec<SyntaxElement> = {
131 let mut buf = Vec::new();
132
133 match add_blank {
134 AddBlankLine::Before | AddBlankLine::Around => {
135 buf.push(make::tokens::single_newline().into())
136 }
137 AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()),
138 _ => (),
139 }
140
141 if add_blank.has_before() {
142 if let Some(indent) = indent.clone() {
143 cov_mark::hit!(insert_use_indent_before);
144 buf.push(indent);
145 }
146 }
147
148 buf.push(use_item.syntax().clone().into());
149
150 match add_blank {
151 AddBlankLine::After | AddBlankLine::Around => {
152 buf.push(make::tokens::single_newline().into())
153 }
154 AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()),
155 _ => (),
156 }
157
158 // only add indentation *after* our stuff if there's another node directly after it
159 if add_blank.has_after() && matches!(insert_position, InsertPosition::Before(_)) {
160 if let Some(indent) = indent {
161 cov_mark::hit!(insert_use_indent_after);
162 buf.push(indent);
163 }
164 } else if add_blank.has_after() && matches!(insert_position, InsertPosition::After(_)) {
165 cov_mark::hit!(insert_use_no_indent_after);
166 }
167
168 buf
169 };
170
171 match insert_position {
172 InsertPosition::First => {
173 rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert)
174 }
175 InsertPosition::Last => return rewriter, // actually unreachable
176 InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert),
177 InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert),
178 }
179 rewriter
180} 86}
181 87
182fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { 88fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
@@ -235,7 +141,7 @@ pub fn try_merge_trees(
235 } else { 141 } else {
236 (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix)) 142 (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
237 }; 143 };
238 recursive_merge(&lhs, &rhs, merge).map(|it| it.clone_for_update()) 144 recursive_merge(&lhs, &rhs, merge)
239} 145}
240 146
241/// Recursively "zips" together lhs and rhs. 147/// Recursively "zips" together lhs and rhs.
@@ -334,7 +240,12 @@ fn recursive_merge(
334 } 240 }
335 } 241 }
336 } 242 }
337 Some(lhs.with_use_tree_list(make::use_tree_list(use_trees))) 243
244 Some(if let Some(old) = lhs.use_tree_list() {
245 lhs.replace_descendant(old, make::use_tree_list(use_trees)).clone_for_update()
246 } else {
247 lhs.clone()
248 })
338} 249}
339 250
340/// Traverses both paths until they differ, returning the common prefix of both. 251/// Traverses both paths until they differ, returning the common prefix of both.
@@ -520,32 +431,15 @@ impl ImportGroup {
520 } 431 }
521} 432}
522 433
523#[derive(PartialEq, Eq)] 434fn insert_use_(
524enum AddBlankLine {
525 Before,
526 BeforeTwice,
527 Around,
528 After,
529 AfterTwice,
530}
531
532impl AddBlankLine {
533 fn has_before(&self) -> bool {
534 matches!(self, AddBlankLine::Before | AddBlankLine::BeforeTwice | AddBlankLine::Around)
535 }
536 fn has_after(&self) -> bool {
537 matches!(self, AddBlankLine::After | AddBlankLine::AfterTwice | AddBlankLine::Around)
538 }
539}
540
541fn find_insert_position(
542 scope: &ImportScope, 435 scope: &ImportScope,
543 insert_path: ast::Path, 436 insert_path: ast::Path,
544 group_imports: bool, 437 group_imports: bool,
545) -> (InsertPosition<SyntaxElement>, AddBlankLine) { 438 use_item: ast::Use,
439) {
440 let scope_syntax = scope.as_syntax_node();
546 let group = ImportGroup::new(&insert_path); 441 let group = ImportGroup::new(&insert_path);
547 let path_node_iter = scope 442 let path_node_iter = scope_syntax
548 .as_syntax_node()
549 .children() 443 .children()
550 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) 444 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
551 .flat_map(|(use_, node)| { 445 .flat_map(|(use_, node)| {
@@ -557,9 +451,14 @@ fn find_insert_position(
557 451
558 if !group_imports { 452 if !group_imports {
559 if let Some((_, _, node)) = path_node_iter.last() { 453 if let Some((_, _, node)) = path_node_iter.last() {
560 return (InsertPosition::After(node.into()), AddBlankLine::Before); 454 cov_mark::hit!(insert_no_grouping_last);
455 ted::insert(ted::Position::after(node), use_item.syntax());
456 } else {
457 cov_mark::hit!(insert_no_grouping_last2);
458 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
459 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
561 } 460 }
562 return (InsertPosition::First, AddBlankLine::AfterTwice); 461 return;
563 } 462 }
564 463
565 // Iterator that discards anything thats not in the required grouping 464 // Iterator that discards anything thats not in the required grouping
@@ -572,43 +471,91 @@ fn find_insert_position(
572 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place 471 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
573 let mut last = None; 472 let mut last = None;
574 // find the element that would come directly after our new import 473 // find the element that would come directly after our new import
575 let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find( 474 let post_insert: Option<(_, _, SyntaxNode)> = group_iter
576 |&(ref path, has_tl, _)| { 475 .inspect(|(.., node)| last = Some(node.clone()))
476 .find(|&(ref path, has_tl, _)| {
577 use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater 477 use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
578 }, 478 });
579 );
580 479
581 match post_insert { 480 if let Some((.., node)) = post_insert {
481 cov_mark::hit!(insert_group);
582 // insert our import before that element 482 // insert our import before that element
583 Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After), 483 return ted::insert(ted::Position::before(node), use_item.syntax());
484 }
485 if let Some(node) = last {
486 cov_mark::hit!(insert_group_last);
584 // there is no element after our new import, so append it to the end of the group 487 // there is no element after our new import, so append it to the end of the group
585 None => match last { 488 return ted::insert(ted::Position::after(node), use_item.syntax());
586 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before), 489 }
587 // the group we were looking for actually doesnt exist, so insert 490
491 // the group we were looking for actually doesn't exist, so insert
492
493 let mut last = None;
494 // find the group that comes after where we want to insert
495 let post_group = path_node_iter
496 .inspect(|(.., node)| last = Some(node.clone()))
497 .find(|(p, ..)| ImportGroup::new(p) > group);
498 if let Some((.., node)) = post_group {
499 cov_mark::hit!(insert_group_new_group);
500 ted::insert(ted::Position::before(&node), use_item.syntax());
501 if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
502 ted::insert(ted::Position::after(node), make::tokens::single_newline());
503 }
504 return;
505 }
506 // there is no such group, so append after the last one
507 if let Some(node) = last {
508 cov_mark::hit!(insert_group_no_group);
509 ted::insert(ted::Position::after(&node), use_item.syntax());
510 ted::insert(ted::Position::after(node), make::tokens::single_newline());
511 return;
512 }
513 // there are no imports in this file at all
514 if let Some(last_inner_element) = scope_syntax
515 .children_with_tokens()
516 .filter(|child| match child {
517 NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
518 NodeOrToken::Token(token) => is_inner_comment(token.clone()),
519 })
520 .last()
521 {
522 cov_mark::hit!(insert_group_empty_inner_attr);
523 ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
524 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
525 return;
526 }
527 match scope {
528 ImportScope::File(_) => {
529 cov_mark::hit!(insert_group_empty_file);
530 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
531 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax())
532 }
533 // don't insert the imports before the item list's opening curly brace
534 ImportScope::Module(item_list) => match item_list.l_curly_token() {
535 Some(b) => {
536 cov_mark::hit!(insert_group_empty_module);
537 ted::insert(ted::Position::after(&b), make::tokens::single_newline());
538 ted::insert(ted::Position::after(&b), use_item.syntax());
539 }
588 None => { 540 None => {
589 // similar concept here to the `last` from above 541 // This should never happens, broken module syntax node
590 let mut last = None; 542 ted::insert(
591 // find the group that comes after where we want to insert 543 ted::Position::first_child_of(scope_syntax),
592 let post_group = path_node_iter 544 make::tokens::blank_line(),
593 .inspect(|(.., node)| last = Some(node.clone())) 545 );
594 .find(|(p, ..)| ImportGroup::new(p) > group); 546 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
595 match post_group {
596 Some((.., node)) => {
597 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
598 }
599 // there is no such group, so append after the last one
600 None => match last {
601 Some(node) => {
602 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
603 }
604 // there are no imports in this file at all
605 None => scope.insert_pos_after_last_inner_element(),
606 },
607 }
608 } 547 }
609 }, 548 },
610 } 549 }
611} 550}
612 551
552fn is_inner_attribute(node: SyntaxNode) -> bool {
553 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
554}
555
556fn is_inner_comment(token: SyntaxToken) -> bool {
557 ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
558 == Some(ast::CommentPlacement::Inner)
559}
613#[cfg(test)] 560#[cfg(test)]
614mod tests; 561mod tests;