diff options
-rw-r--r-- | crates/ide_assists/src/ast_transform.rs | 33 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/auto_import.rs | 10 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs | 208 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/reorder_fields.rs | 8 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/reorder_impl.rs | 20 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs | 34 | ||||
-rw-r--r-- | crates/ide_assists/src/utils.rs | 3 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/flyimport.rs | 2 | ||||
-rw-r--r-- | crates/ide_completion/src/item.rs | 8 | ||||
-rw-r--r-- | crates/ide_completion/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use.rs | 279 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use/tests.rs | 42 | ||||
-rw-r--r-- | crates/syntax/src/algo.rs | 19 | ||||
-rw-r--r-- | crates/syntax/src/ast/make.rs | 1 | ||||
-rw-r--r-- | crates/syntax/src/ted.rs | 17 |
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}; | |||
3 | use ide_db::helpers::mod_path_to_ast; | 3 | use ide_db::helpers::mod_path_to_ast; |
4 | use rustc_hash::FxHashMap; | 4 | use rustc_hash::FxHashMap; |
5 | use syntax::{ | 5 | use syntax::{ |
6 | algo::SyntaxRewriter, | ||
7 | ast::{self, AstNode}, | 6 | ast::{self, AstNode}, |
8 | SyntaxNode, | 7 | ted, SyntaxNode, |
9 | }; | 8 | }; |
10 | 9 | ||
11 | pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { | 10 | pub 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}; | |||
5 | use ide_db::{ | 5 | use 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 | }; |
14 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
15 | use syntax::{ | 15 | use 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 | ||
21 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 21 | use 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 | ||
139 | fn insert_import( | 149 | fn 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 | |||
159 | fn 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 | ||
196 | fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> { | 173 | fn 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 | ||
207 | fn update_reference( | 185 | fn 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 | |||
202 | fn 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 | |||
235 | fn 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 { | 360 | pub 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 | ||
93 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { | 91 | fn 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; | |||
4 | use hir::{PathResolution, Semantics}; | 4 | use hir::{PathResolution, Semantics}; |
5 | use ide_db::RootDatabase; | 5 | use ide_db::RootDatabase; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | algo, | ||
8 | ast::{self, NameOwner}, | 7 | ast::{self, NameOwner}, |
9 | AstNode, | 8 | ted, AstNode, |
10 | }; | 9 | }; |
11 | 10 | ||
12 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 11 | use 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 | ||
87 | fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { | 89 | fn 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 @@ | |||
1 | use ide_db::helpers::insert_use::{insert_use, ImportScope}; | 1 | use ide_db::helpers::insert_use::{insert_use, ImportScope}; |
2 | use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode}; | 2 | use syntax::{ast, match_ast, ted, AstNode, SyntaxNode}; |
3 | 3 | ||
4 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 4 | use 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`. |
54 | fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) { | 53 | fn 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 | ||
76 | fn maybe_replace_path( | 71 | fn 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}; | |||
4 | use hir::Semantics; | 4 | use hir::Semantics; |
5 | use itertools::{EitherOrBoth, Itertools}; | 5 | use itertools::{EitherOrBoth, Itertools}; |
6 | use syntax::{ | 6 | use 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 | ||
16 | use crate::RootDatabase; | 12 | use 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 | |||
92 | fn is_inner_attribute(node: SyntaxNode) -> bool { | ||
93 | ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) | ||
94 | } | ||
95 | |||
96 | fn 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. |
102 | pub fn insert_use<'a>( | 69 | pub 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 | ||
182 | fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { | 88 | fn 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)] | 434 | fn insert_use_( |
524 | enum AddBlankLine { | ||
525 | Before, | ||
526 | BeforeTwice, | ||
527 | Around, | ||
528 | After, | ||
529 | AfterTwice, | ||
530 | } | ||
531 | |||
532 | impl 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 | |||
541 | fn 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 | ||
552 | fn is_inner_attribute(node: SyntaxNode) -> bool { | ||
553 | ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) | ||
554 | } | ||
555 | |||
556 | fn 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)] |
614 | mod tests; | 561 | mod 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] |
7 | fn insert_not_group() { | 7 | fn 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] |
31 | fn 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] | ||
30 | fn insert_existing() { | 46 | fn 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] |
53 | fn insert_start_indent() { | 69 | fn 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] |
68 | fn insert_middle() { | 83 | fn 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] |
104 | fn insert_end() { | 120 | fn 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] |
122 | fn insert_end_indent() { | 139 | fn 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] |
203 | fn insert_missing_group_std() { | 219 | fn 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] |
218 | fn insert_missing_group_self() { | 235 | fn 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] |
244 | fn insert_empty_file() { | 262 | fn 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] |
257 | fn insert_empty_module() { | 276 | fn 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] |
272 | fn insert_after_inner_attr() { | 291 | fn 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)] |
344 | pub struct SyntaxRewriter<'a> { | 344 | pub 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 | ||
351 | impl fmt::Debug for SyntaxRewriter<'_> { | 351 | impl fmt::Debug for SyntaxRewriter<'_> { |
@@ -357,14 +357,7 @@ impl fmt::Debug for SyntaxRewriter<'_> { | |||
357 | } | 357 | } |
358 | } | 358 | } |
359 | 359 | ||
360 | impl<'a> SyntaxRewriter<'a> { | 360 | impl 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 | ||
575 | impl ops::AddAssign for SyntaxRewriter<'_> { | 563 | impl 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}; | |||
7 | use parser::T; | 7 | use parser::T; |
8 | 8 | ||
9 | use crate::{ | 9 | use 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 | |||
147 | fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> { | 147 | fn 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()) |