aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ide_assists/src/ast_transform.rs33
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs10
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs208
-rw-r--r--crates/ide_assists/src/handlers/reorder_fields.rs8
-rw-r--r--crates/ide_assists/src/handlers/reorder_impl.rs20
-rw-r--r--crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs34
-rw-r--r--crates/ide_assists/src/utils.rs3
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs2
-rw-r--r--crates/ide_completion/src/item.rs8
-rw-r--r--crates/ide_completion/src/lib.rs2
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs279
-rw-r--r--crates/ide_db/src/helpers/insert_use/tests.rs42
-rw-r--r--crates/syntax/src/algo.rs19
-rw-r--r--crates/syntax/src/ast/make.rs1
-rw-r--r--crates/syntax/src/ted.rs17
15 files changed, 331 insertions, 355 deletions
diff --git a/crates/ide_assists/src/ast_transform.rs b/crates/ide_assists/src/ast_transform.rs
index 4a3ed7783..e5ae718c9 100644
--- a/crates/ide_assists/src/ast_transform.rs
+++ b/crates/ide_assists/src/ast_transform.rs
@@ -3,20 +3,27 @@ use hir::{HirDisplay, PathResolution, SemanticsScope};
3use ide_db::helpers::mod_path_to_ast; 3use ide_db::helpers::mod_path_to_ast;
4use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
5use syntax::{ 5use syntax::{
6 algo::SyntaxRewriter,
7 ast::{self, AstNode}, 6 ast::{self, AstNode},
8 SyntaxNode, 7 ted, SyntaxNode,
9}; 8};
10 9
11pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { 10pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: &N) {
12 SyntaxRewriter::from_fn(|element| match element { 11 let mut skip_to = None;
13 syntax::SyntaxElement::Node(n) => { 12 for event in node.syntax().preorder() {
14 let replacement = transformer.get_substitution(&n, transformer)?; 13 match event {
15 Some(replacement.into()) 14 syntax::WalkEvent::Enter(node) if skip_to.is_none() => {
15 skip_to = transformer.get_substitution(&node, transformer).zip(Some(node));
16 }
17 syntax::WalkEvent::Enter(_) => (),
18 syntax::WalkEvent::Leave(node) => match &skip_to {
19 Some((replacement, skip_target)) if *skip_target == node => {
20 ted::replace(node, replacement.clone_for_update());
21 skip_to.take();
22 }
23 _ => (),
24 },
16 } 25 }
17 _ => None, 26 }
18 })
19 .rewrite_ast(&node)
20} 27}
21 28
22/// `AstTransform` helps with applying bulk transformations to syntax nodes. 29/// `AstTransform` helps with applying bulk transformations to syntax nodes.
@@ -191,11 +198,9 @@ impl<'a> AstTransform<'a> for QualifyPaths<'a> {
191 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?; 198 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
192 let mut path = mod_path_to_ast(&found_path); 199 let mut path = mod_path_to_ast(&found_path);
193 200
194 let type_args = p 201 let type_args = p.segment().and_then(|s| s.generic_arg_list());
195 .segment()
196 .and_then(|s| s.generic_arg_list())
197 .map(|arg_list| apply(recur, arg_list));
198 if let Some(type_args) = type_args { 202 if let Some(type_args) = type_args {
203 apply(recur, &type_args);
199 let last_segment = path.segment().unwrap(); 204 let last_segment = path.segment().unwrap();
200 path = path.with_segment(last_segment.with_generic_args(type_args)) 205 path = path.with_segment(last_segment.with_generic_args(type_args))
201 } 206 }
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index 49aa70f74..a454a2af3 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -93,7 +93,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
93 93
94 let range = ctx.sema.original_range(&syntax_under_caret).range; 94 let range = ctx.sema.original_range(&syntax_under_caret).range;
95 let group_label = group_label(import_assets.import_candidate()); 95 let group_label = group_label(import_assets.import_candidate());
96 let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?; 96 let scope = ImportScope::find_insert_use_container_with_macros(&syntax_under_caret, &ctx.sema)?;
97 for import in proposed_imports { 97 for import in proposed_imports {
98 acc.add_group( 98 acc.add_group(
99 &group_label, 99 &group_label,
@@ -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..66f274fa7 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
@@ -5,7 +5,7 @@ use hir::{Module, ModuleDef, Name, Variant};
5use ide_db::{ 5use ide_db::{
6 defs::Definition, 6 defs::Definition,
7 helpers::{ 7 helpers::{
8 insert_use::{insert_use, ImportScope}, 8 insert_use::{insert_use, ImportScope, InsertUseConfig},
9 mod_path_to_ast, 9 mod_path_to_ast,
10 }, 10 },
11 search::FileReference, 11 search::FileReference,
@@ -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, SyntaxNode, T,
19}; 19};
20 20
21use crate::{AssistContext, AssistId, AssistKind, Assists}; 21use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -62,40 +62,50 @@ 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 // record file references of the file the def resides in, we only want to swap to the edited file in the builder once
66 let mut def_file_references = None;
66 for (file_id, references) in usages { 67 for (file_id, references) in usages {
67 let mut rewriter = SyntaxRewriter::default();
68 let source_file = ctx.sema.parse(file_id);
69 for reference in references {
70 update_reference(
71 ctx,
72 &mut rewriter,
73 reference,
74 &source_file,
75 &enum_module_def,
76 &variant_hir_name,
77 &mut visited_modules_set,
78 );
79 }
80 if file_id == ctx.frange.file_id { 68 if file_id == ctx.frange.file_id {
81 def_rewriter = Some(rewriter); 69 def_file_references = Some(references);
82 continue; 70 continue;
83 } 71 }
84 builder.edit_file(file_id); 72 builder.edit_file(file_id);
85 builder.rewrite(rewriter); 73 let source_file = builder.make_ast_mut(ctx.sema.parse(file_id));
74 let processed = process_references(
75 ctx,
76 &mut visited_modules_set,
77 source_file.syntax(),
78 &enum_module_def,
79 &variant_hir_name,
80 references,
81 );
82 processed.into_iter().for_each(|(path, node, import)| {
83 apply_references(ctx.config.insert_use, path, node, import)
84 });
86 } 85 }
87 let mut rewriter = def_rewriter.unwrap_or_default();
88 update_variant(&mut rewriter, &variant);
89 extract_struct_def(
90 &mut rewriter,
91 &enum_ast,
92 variant_name.clone(),
93 &field_list,
94 &variant.parent_enum().syntax().clone().into(),
95 enum_ast.visibility(),
96 );
97 builder.edit_file(ctx.frange.file_id); 86 builder.edit_file(ctx.frange.file_id);
98 builder.rewrite(rewriter); 87 let source_file = builder.make_ast_mut(ctx.sema.parse(ctx.frange.file_id));
88 let variant = builder.make_ast_mut(variant.clone());
89 if let Some(references) = def_file_references {
90 let processed = process_references(
91 ctx,
92 &mut visited_modules_set,
93 source_file.syntax(),
94 &enum_module_def,
95 &variant_hir_name,
96 references,
97 );
98 processed.into_iter().for_each(|(path, node, import)| {
99 apply_references(ctx.config.insert_use, path, node, import)
100 });
101 }
102
103 let def = create_struct_def(variant_name.clone(), &field_list, enum_ast.visibility());
104 let start_offset = &variant.parent_enum().syntax().clone();
105 ted::insert_raw(ted::Position::before(start_offset), def.syntax());
106 ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
107
108 update_variant(&variant);
99 }, 109 },
100 ) 110 )
101} 111}
@@ -136,34 +146,11 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
136 .any(|(name, _)| name.to_string() == variant_name.to_string()) 146 .any(|(name, _)| name.to_string() == variant_name.to_string())
137} 147}
138 148
139fn insert_import( 149fn create_struct_def(
140 ctx: &AssistContext,
141 rewriter: &mut SyntaxRewriter,
142 scope_node: &SyntaxNode,
143 module: &Module,
144 enum_module_def: &ModuleDef,
145 variant_hir_name: &Name,
146) -> Option<()> {
147 let db = ctx.db();
148 let mod_path =
149 module.find_use_path_prefixed(db, *enum_module_def, ctx.config.insert_use.prefix_kind);
150 if let Some(mut mod_path) = mod_path {
151 mod_path.pop_segment();
152 mod_path.push_segment(variant_hir_name.clone());
153 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);
155 }
156 Some(())
157}
158
159fn extract_struct_def(
160 rewriter: &mut SyntaxRewriter,
161 enum_: &ast::Enum,
162 variant_name: ast::Name, 150 variant_name: ast::Name,
163 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, 151 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
164 start_offset: &SyntaxElement,
165 visibility: Option<ast::Visibility>, 152 visibility: Option<ast::Visibility>,
166) -> Option<()> { 153) -> ast::Struct {
167 let pub_vis = Some(make::visibility_pub()); 154 let pub_vis = Some(make::visibility_pub());
168 let field_list = match field_list { 155 let field_list = match field_list {
169 Either::Left(field_list) => { 156 Either::Left(field_list) => {
@@ -180,65 +167,90 @@ fn extract_struct_def(
180 .into(), 167 .into(),
181 }; 168 };
182 169
183 rewriter.insert_before( 170 make::struct_(visibility, variant_name, None, field_list).clone_for_update()
184 start_offset,
185 make::struct_(visibility, variant_name, None, field_list).syntax(),
186 );
187 rewriter.insert_before(start_offset, &make::tokens::blank_line());
188
189 if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
190 rewriter
191 .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
192 }
193 Some(())
194} 171}
195 172
196fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> { 173fn update_variant(variant: &ast::Variant) -> Option<()> {
197 let name = variant.name()?; 174 let name = variant.name()?;
198 let tuple_field = make::tuple_field(None, make::ty(&name.text())); 175 let tuple_field = make::tuple_field(None, make::ty(&name.text()));
199 let replacement = make::variant( 176 let replacement = make::variant(
200 name, 177 name,
201 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))), 178 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
202 ); 179 )
203 rewriter.replace(variant.syntax(), replacement.syntax()); 180 .clone_for_update();
181 ted::replace(variant.syntax(), replacement.syntax());
204 Some(()) 182 Some(())
205} 183}
206 184
207fn update_reference( 185fn apply_references(
186 insert_use_cfg: InsertUseConfig,
187 segment: ast::PathSegment,
188 node: SyntaxNode,
189 import: Option<(ImportScope, hir::ModPath)>,
190) {
191 if let Some((scope, path)) = import {
192 insert_use(&scope, mod_path_to_ast(&path), insert_use_cfg);
193 }
194 ted::insert_raw(
195 ted::Position::before(segment.syntax()),
196 make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(),
197 );
198 ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
199 ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
200}
201
202fn process_references(
208 ctx: &AssistContext, 203 ctx: &AssistContext,
209 rewriter: &mut SyntaxRewriter, 204 visited_modules: &mut FxHashSet<Module>,
210 reference: FileReference, 205 source_file: &SyntaxNode,
211 source_file: &SourceFile,
212 enum_module_def: &ModuleDef, 206 enum_module_def: &ModuleDef,
213 variant_hir_name: &Name, 207 variant_hir_name: &Name,
214 visited_modules_set: &mut FxHashSet<Module>, 208 refs: Vec<FileReference>,
215) -> Option<()> { 209) -> Vec<(ast::PathSegment, SyntaxNode, Option<(ImportScope, hir::ModPath)>)> {
210 // we have to recollect here eagerly as we are about to edit the tree we need to calculate the changes
211 // and corresponding nodes up front
212 refs.into_iter()
213 .flat_map(|reference| {
214 let (segment, scope_node, module) =
215 reference_to_node(&ctx.sema, source_file, reference)?;
216 if !visited_modules.contains(&module) {
217 let mod_path = module.find_use_path_prefixed(
218 ctx.sema.db,
219 *enum_module_def,
220 ctx.config.insert_use.prefix_kind,
221 );
222 if let Some(mut mod_path) = mod_path {
223 mod_path.pop_segment();
224 mod_path.push_segment(variant_hir_name.clone());
225 let scope = ImportScope::find_insert_use_container(&scope_node)?;
226 visited_modules.insert(module);
227 return Some((segment, scope_node, Some((scope, mod_path))));
228 }
229 }
230 Some((segment, scope_node, None))
231 })
232 .collect()
233}
234
235fn reference_to_node(
236 sema: &hir::Semantics<RootDatabase>,
237 source_file: &SyntaxNode,
238 reference: FileReference,
239) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
216 let offset = reference.range.start(); 240 let offset = reference.range.start();
217 let (segment, expr) = if let Some(path_expr) = 241 if let Some(path_expr) = find_node_at_offset::<ast::PathExpr>(source_file, offset) {
218 find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
219 {
220 // tuple variant 242 // tuple variant
221 (path_expr.path()?.segment()?, path_expr.syntax().parent()?) 243 Some((path_expr.path()?.segment()?, path_expr.syntax().parent()?))
222 } else if let Some(record_expr) = 244 } else if let Some(record_expr) = find_node_at_offset::<ast::RecordExpr>(source_file, offset) {
223 find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
224 {
225 // record variant 245 // record variant
226 (record_expr.path()?.segment()?, record_expr.syntax().clone()) 246 Some((record_expr.path()?.segment()?, record_expr.syntax().clone()))
227 } else { 247 } else {
228 return None; 248 None
229 };
230
231 let module = ctx.sema.scope(&expr).module()?;
232 if !visited_modules_set.contains(&module) {
233 if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
234 {
235 visited_modules_set.insert(module);
236 }
237 } 249 }
238 rewriter.insert_after(segment.syntax(), &make::token(T!['('])); 250 .and_then(|(segment, expr)| {
239 rewriter.insert_after(segment.syntax(), segment.syntax()); 251 let module = sema.scope(&expr).module()?;
240 rewriter.insert_after(&expr, &make::token(T![')'])); 252 Some((segment, expr, module))
241 Some(()) 253 })
242} 254}
243 255
244#[cfg(test)] 256#[cfg(test)]
@@ -345,7 +357,7 @@ mod my_mod {
345 357
346 pub struct MyField(pub u8, pub u8); 358 pub struct MyField(pub u8, pub u8);
347 359
348 pub enum MyEnum { 360pub enum MyEnum {
349 MyField(MyField), 361 MyField(MyField),
350 } 362 }
351 } 363 }
diff --git a/crates/ide_assists/src/handlers/reorder_fields.rs b/crates/ide_assists/src/handlers/reorder_fields.rs
index 1a95135ca..e90bbdbcf 100644
--- a/crates/ide_assists/src/handlers/reorder_fields.rs
+++ b/crates/ide_assists/src/handlers/reorder_fields.rs
@@ -83,11 +83,9 @@ fn replace<T: AstNode + PartialEq>(
83 fields: impl Iterator<Item = T>, 83 fields: impl Iterator<Item = T>,
84 sorted_fields: impl IntoIterator<Item = T>, 84 sorted_fields: impl IntoIterator<Item = T>,
85) { 85) {
86 fields.zip(sorted_fields).filter(|(field, sorted)| field != sorted).for_each( 86 fields.zip(sorted_fields).for_each(|(field, sorted_field)| {
87 |(field, sorted_field)| { 87 ted::replace(field.syntax(), sorted_field.syntax().clone_for_update())
88 ted::replace(field.syntax(), sorted_field.syntax().clone_for_update()); 88 });
89 },
90 );
91} 89}
92 90
93fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { 91fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
diff --git a/crates/ide_assists/src/handlers/reorder_impl.rs b/crates/ide_assists/src/handlers/reorder_impl.rs
index f976e73ad..72d889248 100644
--- a/crates/ide_assists/src/handlers/reorder_impl.rs
+++ b/crates/ide_assists/src/handlers/reorder_impl.rs
@@ -4,9 +4,8 @@ use rustc_hash::FxHashMap;
4use hir::{PathResolution, Semantics}; 4use hir::{PathResolution, Semantics};
5use ide_db::RootDatabase; 5use ide_db::RootDatabase;
6use syntax::{ 6use syntax::{
7 algo,
8 ast::{self, NameOwner}, 7 ast::{self, NameOwner},
9 AstNode, 8 ted, AstNode,
10}; 9};
11 10
12use crate::{AssistContext, AssistId, AssistKind, Assists}; 11use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -75,13 +74,16 @@ pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
75 } 74 }
76 75
77 let target = items.syntax().text_range(); 76 let target = items.syntax().text_range();
78 acc.add(AssistId("reorder_impl", AssistKind::RefactorRewrite), "Sort methods", target, |edit| { 77 acc.add(
79 let mut rewriter = algo::SyntaxRewriter::default(); 78 AssistId("reorder_impl", AssistKind::RefactorRewrite),
80 for (old, new) in methods.iter().zip(&sorted) { 79 "Sort methods",
81 rewriter.replace(old.syntax(), new.syntax()); 80 target,
82 } 81 |builder| {
83 edit.rewrite(rewriter); 82 methods.into_iter().zip(sorted).for_each(|(old, new)| {
84 }) 83 ted::replace(builder.make_ast_mut(old).syntax(), new.clone_for_update().syntax())
84 });
85 },
86 )
85} 87}
86 88
87fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { 89fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
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..99ba79860 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
@@ -31,7 +31,7 @@ pub(crate) fn replace_qualified_name_with_use(
31 } 31 }
32 32
33 let target = path.syntax().text_range(); 33 let target = path.syntax().text_range();
34 let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?; 34 let scope = ImportScope::find_insert_use_container_with_macros(path.syntax(), &ctx.sema)?;
35 let syntax = scope.as_syntax_node(); 35 let syntax = scope.as_syntax_node();
36 acc.add( 36 acc.add(
37 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), 37 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
@@ -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 shorten_paths(&syntax, &path.clone_for_update());
47 builder.rewrite(rewriter); 46 insert_use(import_scope, path, ctx.config.insert_use);
48 } 47 }
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 {
@@ -60,34 +59,26 @@ fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path:
60 ast::Use(_it) => continue, 59 ast::Use(_it) => continue,
61 // Don't descend into submodules, they don't have the same `use` items in scope. 60 // Don't descend into submodules, they don't have the same `use` items in scope.
62 ast::Module(_it) => continue, 61 ast::Module(_it) => continue,
63 62 ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
64 ast::Path(p) => { 63 shorten_paths(p.syntax(), path);
65 match maybe_replace_path(rewriter, p.clone(), path.clone()) {
66 Some(()) => {},
67 None => shorten_paths(rewriter, p.syntax().clone(), path),
68 }
69 }, 64 },
70 _ => shorten_paths(rewriter, child, path), 65 _ => shorten_paths(&child, path),
71 } 66 }
72 } 67 }
73 } 68 }
74} 69}
75 70
76fn maybe_replace_path( 71fn 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) { 72 if !path_eq(path.clone(), target) {
82 return None; 73 return None;
83 } 74 }
84 75
85 // Shorten `path`, leaving only its last segment. 76 // Shorten `path`, leaving only its last segment.
86 if let Some(parent) = path.qualifier() { 77 if let Some(parent) = path.qualifier() {
87 rewriter.delete(parent.syntax()); 78 ted::remove(parent.syntax());
88 } 79 }
89 if let Some(double_colon) = path.coloncolon_token() { 80 if let Some(double_colon) = path.coloncolon_token() {
90 rewriter.delete(&double_colon); 81 ted::remove(&double_colon);
91 } 82 }
92 83
93 Some(()) 84 Some(())
@@ -150,6 +141,7 @@ Debug
150 ", 141 ",
151 ); 142 );
152 } 143 }
144
153 #[test] 145 #[test]
154 fn test_replace_add_use_no_anchor_with_item_below() { 146 fn test_replace_add_use_no_anchor_with_item_below() {
155 check_assist( 147 check_assist(
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index d67524937..5a90ad715 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -140,7 +140,8 @@ pub fn add_trait_assoc_items_to_impl(
140 140
141 let items = items 141 let items = items
142 .into_iter() 142 .into_iter()
143 .map(|it| ast_transform::apply(&*ast_transform, it)) 143 .map(|it| it.clone_for_update())
144 .inspect(|it| ast_transform::apply(&*ast_transform, it))
144 .map(|it| match it { 145 .map(|it| match it {
145 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), 146 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
146 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), 147 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index 8e211ae1e..9d5b61562 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -132,7 +132,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
132 132
133 let user_input_lowercased = potential_import_name.to_lowercase(); 133 let user_input_lowercased = potential_import_name.to_lowercase();
134 let import_assets = import_assets(ctx, potential_import_name)?; 134 let import_assets = import_assets(ctx, potential_import_name)?;
135 let import_scope = ImportScope::find_insert_use_container( 135 let import_scope = ImportScope::find_insert_use_container_with_macros(
136 position_for_import(ctx, Some(import_assets.import_candidate()))?, 136 position_for_import(ctx, Some(import_assets.import_candidate()))?,
137 &ctx.sema, 137 &ctx.sema,
138 )?; 138 )?;
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_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index 6f3d5c5c5..e32633565 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -179,7 +179,7 @@ pub fn resolve_completion_edits(
179) -> Option<Vec<TextEdit>> { 179) -> Option<Vec<TextEdit>> {
180 let ctx = CompletionContext::new(db, position, config)?; 180 let ctx = CompletionContext::new(db, position, config)?;
181 let position_for_import = position_for_import(&ctx, None)?; 181 let position_for_import = position_for_import(&ctx, None)?;
182 let scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?; 182 let scope = ImportScope::find_insert_use_container_with_macros(position_for_import, &ctx.sema)?;
183 183
184 let current_module = ctx.sema.scope(position_for_import).module()?; 184 let current_module = ctx.sema.scope(position_for_import).module()?;
185 let current_crate = current_module.krate(); 185 let current_crate = current_module.krate();
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;
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 3d151e629..048c213e2 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -5,6 +5,7 @@ use test_utils::assert_eq_text;
5 5
6#[test] 6#[test]
7fn insert_not_group() { 7fn insert_not_group() {
8 cov_mark::check!(insert_no_grouping_last);
8 check( 9 check(
9 "use external_crate2::bar::A", 10 "use external_crate2::bar::A",
10 r" 11 r"
@@ -27,6 +28,21 @@ use external_crate2::bar::A;",
27} 28}
28 29
29#[test] 30#[test]
31fn insert_not_group_empty() {
32 cov_mark::check!(insert_no_grouping_last2);
33 check(
34 "use external_crate2::bar::A",
35 r"",
36 r"use external_crate2::bar::A;
37
38",
39 None,
40 false,
41 false,
42 );
43}
44
45#[test]
30fn insert_existing() { 46fn insert_existing() {
31 check_full("std::fs", "use std::fs;", "use std::fs;") 47 check_full("std::fs", "use std::fs;", "use std::fs;")
32} 48}
@@ -51,21 +67,21 @@ use std::bar::G;",
51 67
52#[test] 68#[test]
53fn insert_start_indent() { 69fn insert_start_indent() {
54 cov_mark::check!(insert_use_indent_after);
55 check_none( 70 check_none(
56 "std::bar::AA", 71 "std::bar::AA",
57 r" 72 r"
58 use std::bar::B; 73 use std::bar::B;
59 use std::bar::D;", 74 use std::bar::C;",
60 r" 75 r"
61 use std::bar::AA; 76 use std::bar::AA;
62 use std::bar::B; 77 use std::bar::B;
63 use std::bar::D;", 78 use std::bar::C;",
64 ) 79 );
65} 80}
66 81
67#[test] 82#[test]
68fn insert_middle() { 83fn insert_middle() {
84 cov_mark::check!(insert_group);
69 check_none( 85 check_none(
70 "std::bar::EE", 86 "std::bar::EE",
71 r" 87 r"
@@ -102,6 +118,7 @@ fn insert_middle_indent() {
102 118
103#[test] 119#[test]
104fn insert_end() { 120fn insert_end() {
121 cov_mark::check!(insert_group_last);
105 check_none( 122 check_none(
106 "std::bar::ZZ", 123 "std::bar::ZZ",
107 r" 124 r"
@@ -120,7 +137,6 @@ use std::bar::ZZ;",
120 137
121#[test] 138#[test]
122fn insert_end_indent() { 139fn insert_end_indent() {
123 cov_mark::check!(insert_use_indent_before);
124 check_none( 140 check_none(
125 "std::bar::ZZ", 141 "std::bar::ZZ",
126 r" 142 r"
@@ -201,6 +217,7 @@ fn insert_first_matching_group() {
201 217
202#[test] 218#[test]
203fn insert_missing_group_std() { 219fn insert_missing_group_std() {
220 cov_mark::check!(insert_group_new_group);
204 check_none( 221 check_none(
205 "std::fmt", 222 "std::fmt",
206 r" 223 r"
@@ -216,6 +233,7 @@ fn insert_missing_group_std() {
216 233
217#[test] 234#[test]
218fn insert_missing_group_self() { 235fn insert_missing_group_self() {
236 cov_mark::check!(insert_group_no_group);
219 check_none( 237 check_none(
220 "self::fmt", 238 "self::fmt",
221 r" 239 r"
@@ -242,6 +260,7 @@ fn main() {}",
242 260
243#[test] 261#[test]
244fn insert_empty_file() { 262fn insert_empty_file() {
263 cov_mark::check!(insert_group_empty_file);
245 // empty files will get two trailing newlines 264 // empty files will get two trailing newlines
246 // this is due to the test case insert_no_imports above 265 // this is due to the test case insert_no_imports above
247 check_full( 266 check_full(
@@ -255,7 +274,7 @@ fn insert_empty_file() {
255 274
256#[test] 275#[test]
257fn insert_empty_module() { 276fn insert_empty_module() {
258 cov_mark::check!(insert_use_no_indent_after); 277 cov_mark::check!(insert_group_empty_module);
259 check( 278 check(
260 "foo::bar", 279 "foo::bar",
261 "mod x {}", 280 "mod x {}",
@@ -270,6 +289,7 @@ fn insert_empty_module() {
270 289
271#[test] 290#[test]
272fn insert_after_inner_attr() { 291fn insert_after_inner_attr() {
292 cov_mark::check!(insert_group_empty_inner_attr);
273 check_full( 293 check_full(
274 "foo::bar", 294 "foo::bar",
275 r"#![allow(unused_imports)]", 295 r"#![allow(unused_imports)]",
@@ -615,7 +635,7 @@ fn check(
615 if module { 635 if module {
616 syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone(); 636 syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone();
617 } 637 }
618 let file = super::ImportScope::from(syntax).unwrap(); 638 let file = super::ImportScope::from(syntax.clone_for_update()).unwrap();
619 let path = ast::SourceFile::parse(&format!("use {};", path)) 639 let path = ast::SourceFile::parse(&format!("use {};", path))
620 .tree() 640 .tree()
621 .syntax() 641 .syntax()
@@ -623,12 +643,8 @@ fn check(
623 .find_map(ast::Path::cast) 643 .find_map(ast::Path::cast)
624 .unwrap(); 644 .unwrap();
625 645
626 let rewriter = insert_use( 646 insert_use(&file, path, InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group });
627 &file, 647 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); 648 assert_eq_text!(ra_fixture_after, &result);
633} 649}
634 650
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index a153a9e1c..c9229c4e0 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -342,10 +342,10 @@ enum InsertPos {
342 342
343#[derive(Default)] 343#[derive(Default)]
344pub struct SyntaxRewriter<'a> { 344pub struct SyntaxRewriter<'a> {
345 f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>,
346 //FIXME: add debug_assertions that all elements are in fact from the same file. 345 //FIXME: add debug_assertions that all elements are in fact from the same file.
347 replacements: FxHashMap<SyntaxElement, Replacement>, 346 replacements: FxHashMap<SyntaxElement, Replacement>,
348 insertions: IndexMap<InsertPos, Vec<SyntaxElement>>, 347 insertions: IndexMap<InsertPos, Vec<SyntaxElement>>,
348 _pd: std::marker::PhantomData<&'a ()>,
349} 349}
350 350
351impl fmt::Debug for SyntaxRewriter<'_> { 351impl fmt::Debug for SyntaxRewriter<'_> {
@@ -357,14 +357,7 @@ impl fmt::Debug for SyntaxRewriter<'_> {
357 } 357 }
358} 358}
359 359
360impl<'a> SyntaxRewriter<'a> { 360impl SyntaxRewriter<'_> {
361 pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> {
362 SyntaxRewriter {
363 f: Some(Box::new(f)),
364 replacements: FxHashMap::default(),
365 insertions: IndexMap::default(),
366 }
367 }
368 pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) { 361 pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) {
369 let what = what.clone().into(); 362 let what = what.clone().into();
370 let replacement = Replacement::Delete; 363 let replacement = Replacement::Delete;
@@ -470,7 +463,7 @@ impl<'a> SyntaxRewriter<'a> {
470 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode { 463 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode {
471 let _p = profile::span("rewrite"); 464 let _p = profile::span("rewrite");
472 465
473 if self.f.is_none() && self.replacements.is_empty() && self.insertions.is_empty() { 466 if self.replacements.is_empty() && self.insertions.is_empty() {
474 return node.clone(); 467 return node.clone();
475 } 468 }
476 let green = self.rewrite_children(node); 469 let green = self.rewrite_children(node);
@@ -495,7 +488,6 @@ impl<'a> SyntaxRewriter<'a> {
495 } 488 }
496 } 489 }
497 490
498 assert!(self.f.is_none());
499 self.replacements 491 self.replacements
500 .keys() 492 .keys()
501 .filter_map(element_to_node_or_parent) 493 .filter_map(element_to_node_or_parent)
@@ -510,10 +502,6 @@ impl<'a> SyntaxRewriter<'a> {
510 } 502 }
511 503
512 fn replacement(&self, element: &SyntaxElement) -> Option<Replacement> { 504 fn replacement(&self, element: &SyntaxElement) -> Option<Replacement> {
513 if let Some(f) = &self.f {
514 assert!(self.replacements.is_empty());
515 return f(element).map(Replacement::Single);
516 }
517 self.replacements.get(element).cloned() 505 self.replacements.get(element).cloned()
518 } 506 }
519 507
@@ -574,7 +562,6 @@ fn element_to_green(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, row
574 562
575impl ops::AddAssign for SyntaxRewriter<'_> { 563impl ops::AddAssign for SyntaxRewriter<'_> {
576 fn add_assign(&mut self, rhs: SyntaxRewriter) { 564 fn add_assign(&mut self, rhs: SyntaxRewriter) {
577 assert!(rhs.f.is_none());
578 self.replacements.extend(rhs.replacements); 565 self.replacements.extend(rhs.replacements);
579 for (pos, insertions) in rhs.insertions.into_iter() { 566 for (pos, insertions) in rhs.insertions.into_iter() {
580 match self.insertions.entry(pos) { 567 match self.insertions.entry(pos) {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 222b7e212..42da09606 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -632,6 +632,7 @@ pub mod tokens {
632 SOURCE_FILE 632 SOURCE_FILE
633 .tree() 633 .tree()
634 .syntax() 634 .syntax()
635 .clone_for_update()
635 .descendants_with_tokens() 636 .descendants_with_tokens()
636 .filter_map(|it| it.into_token()) 637 .filter_map(|it| it.into_token())
637 .find(|it| it.kind() == WHITESPACE && it.text() == "\n\n") 638 .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())