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