aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs8
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs85
-rw-r--r--crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs28
-rw-r--r--crates/ide_completion/src/item.rs8
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs262
-rw-r--r--crates/ide_db/src/helpers/insert_use/tests.rs19
-rw-r--r--crates/syntax/src/ast/make.rs1
-rw-r--r--crates/syntax/src/ted.rs17
8 files changed, 185 insertions, 243 deletions
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index 49aa70f74..6db2d2edd 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -101,9 +101,11 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
101 format!("Import `{}`", import.import_path), 101 format!("Import `{}`", import.import_path),
102 range, 102 range,
103 |builder| { 103 |builder| {
104 let rewriter = 104 let scope = match scope.clone() {
105 insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use); 105 ImportScope::File(it) => ImportScope::File(builder.make_ast_mut(it)),
106 builder.rewrite(rewriter); 106 ImportScope::Module(it) => ImportScope::Module(builder.make_ast_mut(it)),
107 };
108 insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
107 }, 109 },
108 ); 110 );
109 } 111 }
diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
index a8d6355bd..26e1c66ab 100644
--- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -13,9 +13,9 @@ use ide_db::{
13}; 13};
14use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
15use syntax::{ 15use syntax::{
16 algo::{find_node_at_offset, SyntaxRewriter}, 16 algo::find_node_at_offset,
17 ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner}, 17 ast::{self, make, AstNode, NameOwner, VisibilityOwner},
18 SourceFile, SyntaxElement, SyntaxNode, T, 18 ted, SourceFile, SyntaxElement, SyntaxNode, T,
19}; 19};
20 20
21use crate::{AssistContext, AssistId, AssistKind, Assists}; 21use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -62,14 +62,17 @@ pub(crate) fn extract_struct_from_enum_variant(
62 let mut visited_modules_set = FxHashSet::default(); 62 let mut visited_modules_set = FxHashSet::default();
63 let current_module = enum_hir.module(ctx.db()); 63 let current_module = enum_hir.module(ctx.db());
64 visited_modules_set.insert(current_module); 64 visited_modules_set.insert(current_module);
65 let mut def_rewriter = None; 65 let mut def_file_references = None;
66 for (file_id, references) in usages { 66 for (file_id, references) in usages {
67 let mut rewriter = SyntaxRewriter::default(); 67 if file_id == ctx.frange.file_id {
68 let source_file = ctx.sema.parse(file_id); 68 def_file_references = Some(references);
69 continue;
70 }
71 builder.edit_file(file_id);
72 let source_file = builder.make_ast_mut(ctx.sema.parse(file_id));
69 for reference in references { 73 for reference in references {
70 update_reference( 74 update_reference(
71 ctx, 75 ctx,
72 &mut rewriter,
73 reference, 76 reference,
74 &source_file, 77 &source_file,
75 &enum_module_def, 78 &enum_module_def,
@@ -77,25 +80,27 @@ pub(crate) fn extract_struct_from_enum_variant(
77 &mut visited_modules_set, 80 &mut visited_modules_set,
78 ); 81 );
79 } 82 }
80 if file_id == ctx.frange.file_id {
81 def_rewriter = Some(rewriter);
82 continue;
83 }
84 builder.edit_file(file_id);
85 builder.rewrite(rewriter);
86 } 83 }
87 let mut rewriter = def_rewriter.unwrap_or_default(); 84 builder.edit_file(ctx.frange.file_id);
88 update_variant(&mut rewriter, &variant); 85 let variant = builder.make_ast_mut(variant.clone());
86 let source_file = builder.make_ast_mut(ctx.sema.parse(ctx.frange.file_id));
87 for reference in def_file_references.into_iter().flatten() {
88 update_reference(
89 ctx,
90 reference,
91 &source_file,
92 &enum_module_def,
93 &variant_hir_name,
94 &mut visited_modules_set,
95 );
96 }
89 extract_struct_def( 97 extract_struct_def(
90 &mut rewriter,
91 &enum_ast,
92 variant_name.clone(), 98 variant_name.clone(),
93 &field_list, 99 &field_list,
94 &variant.parent_enum().syntax().clone().into(), 100 &variant.parent_enum().syntax().clone().into(),
95 enum_ast.visibility(), 101 enum_ast.visibility(),
96 ); 102 );
97 builder.edit_file(ctx.frange.file_id); 103 update_variant(&variant);
98 builder.rewrite(rewriter);
99 }, 104 },
100 ) 105 )
101} 106}
@@ -138,7 +143,6 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
138 143
139fn insert_import( 144fn insert_import(
140 ctx: &AssistContext, 145 ctx: &AssistContext,
141 rewriter: &mut SyntaxRewriter,
142 scope_node: &SyntaxNode, 146 scope_node: &SyntaxNode,
143 module: &Module, 147 module: &Module,
144 enum_module_def: &ModuleDef, 148 enum_module_def: &ModuleDef,
@@ -151,14 +155,12 @@ fn insert_import(
151 mod_path.pop_segment(); 155 mod_path.pop_segment();
152 mod_path.push_segment(variant_hir_name.clone()); 156 mod_path.push_segment(variant_hir_name.clone());
153 let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?; 157 let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
154 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use); 158 insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use);
155 } 159 }
156 Some(()) 160 Some(())
157} 161}
158 162
159fn extract_struct_def( 163fn extract_struct_def(
160 rewriter: &mut SyntaxRewriter,
161 enum_: &ast::Enum,
162 variant_name: ast::Name, 164 variant_name: ast::Name,
163 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, 165 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
164 start_offset: &SyntaxElement, 166 start_offset: &SyntaxElement,
@@ -180,33 +182,34 @@ fn extract_struct_def(
180 .into(), 182 .into(),
181 }; 183 };
182 184
183 rewriter.insert_before( 185 ted::insert_raw(
184 start_offset, 186 ted::Position::before(start_offset),
185 make::struct_(visibility, variant_name, None, field_list).syntax(), 187 make::struct_(visibility, variant_name, None, field_list).clone_for_update().syntax(),
186 ); 188 );
187 rewriter.insert_before(start_offset, &make::tokens::blank_line()); 189 ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
188 190
189 if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize { 191 // if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
190 rewriter 192 // ted::insert(ted::Position::before(start_offset), &make::tokens::blank_line());
191 .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level))); 193 // rewriter
192 } 194 // .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
195 // }
193 Some(()) 196 Some(())
194} 197}
195 198
196fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> { 199fn update_variant(variant: &ast::Variant) -> Option<()> {
197 let name = variant.name()?; 200 let name = variant.name()?;
198 let tuple_field = make::tuple_field(None, make::ty(&name.text())); 201 let tuple_field = make::tuple_field(None, make::ty(&name.text()));
199 let replacement = make::variant( 202 let replacement = make::variant(
200 name, 203 name,
201 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))), 204 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
202 ); 205 )
203 rewriter.replace(variant.syntax(), replacement.syntax()); 206 .clone_for_update();
207 ted::replace(variant.syntax(), replacement.syntax());
204 Some(()) 208 Some(())
205} 209}
206 210
207fn update_reference( 211fn update_reference(
208 ctx: &AssistContext, 212 ctx: &AssistContext,
209 rewriter: &mut SyntaxRewriter,
210 reference: FileReference, 213 reference: FileReference,
211 source_file: &SourceFile, 214 source_file: &SourceFile,
212 enum_module_def: &ModuleDef, 215 enum_module_def: &ModuleDef,
@@ -230,14 +233,16 @@ fn update_reference(
230 233
231 let module = ctx.sema.scope(&expr).module()?; 234 let module = ctx.sema.scope(&expr).module()?;
232 if !visited_modules_set.contains(&module) { 235 if !visited_modules_set.contains(&module) {
233 if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some() 236 if insert_import(ctx, &expr, &module, enum_module_def, variant_hir_name).is_some() {
234 {
235 visited_modules_set.insert(module); 237 visited_modules_set.insert(module);
236 } 238 }
237 } 239 }
238 rewriter.insert_after(segment.syntax(), &make::token(T!['('])); 240 ted::insert_raw(
239 rewriter.insert_after(segment.syntax(), segment.syntax()); 241 ted::Position::before(segment.syntax()),
240 rewriter.insert_after(&expr, &make::token(T![')'])); 242 make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(),
243 );
244 ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
245 ted::insert_raw(ted::Position::after(&expr), make::token(T![')']));
241 Some(()) 246 Some(())
242} 247}
243 248
diff --git a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
index 36d2e0331..2f2306fcc 100644
--- a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -1,5 +1,5 @@
1use ide_db::helpers::insert_use::{insert_use, ImportScope}; 1use ide_db::helpers::insert_use::{insert_use, ImportScope};
2use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode}; 2use syntax::{ast, match_ast, ted, AstNode, SyntaxNode};
3 3
4use crate::{AssistContext, AssistId, AssistKind, Assists}; 4use crate::{AssistContext, AssistId, AssistKind, Assists};
5 5
@@ -40,18 +40,17 @@ pub(crate) fn replace_qualified_name_with_use(
40 |builder| { 40 |builder| {
41 // Now that we've brought the name into scope, re-qualify all paths that could be 41 // Now that we've brought the name into scope, re-qualify all paths that could be
42 // affected (that is, all paths inside the node we added the `use` to). 42 // affected (that is, all paths inside the node we added the `use` to).
43 let mut rewriter = SyntaxRewriter::default(); 43 let syntax = builder.make_mut(syntax.clone());
44 shorten_paths(&mut rewriter, syntax.clone(), &path);
45 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) { 44 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
46 rewriter += insert_use(import_scope, path, ctx.config.insert_use); 45 insert_use(import_scope, path.clone(), ctx.config.insert_use);
47 builder.rewrite(rewriter);
48 } 46 }
47 shorten_paths(syntax.clone(), &path.clone_for_update());
49 }, 48 },
50 ) 49 )
51} 50}
52 51
53/// Adds replacements to `re` that shorten `path` in all descendants of `node`. 52/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
54fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) { 53fn shorten_paths(node: SyntaxNode, path: &ast::Path) {
55 for child in node.children() { 54 for child in node.children() {
56 match_ast! { 55 match_ast! {
57 match child { 56 match child {
@@ -62,32 +61,28 @@ fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path:
62 ast::Module(_it) => continue, 61 ast::Module(_it) => continue,
63 62
64 ast::Path(p) => { 63 ast::Path(p) => {
65 match maybe_replace_path(rewriter, p.clone(), path.clone()) { 64 match maybe_replace_path(p.clone(), path.clone()) {
66 Some(()) => {}, 65 Some(()) => {},
67 None => shorten_paths(rewriter, p.syntax().clone(), path), 66 None => shorten_paths(p.syntax().clone(), path),
68 } 67 }
69 }, 68 },
70 _ => shorten_paths(rewriter, child, path), 69 _ => shorten_paths(child, path),
71 } 70 }
72 } 71 }
73 } 72 }
74} 73}
75 74
76fn maybe_replace_path( 75fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
77 rewriter: &mut SyntaxRewriter<'static>,
78 path: ast::Path,
79 target: ast::Path,
80) -> Option<()> {
81 if !path_eq(path.clone(), target) { 76 if !path_eq(path.clone(), target) {
82 return None; 77 return None;
83 } 78 }
84 79
85 // Shorten `path`, leaving only its last segment. 80 // Shorten `path`, leaving only its last segment.
86 if let Some(parent) = path.qualifier() { 81 if let Some(parent) = path.qualifier() {
87 rewriter.delete(parent.syntax()); 82 ted::remove(parent.syntax());
88 } 83 }
89 if let Some(double_colon) = path.coloncolon_token() { 84 if let Some(double_colon) = path.coloncolon_token() {
90 rewriter.delete(&double_colon); 85 ted::remove(&double_colon);
91 } 86 }
92 87
93 Some(()) 88 Some(())
@@ -150,6 +145,7 @@ Debug
150 ", 145 ",
151 ); 146 );
152 } 147 }
148
153 #[test] 149 #[test]
154 fn test_replace_add_use_no_anchor_with_item_below() { 150 fn test_replace_add_use_no_anchor_with_item_below() {
155 check_assist( 151 check_assist(
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs
index 16991b688..99edb9499 100644
--- a/crates/ide_completion/src/item.rs
+++ b/crates/ide_completion/src/item.rs
@@ -377,11 +377,11 @@ impl ImportEdit {
377 pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> { 377 pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> {
378 let _p = profile::span("ImportEdit::to_text_edit"); 378 let _p = profile::span("ImportEdit::to_text_edit");
379 379
380 let rewriter = 380 let new_ast = self.scope.clone_for_update();
381 insert_use::insert_use(&self.scope, mod_path_to_ast(&self.import.import_path), cfg); 381 insert_use::insert_use(&new_ast, mod_path_to_ast(&self.import.import_path), cfg);
382 let old_ast = rewriter.rewrite_root()?;
383 let mut import_insert = TextEdit::builder(); 382 let mut import_insert = TextEdit::builder();
384 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); 383 algo::diff(self.scope.as_syntax_node(), new_ast.as_syntax_node())
384 .into_text_edit(&mut import_insert);
385 385
386 Some(import_insert.finish()) 386 Some(import_insert.finish())
387 } 387 }
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index be3a22725..498d76f72 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;
@@ -56,127 +52,32 @@ impl ImportScope {
56 } 52 }
57 } 53 }
58 54
59 fn indent_level(&self) -> IndentLevel { 55 pub fn clone_for_update(&self) -> Self {
60 match self { 56 match self {
61 ImportScope::File(file) => file.indent_level(), 57 ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
62 ImportScope::Module(item_list) => item_list.indent_level() + 1, 58 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
63 } 59 }
64 } 60 }
65
66 fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
67 match self {
68 ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice),
69 // don't insert the imports before the item list's opening curly brace
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 }
75 }
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} 61}
100 62
101/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 63/// 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>( 64pub 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"); 65 let _p = profile::span("insert_use");
108 let mut rewriter = SyntaxRewriter::default(); 66 let use_item =
109 let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false)); 67 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
110 // merge into existing imports if possible 68 // merge into existing imports if possible
111 if let Some(mb) = cfg.merge { 69 if let Some(mb) = cfg.merge {
112 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 70 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) { 71 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
114 rewriter.replace(existing_use.syntax(), merged.syntax()); 72 ted::replace(existing_use.syntax(), merged.syntax());
115 return rewriter; 73 return;
116 } 74 }
117 } 75 }
118 } 76 }
119 77
120 // either we weren't allowed to merge or there is no import that fits the merge conditions 78 // 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 79 // so look for the place we have to insert to
122 let (insert_position, add_blank) = find_insert_position(scope, path, cfg.group); 80 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} 81}
181 82
182fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { 83fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
@@ -235,7 +136,7 @@ pub fn try_merge_trees(
235 } else { 136 } else {
236 (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix)) 137 (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
237 }; 138 };
238 recursive_merge(&lhs, &rhs, merge).map(|it| it.clone_for_update()) 139 recursive_merge(&lhs, &rhs, merge)
239} 140}
240 141
241/// Recursively "zips" together lhs and rhs. 142/// Recursively "zips" together lhs and rhs.
@@ -334,7 +235,12 @@ fn recursive_merge(
334 } 235 }
335 } 236 }
336 } 237 }
337 Some(lhs.with_use_tree_list(make::use_tree_list(use_trees))) 238
239 Some(if let Some(old) = lhs.use_tree_list() {
240 lhs.replace_descendant(old, make::use_tree_list(use_trees)).clone_for_update()
241 } else {
242 lhs.clone()
243 })
338} 244}
339 245
340/// Traverses both paths until they differ, returning the common prefix of both. 246/// Traverses both paths until they differ, returning the common prefix of both.
@@ -520,32 +426,15 @@ impl ImportGroup {
520 } 426 }
521} 427}
522 428
523#[derive(PartialEq, Eq)] 429fn 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, 430 scope: &ImportScope,
543 insert_path: ast::Path, 431 insert_path: ast::Path,
544 group_imports: bool, 432 group_imports: bool,
545) -> (InsertPosition<SyntaxElement>, AddBlankLine) { 433 use_item: ast::Use,
434) {
435 let scope_syntax = scope.as_syntax_node();
546 let group = ImportGroup::new(&insert_path); 436 let group = ImportGroup::new(&insert_path);
547 let path_node_iter = scope 437 let path_node_iter = scope_syntax
548 .as_syntax_node()
549 .children() 438 .children()
550 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) 439 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
551 .flat_map(|(use_, node)| { 440 .flat_map(|(use_, node)| {
@@ -557,9 +446,12 @@ fn find_insert_position(
557 446
558 if !group_imports { 447 if !group_imports {
559 if let Some((_, _, node)) = path_node_iter.last() { 448 if let Some((_, _, node)) = path_node_iter.last() {
560 return (InsertPosition::After(node.into()), AddBlankLine::Before); 449 ted::insert(ted::Position::after(node), use_item.syntax());
450 } else {
451 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
452 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
561 } 453 }
562 return (InsertPosition::First, AddBlankLine::AfterTwice); 454 return;
563 } 455 }
564 456
565 // Iterator that discards anything thats not in the required grouping 457 // Iterator that discards anything thats not in the required grouping
@@ -572,43 +464,83 @@ 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 464 // 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; 465 let mut last = None;
574 // find the element that would come directly after our new import 466 // find the element that would come directly after our new import
575 let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find( 467 let post_insert: Option<(_, _, SyntaxNode)> = group_iter
576 |&(ref path, has_tl, _)| { 468 .inspect(|(.., node)| last = Some(node.clone()))
469 .find(|&(ref path, has_tl, _)| {
577 use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater 470 use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
578 }, 471 });
579 );
580 472
581 match post_insert { 473 if let Some((.., node)) = post_insert {
582 // insert our import before that element 474 // insert our import before that element
583 Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After), 475 return ted::insert(ted::Position::before(node), use_item.syntax());
476 }
477 if let Some(node) = last {
584 // there is no element after our new import, so append it to the end of the group 478 // there is no element after our new import, so append it to the end of the group
585 None => match last { 479 return ted::insert(ted::Position::after(node), use_item.syntax());
586 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before), 480 }
587 // the group we were looking for actually doesnt exist, so insert 481
482 // the group we were looking for actually doesn't exist, so insert
483
484 let mut last = None;
485 // find the group that comes after where we want to insert
486 let post_group = path_node_iter
487 .inspect(|(.., node)| last = Some(node.clone()))
488 .find(|(p, ..)| ImportGroup::new(p) > group);
489 if let Some((.., node)) = post_group {
490 ted::insert(ted::Position::before(&node), use_item.syntax());
491 if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
492 ted::insert(ted::Position::after(node), make::tokens::single_newline());
493 }
494 return;
495 }
496 // there is no such group, so append after the last one
497 if let Some(node) = last {
498 ted::insert(ted::Position::after(&node), use_item.syntax());
499 ted::insert(ted::Position::after(node), make::tokens::single_newline());
500 return;
501 }
502 // there are no imports in this file at all
503 if let Some(last_inner_element) = scope_syntax
504 .children_with_tokens()
505 .filter(|child| match child {
506 NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
507 NodeOrToken::Token(token) => is_inner_comment(token.clone()),
508 })
509 .last()
510 {
511 ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
512 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
513 return;
514 }
515 match scope {
516 ImportScope::File(_) => {
517 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
518 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax())
519 }
520 // don't insert the imports before the item list's opening curly brace
521 ImportScope::Module(item_list) => match item_list.l_curly_token() {
522 Some(b) => {
523 ted::insert(ted::Position::after(&b), make::tokens::single_newline());
524 ted::insert(ted::Position::after(&b), use_item.syntax());
525 }
588 None => { 526 None => {
589 // similar concept here to the `last` from above 527 ted::insert(
590 let mut last = None; 528 ted::Position::first_child_of(scope_syntax),
591 // find the group that comes after where we want to insert 529 make::tokens::blank_line(),
592 let post_group = path_node_iter 530 );
593 .inspect(|(.., node)| last = Some(node.clone())) 531 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
594 .find(|(p, ..)| ImportGroup::new(p) > group);
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 } 532 }
609 }, 533 },
610 } 534 }
611} 535}
612 536
537fn is_inner_attribute(node: SyntaxNode) -> bool {
538 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
539}
540
541fn is_inner_comment(token: SyntaxToken) -> bool {
542 ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
543 == Some(ast::CommentPlacement::Inner)
544}
613#[cfg(test)] 545#[cfg(test)]
614mod tests; 546mod tests;
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 3d151e629..a3464d606 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -51,17 +51,16 @@ use std::bar::G;",
51 51
52#[test] 52#[test]
53fn insert_start_indent() { 53fn insert_start_indent() {
54 cov_mark::check!(insert_use_indent_after);
55 check_none( 54 check_none(
56 "std::bar::AA", 55 "std::bar::AA",
57 r" 56 r"
58 use std::bar::B; 57 use std::bar::B;
59 use std::bar::D;", 58 use std::bar::C;",
60 r" 59 r"
61 use std::bar::AA; 60 use std::bar::AA;
62 use std::bar::B; 61 use std::bar::B;
63 use std::bar::D;", 62 use std::bar::C;",
64 ) 63 );
65} 64}
66 65
67#[test] 66#[test]
@@ -120,7 +119,6 @@ use std::bar::ZZ;",
120 119
121#[test] 120#[test]
122fn insert_end_indent() { 121fn insert_end_indent() {
123 cov_mark::check!(insert_use_indent_before);
124 check_none( 122 check_none(
125 "std::bar::ZZ", 123 "std::bar::ZZ",
126 r" 124 r"
@@ -255,7 +253,6 @@ fn insert_empty_file() {
255 253
256#[test] 254#[test]
257fn insert_empty_module() { 255fn insert_empty_module() {
258 cov_mark::check!(insert_use_no_indent_after);
259 check( 256 check(
260 "foo::bar", 257 "foo::bar",
261 "mod x {}", 258 "mod x {}",
@@ -615,7 +612,7 @@ fn check(
615 if module { 612 if module {
616 syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone(); 613 syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone();
617 } 614 }
618 let file = super::ImportScope::from(syntax).unwrap(); 615 let file = super::ImportScope::from(syntax.clone_for_update()).unwrap();
619 let path = ast::SourceFile::parse(&format!("use {};", path)) 616 let path = ast::SourceFile::parse(&format!("use {};", path))
620 .tree() 617 .tree()
621 .syntax() 618 .syntax()
@@ -623,12 +620,8 @@ fn check(
623 .find_map(ast::Path::cast) 620 .find_map(ast::Path::cast)
624 .unwrap(); 621 .unwrap();
625 622
626 let rewriter = insert_use( 623 insert_use(&file, path, InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group });
627 &file, 624 let result = file.as_syntax_node().to_string();
628 path,
629 InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group },
630 );
631 let result = rewriter.rewrite(file.as_syntax_node()).to_string();
632 assert_eq_text!(ra_fixture_after, &result); 625 assert_eq_text!(ra_fixture_after, &result);
633} 626}
634 627
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 94d4f2cf0..882e9fa09 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -598,6 +598,7 @@ pub mod tokens {
598 SOURCE_FILE 598 SOURCE_FILE
599 .tree() 599 .tree()
600 .syntax() 600 .syntax()
601 .clone_for_update()
601 .descendants_with_tokens() 602 .descendants_with_tokens()
602 .filter_map(|it| it.into_token()) 603 .filter_map(|it| it.into_token())
603 .find(|it| it.kind() == WHITESPACE && it.text() == "\n\n") 604 .find(|it| it.kind() == WHITESPACE && it.text() == "\n\n")
diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs
index 450f2e447..91a06101f 100644
--- a/crates/syntax/src/ted.rs
+++ b/crates/syntax/src/ted.rs
@@ -7,7 +7,7 @@ use std::{mem, ops::RangeInclusive};
7use parser::T; 7use parser::T;
8 8
9use crate::{ 9use crate::{
10 ast::{edit::IndentLevel, make}, 10 ast::{self, edit::IndentLevel, make, AstNode},
11 SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, 11 SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
12}; 12};
13 13
@@ -147,6 +147,16 @@ pub fn append_child_raw(node: &(impl Into<SyntaxNode> + Clone), child: impl Elem
147fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> { 147fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
148 let prev = match &position.repr { 148 let prev = match &position.repr {
149 PositionRepr::FirstChild(_) => return None, 149 PositionRepr::FirstChild(_) => return None,
150 PositionRepr::After(it) if it.kind() == SyntaxKind::L_CURLY => {
151 if new.kind() == SyntaxKind::USE {
152 if let Some(item_list) = it.parent().and_then(ast::ItemList::cast) {
153 let mut indent = IndentLevel::from_element(&item_list.syntax().clone().into());
154 indent.0 += 1;
155 return Some(make::tokens::whitespace(&format!("\n{}", indent)));
156 }
157 }
158 it
159 }
150 PositionRepr::After(it) => it, 160 PositionRepr::After(it) => it,
151 }; 161 };
152 ws_between(prev, new) 162 ws_between(prev, new)
@@ -173,7 +183,10 @@ fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option<SyntaxToken
173 } 183 }
174 184
175 if right.kind() == SyntaxKind::USE { 185 if right.kind() == SyntaxKind::USE {
176 let indent = IndentLevel::from_element(left); 186 let mut indent = IndentLevel::from_element(left);
187 if left.kind() == SyntaxKind::USE {
188 indent.0 = IndentLevel::from_element(right).0.max(indent.0);
189 }
177 return Some(make::tokens::whitespace(&format!("\n{}", indent))); 190 return Some(make::tokens::whitespace(&format!("\n{}", indent)));
178 } 191 }
179 Some(make::tokens::single_space()) 192 Some(make::tokens::single_space())