Skip to content

Commit cd349db

Browse files
committed
Make insert_use return a SyntaxRewriter
1 parent 245e1b5 commit cd349db

File tree

5 files changed

+129
-107
lines changed

5 files changed

+129
-107
lines changed

crates/assists/src/handlers/auto_import.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,16 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
9999
let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
100100
let group = import_group_message(import_assets.import_candidate());
101101
let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
102-
let syntax = scope.as_syntax_node();
103102
for (import, _) in proposed_imports {
104103
acc.add_group(
105104
&group,
106105
AssistId("auto_import", AssistKind::QuickFix),
107106
format!("Import `{}`", &import),
108107
range,
109108
|builder| {
110-
let new_syntax =
109+
let rewriter =
111110
insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge);
112-
builder.replace(syntax.text_range(), new_syntax.to_string())
111+
builder.rewrite(rewriter);
113112
},
114113
);
115114
}

crates/assists/src/handlers/extract_struct_from_enum_variant.rs

Lines changed: 69 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
use hir::{EnumVariant, Module, ModuleDef, Name};
2-
use ide_db::base_db::FileId;
1+
use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
32
use ide_db::{defs::Definition, search::Reference, RootDatabase};
4-
use itertools::Itertools;
5-
use rustc_hash::FxHashSet;
3+
use rustc_hash::{FxHashMap, FxHashSet};
64
use syntax::{
75
algo::find_node_at_offset,
8-
ast::{self, edit::IndentLevel, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
9-
SourceFile, TextRange, TextSize,
6+
algo::SyntaxRewriter,
7+
ast::{self, edit::IndentLevel, make, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
8+
SourceFile, SyntaxElement,
109
};
1110

1211
use crate::{
13-
assist_context::AssistBuilder,
1412
utils::{insert_use, mod_path_to_ast, ImportScope},
1513
AssistContext, AssistId, AssistKind, Assists,
1614
};
@@ -43,7 +41,7 @@ pub(crate) fn extract_struct_from_enum_variant(
4341
return None;
4442
}
4543

46-
let variant_name = variant.name()?.to_string();
44+
let variant_name = variant.name()?;
4745
let variant_hir = ctx.sema.to_def(&variant)?;
4846
if existing_struct_def(ctx.db(), &variant_name, &variant_hir) {
4947
return None;
@@ -62,49 +60,58 @@ pub(crate) fn extract_struct_from_enum_variant(
6260
|builder| {
6361
let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
6462
let res = definition.usages(&ctx.sema).all();
65-
let start_offset = variant.parent_enum().syntax().text_range().start();
63+
6664
let mut visited_modules_set = FxHashSet::default();
6765
visited_modules_set.insert(current_module);
66+
let mut rewriters = FxHashMap::default();
6867
for reference in res {
68+
let rewriter = rewriters
69+
.entry(reference.file_range.file_id)
70+
.or_insert_with(SyntaxRewriter::default);
6971
let source_file = ctx.sema.parse(reference.file_range.file_id);
7072
update_reference(
7173
ctx,
72-
builder,
74+
rewriter,
7375
reference,
7476
&source_file,
7577
&enum_module_def,
7678
&variant_hir_name,
7779
&mut visited_modules_set,
7880
);
7981
}
82+
let mut rewriter =
83+
rewriters.remove(&ctx.frange.file_id).unwrap_or_else(SyntaxRewriter::default);
84+
for (file_id, rewriter) in rewriters {
85+
builder.edit_file(file_id);
86+
builder.rewrite(rewriter);
87+
}
88+
builder.edit_file(ctx.frange.file_id);
89+
update_variant(&mut rewriter, &variant_name, &field_list);
8090
extract_struct_def(
81-
builder,
91+
&mut rewriter,
8292
&enum_ast,
83-
&variant_name,
84-
&field_list.to_string(),
85-
start_offset,
86-
ctx.frange.file_id,
87-
&visibility,
93+
variant_name.clone(),
94+
&field_list,
95+
&variant.parent_enum().syntax().clone().into(),
96+
visibility,
8897
);
89-
let list_range = field_list.syntax().text_range();
90-
update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
98+
builder.rewrite(rewriter);
9199
},
92100
)
93101
}
94102

95-
fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
103+
fn existing_struct_def(db: &RootDatabase, variant_name: &ast::Name, variant: &EnumVariant) -> bool {
96104
variant
97105
.parent_enum(db)
98106
.module(db)
99107
.scope(db, None)
100108
.into_iter()
101-
.any(|(name, _)| name.to_string() == variant_name)
109+
.any(|(name, _)| name == variant_name.as_name())
102110
}
103111

104-
#[allow(dead_code)]
105112
fn insert_import(
106113
ctx: &AssistContext,
107-
builder: &mut AssistBuilder,
114+
rewriter: &mut SyntaxRewriter,
108115
path: &ast::PathExpr,
109116
module: &Module,
110117
enum_module_def: &ModuleDef,
@@ -116,69 +123,59 @@ fn insert_import(
116123
mod_path.segments.pop();
117124
mod_path.segments.push(variant_hir_name.clone());
118125
let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
119-
let syntax = scope.as_syntax_node();
120126

121-
let new_syntax =
122-
insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
123-
// FIXME: this will currently panic as multiple imports will have overlapping text ranges
124-
builder.replace(syntax.text_range(), new_syntax.to_string())
127+
*rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
125128
}
126129
Some(())
127130
}
128131

129-
// FIXME: this should use strongly-typed `make`, rather than string manipulation.
130132
fn extract_struct_def(
131-
builder: &mut AssistBuilder,
133+
rewriter: &mut SyntaxRewriter,
132134
enum_: &ast::Enum,
133-
variant_name: &str,
134-
variant_list: &str,
135-
start_offset: TextSize,
136-
file_id: FileId,
137-
visibility: &Option<ast::Visibility>,
135+
variant_name: ast::Name,
136+
variant_list: &ast::TupleFieldList,
137+
start_offset: &SyntaxElement,
138+
visibility: Option<ast::Visibility>,
138139
) -> Option<()> {
139-
let visibility_string = if let Some(visibility) = visibility {
140-
format!("{} ", visibility.to_string())
141-
} else {
142-
"".to_string()
143-
};
144-
let indent = IndentLevel::from_node(enum_.syntax());
145-
let struct_def = format!(
146-
r#"{}struct {}{};
147-
148-
{}"#,
149-
visibility_string,
150-
variant_name,
151-
list_with_visibility(variant_list),
152-
indent
140+
let variant_list = make::tuple_field_list(
141+
variant_list
142+
.fields()
143+
.flat_map(|field| Some(make::tuple_field(Some(make::visibility_pub()), field.ty()?))),
153144
);
154-
builder.edit_file(file_id);
155-
builder.insert(start_offset, struct_def);
145+
146+
rewriter.insert_before(
147+
start_offset,
148+
make::struct_(visibility, variant_name, None, variant_list.into()).syntax(),
149+
);
150+
rewriter.insert_before(start_offset, &make::tokens::blank_line());
151+
152+
if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
153+
rewriter
154+
.insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
155+
}
156156
Some(())
157157
}
158158

159159
fn update_variant(
160-
builder: &mut AssistBuilder,
161-
variant_name: &str,
162-
file_id: FileId,
163-
list_range: TextRange,
160+
rewriter: &mut SyntaxRewriter,
161+
variant_name: &ast::Name,
162+
field_list: &ast::TupleFieldList,
164163
) -> Option<()> {
165-
let inside_variant_range = TextRange::new(
166-
list_range.start().checked_add(TextSize::from(1))?,
167-
list_range.end().checked_sub(TextSize::from(1))?,
168-
);
169-
builder.edit_file(file_id);
170-
builder.replace(inside_variant_range, variant_name);
164+
let (l, r): (SyntaxElement, SyntaxElement) =
165+
(field_list.l_paren_token()?.into(), field_list.r_paren_token()?.into());
166+
let replacement = vec![l, variant_name.syntax().clone().into(), r];
167+
rewriter.replace_with_many(field_list.syntax(), replacement);
171168
Some(())
172169
}
173170

174171
fn update_reference(
175172
ctx: &AssistContext,
176-
builder: &mut AssistBuilder,
173+
rewriter: &mut SyntaxRewriter,
177174
reference: Reference,
178175
source_file: &SourceFile,
179-
_enum_module_def: &ModuleDef,
180-
_variant_hir_name: &Name,
181-
_visited_modules_set: &mut FxHashSet<Module>,
176+
enum_module_def: &ModuleDef,
177+
variant_hir_name: &Name,
178+
visited_modules_set: &mut FxHashSet<Module>,
182179
) -> Option<()> {
183180
let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
184181
source_file.syntax(),
@@ -187,35 +184,21 @@ fn update_reference(
187184
let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
188185
let list = call.arg_list()?;
189186
let segment = path_expr.path()?.segment()?;
190-
let _module = ctx.sema.scope(&path_expr.syntax()).module()?;
191-
let list_range = list.syntax().text_range();
192-
let inside_list_range = TextRange::new(
193-
list_range.start().checked_add(TextSize::from(1))?,
194-
list_range.end().checked_sub(TextSize::from(1))?,
195-
);
196-
builder.edit_file(reference.file_range.file_id);
197-
/* FIXME: this most likely requires AST-based editing, see `insert_import`
187+
let module = ctx.sema.scope(&path_expr.syntax()).module()?;
198188
if !visited_modules_set.contains(&module) {
199-
if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
189+
if insert_import(ctx, rewriter, &path_expr, &module, enum_module_def, variant_hir_name)
200190
.is_some()
201191
{
202192
visited_modules_set.insert(module);
203193
}
204194
}
205-
*/
206-
builder.replace(inside_list_range, format!("{}{}", segment, list));
207-
Some(())
208-
}
209195

210-
fn list_with_visibility(list: &str) -> String {
211-
list.split(',')
212-
.map(|part| {
213-
let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
214-
let mut mod_part = part.trim().to_string();
215-
mod_part.insert_str(index, "pub ");
216-
mod_part
217-
})
218-
.join(", ")
196+
let lparen = syntax::SyntaxElement::from(list.l_paren_token()?);
197+
let rparen = syntax::SyntaxElement::from(list.r_paren_token()?);
198+
rewriter.insert_after(&lparen, segment.syntax());
199+
rewriter.insert_after(&lparen, &lparen);
200+
rewriter.insert_before(&rparen, &rparen);
201+
Some(())
219202
}
220203

221204
#[cfg(test)]
@@ -250,7 +233,6 @@ pub enum A { One(One) }"#,
250233
}
251234

252235
#[test]
253-
#[ignore] // FIXME: this currently panics if `insert_import` is used
254236
fn test_extract_struct_with_complex_imports() {
255237
check_assist(
256238
extract_struct_from_enum_variant,

crates/assists/src/handlers/replace_qualified_name_with_use.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@ pub(crate) fn replace_qualified_name_with_use(
4545
// affected (that is, all paths inside the node we added the `use` to).
4646
let mut rewriter = SyntaxRewriter::default();
4747
shorten_paths(&mut rewriter, syntax.clone(), &path);
48-
let rewritten_syntax = rewriter.rewrite(&syntax);
49-
if let Some(ref import_scope) = ImportScope::from(rewritten_syntax) {
50-
let new_syntax = insert_use(import_scope, path, ctx.config.insert_use.merge);
51-
builder.replace(syntax.text_range(), new_syntax.to_string())
48+
if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
49+
rewriter += insert_use(import_scope, path, ctx.config.insert_use.merge);
50+
builder.rewrite(rewriter);
5251
}
5352
},
5453
)

crates/assists/src/utils/insert_use.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
//! Handle syntactic aspects of inserting a new `use`.
2-
use std::{
3-
cmp::Ordering,
4-
iter::{self, successors},
5-
};
2+
use std::{cmp::Ordering, iter::successors};
63

74
use itertools::{EitherOrBoth, Itertools};
85
use syntax::{
9-
algo,
6+
algo::SyntaxRewriter,
107
ast::{
118
self,
129
edit::{AstNodeEdit, IndentLevel},
@@ -88,20 +85,19 @@ impl ImportScope {
8885
}
8986

9087
/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
91-
pub(crate) fn insert_use(
88+
pub(crate) fn insert_use<'a>(
9289
scope: &ImportScope,
9390
path: ast::Path,
9491
merge: Option<MergeBehaviour>,
95-
) -> SyntaxNode {
92+
) -> SyntaxRewriter<'a> {
93+
let mut rewriter = SyntaxRewriter::default();
9694
let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
9795
// merge into existing imports if possible
9896
if let Some(mb) = merge {
9997
for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
10098
if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
101-
let to_delete: SyntaxElement = existing_use.syntax().clone().into();
102-
let to_delete = to_delete.clone()..=to_delete;
103-
let to_insert = iter::once(merged.syntax().clone().into());
104-
return algo::replace_children(scope.as_syntax_node(), to_delete, to_insert);
99+
rewriter.replace(existing_use.syntax(), merged.syntax());
100+
return rewriter;
105101
}
106102
}
107103
}
@@ -157,7 +153,15 @@ pub(crate) fn insert_use(
157153
buf
158154
};
159155

160-
algo::insert_children(scope.as_syntax_node(), insert_position, to_insert)
156+
match insert_position {
157+
InsertPosition::First => {
158+
rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert)
159+
}
160+
InsertPosition::Last => return rewriter, // actually unreachable
161+
InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert),
162+
InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert),
163+
}
164+
rewriter
161165
}
162166

163167
fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
@@ -1101,7 +1105,8 @@ use foo::bar::baz::Qux;",
11011105
.find_map(ast::Path::cast)
11021106
.unwrap();
11031107

1104-
let result = insert_use(&file, path, mb).to_string();
1108+
let rewriter = insert_use(&file, path, mb);
1109+
let result = rewriter.rewrite(file.as_syntax_node()).to_string();
11051110
assert_eq_text!(&result, ra_fixture_after);
11061111
}
11071112

0 commit comments

Comments
 (0)