aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assist_ctx.rs17
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs5
-rw-r--r--crates/ra_assists/src/handlers/add_derive.rs4
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs2
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs2
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs6
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs59
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs14
-rw-r--r--crates/ra_assists/src/handlers/add_new.rs5
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs3
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs3
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs32
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs159
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs5
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs3
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs3
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs4
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs2
-rw-r--r--crates/ra_assists/src/handlers/introduce_variable.rs4
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs3
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs3
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs3
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs55
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs7
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs16
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs4
-rw-r--r--crates/ra_assists/src/handlers/remove_mut.rs3
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs4
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs47
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs4
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs2
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs38
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs4
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs4
-rw-r--r--crates/ra_assists/src/lib.rs16
-rw-r--r--crates/ra_assists/src/tests.rs3
36 files changed, 292 insertions, 256 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 83dd270c6..600e5689c 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -94,9 +94,10 @@ impl<'a> AssistCtx<'a> {
94 self, 94 self,
95 id: AssistId, 95 id: AssistId,
96 label: impl Into<String>, 96 label: impl Into<String>,
97 target: TextRange,
97 f: impl FnOnce(&mut ActionBuilder), 98 f: impl FnOnce(&mut ActionBuilder),
98 ) -> Option<Assist> { 99 ) -> Option<Assist> {
99 let label = AssistLabel::new(id, label.into(), None); 100 let label = AssistLabel::new(id, label.into(), None, target);
100 101
101 let mut info = AssistInfo::new(label); 102 let mut info = AssistInfo::new(label);
102 if self.should_compute_edit { 103 if self.should_compute_edit {
@@ -152,9 +153,10 @@ impl<'a> AssistGroup<'a> {
152 &mut self, 153 &mut self,
153 id: AssistId, 154 id: AssistId,
154 label: impl Into<String>, 155 label: impl Into<String>,
156 target: TextRange,
155 f: impl FnOnce(&mut ActionBuilder), 157 f: impl FnOnce(&mut ActionBuilder),
156 ) { 158 ) {
157 let label = AssistLabel::new(id, label.into(), Some(self.group.clone())); 159 let label = AssistLabel::new(id, label.into(), Some(self.group.clone()), target);
158 160
159 let mut info = AssistInfo::new(label).with_group(self.group.clone()); 161 let mut info = AssistInfo::new(label).with_group(self.group.clone());
160 if self.ctx.should_compute_edit { 162 if self.ctx.should_compute_edit {
@@ -181,7 +183,6 @@ impl<'a> AssistGroup<'a> {
181pub(crate) struct ActionBuilder<'a, 'b> { 183pub(crate) struct ActionBuilder<'a, 'b> {
182 edit: TextEditBuilder, 184 edit: TextEditBuilder,
183 cursor_position: Option<TextSize>, 185 cursor_position: Option<TextSize>,
184 target: Option<TextRange>,
185 file: AssistFile, 186 file: AssistFile,
186 ctx: &'a AssistCtx<'b>, 187 ctx: &'a AssistCtx<'b>,
187} 188}
@@ -191,7 +192,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
191 Self { 192 Self {
192 edit: TextEditBuilder::default(), 193 edit: TextEditBuilder::default(),
193 cursor_position: None, 194 cursor_position: None,
194 target: None,
195 file: AssistFile::default(), 195 file: AssistFile::default(),
196 ctx, 196 ctx,
197 } 197 }
@@ -237,14 +237,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
237 self.cursor_position = Some(offset) 237 self.cursor_position = Some(offset)
238 } 238 }
239 239
240 /// Specify that the assist should be active withing the `target` range.
241 ///
242 /// Target ranges are used to sort assists: the smaller the target range,
243 /// the more specific assist is, and so it should be sorted first.
244 pub(crate) fn target(&mut self, target: TextRange) {
245 self.target = Some(target)
246 }
247
248 /// Get access to the raw `TextEditBuilder`. 240 /// Get access to the raw `TextEditBuilder`.
249 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { 241 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
250 &mut self.edit 242 &mut self.edit
@@ -267,7 +259,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
267 AssistAction { 259 AssistAction {
268 edit: self.edit.finish(), 260 edit: self.edit.finish(),
269 cursor_position: self.cursor_position, 261 cursor_position: self.cursor_position,
270 target: self.target,
271 file: self.file, 262 file: self.file,
272 } 263 }
273 } 264 }
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index b72f7aeac..869d4dc04 100644
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -48,9 +48,8 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
48 let label = 48 let label =
49 format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name); 49 format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name);
50 50
51 ctx.add_assist(AssistId("add_custom_impl"), label, |edit| { 51 let target = attr.syntax().text_range();
52 edit.target(attr.syntax().text_range()); 52 ctx.add_assist(AssistId("add_custom_impl"), label, target, |edit| {
53
54 let new_attr_input = input 53 let new_attr_input = input
55 .syntax() 54 .syntax()
56 .descendants_with_tokens() 55 .descendants_with_tokens()
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs
index 3629dac6b..2a6bb1cae 100644
--- a/crates/ra_assists/src/handlers/add_derive.rs
+++ b/crates/ra_assists/src/handlers/add_derive.rs
@@ -27,7 +27,8 @@ use crate::{Assist, AssistCtx, AssistId};
27pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> { 27pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
28 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 28 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
29 let node_start = derive_insertion_offset(&nominal)?; 29 let node_start = derive_insertion_offset(&nominal)?;
30 ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", |edit| { 30 let target = nominal.syntax().text_range();
31 ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", target, |edit| {
31 let derive_attr = nominal 32 let derive_attr = nominal
32 .attrs() 33 .attrs()
33 .filter_map(|x| x.as_simple_call()) 34 .filter_map(|x| x.as_simple_call())
@@ -41,7 +42,6 @@ pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
41 } 42 }
42 Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'), 43 Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'),
43 }; 44 };
44 edit.target(nominal.syntax().text_range());
45 edit.set_cursor(offset) 45 edit.set_cursor(offset)
46 }) 46 })
47} 47}
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
index e39e1f4f3..a59ec16b2 100644
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -62,8 +62,8 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
62 ctx.add_assist( 62 ctx.add_assist(
63 AssistId("add_explicit_type"), 63 AssistId("add_explicit_type"),
64 format!("Insert explicit type '{}'", new_type_string), 64 format!("Insert explicit type '{}'", new_type_string),
65 pat_range,
65 |edit| { 66 |edit| {
66 edit.target(pat_range);
67 if let Some(ascribed_ty) = ascribed_ty { 67 if let Some(ascribed_ty) = ascribed_ty {
68 edit.replace(ascribed_ty.syntax().text_range(), new_type_string); 68 edit.replace(ascribed_ty.syntax().text_range(), new_type_string);
69 } else { 69 } else {
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
index ee0d5ce98..81deb3dfa 100644
--- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
+++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
@@ -47,9 +47,11 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
47 return None; 47 return None;
48 } 48 }
49 49
50 let target = variant.syntax().text_range();
50 ctx.add_assist( 51 ctx.add_assist(
51 AssistId("add_from_impl_for_enum"), 52 AssistId("add_from_impl_for_enum"),
52 "Add From impl for this enum variant", 53 "Add From impl for this enum variant",
54 target,
53 |edit| { 55 |edit| {
54 let start_offset = variant.parent_enum().syntax().text_range().end(); 56 let start_offset = variant.parent_enum().syntax().text_range().end();
55 let mut buf = String::new(); 57 let mut buf = String::new();
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs
index cb2afc863..76c0f9783 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -57,9 +57,9 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
57 57
58 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; 58 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
59 59
60 ctx.add_assist(AssistId("add_function"), "Add function", |edit| { 60 let target = call.syntax().text_range();
61 edit.target(call.syntax().text_range()); 61 // TODO: assert here?
62 62 ctx.add_assist(AssistId("add_function"), "Add function", target, |edit| {
63 if let Some(function_template) = function_builder.render() { 63 if let Some(function_template) = function_builder.render() {
64 edit.set_file(function_template.file); 64 edit.set_file(function_template.file);
65 edit.set_cursor(function_template.cursor_offset); 65 edit.set_cursor(function_template.cursor_offset);
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
index 3d390c20b..557344ebb 100644
--- a/crates/ra_assists/src/handlers/add_impl.rs
+++ b/crates/ra_assists/src/handlers/add_impl.rs
@@ -28,33 +28,40 @@ use crate::{Assist, AssistCtx, AssistId};
28pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> { 28pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
30 let name = nominal.name()?; 30 let name = nominal.name()?;
31 ctx.add_assist(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), |edit| { 31 let target = nominal.syntax().text_range();
32 edit.target(nominal.syntax().text_range()); 32 ctx.add_assist(
33 let type_params = nominal.type_param_list(); 33 AssistId("add_impl"),
34 let start_offset = nominal.syntax().text_range().end(); 34 format!("Implement {}", name.text().as_str()),
35 let mut buf = String::new(); 35 target,
36 buf.push_str("\n\nimpl"); 36 |edit| {
37 if let Some(type_params) = &type_params { 37 let type_params = nominal.type_param_list();
38 format_to!(buf, "{}", type_params.syntax()); 38 let start_offset = nominal.syntax().text_range().end();
39 } 39 let mut buf = String::new();
40 buf.push_str(" "); 40 buf.push_str("\n\nimpl");
41 buf.push_str(name.text().as_str()); 41 if let Some(type_params) = &type_params {
42 if let Some(type_params) = type_params { 42 format_to!(buf, "{}", type_params.syntax());
43 let lifetime_params = type_params 43 }
44 .lifetime_params() 44 buf.push_str(" ");
45 .filter_map(|it| it.lifetime_token()) 45 buf.push_str(name.text().as_str());
46 .map(|it| it.text().clone()); 46 if let Some(type_params) = type_params {
47 let type_params = 47 let lifetime_params = type_params
48 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); 48 .lifetime_params()
49 .filter_map(|it| it.lifetime_token())
50 .map(|it| it.text().clone());
51 let type_params = type_params
52 .type_params()
53 .filter_map(|it| it.name())
54 .map(|it| it.text().clone());
49 55
50 let generic_params = lifetime_params.chain(type_params).sep_by(", "); 56 let generic_params = lifetime_params.chain(type_params).sep_by(", ");
51 format_to!(buf, "<{}>", generic_params) 57 format_to!(buf, "<{}>", generic_params)
52 } 58 }
53 buf.push_str(" {\n"); 59 buf.push_str(" {\n");
54 edit.set_cursor(start_offset + TextSize::of(&buf)); 60 edit.set_cursor(start_offset + TextSize::of(&buf));
55 buf.push_str("\n}"); 61 buf.push_str("\n}");
56 edit.insert(start_offset, buf); 62 edit.insert(start_offset, buf);
57 }) 63 },
64 )
58} 65}
59 66
60#[cfg(test)] 67#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
index f7a101503..7df786590 100644
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -107,10 +107,10 @@ fn add_missing_impl_members_inner(
107 label: &'static str, 107 label: &'static str,
108) -> Option<Assist> { 108) -> Option<Assist> {
109 let _p = ra_prof::profile("add_missing_impl_members_inner"); 109 let _p = ra_prof::profile("add_missing_impl_members_inner");
110 let impl_node = ctx.find_node_at_offset::<ast::ImplDef>()?; 110 let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?;
111 let impl_item_list = impl_node.item_list()?; 111 let impl_item_list = impl_def.item_list()?;
112 112
113 let trait_ = resolve_target_trait(&ctx.sema, &impl_node)?; 113 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
114 114
115 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { 115 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
116 match item { 116 match item {
@@ -121,7 +121,7 @@ fn add_missing_impl_members_inner(
121 .map(|it| it.text().clone()) 121 .map(|it| it.text().clone())
122 }; 122 };
123 123
124 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_node) 124 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
125 .iter() 125 .iter()
126 .map(|i| match i { 126 .map(|i| match i {
127 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), 127 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value),
@@ -143,13 +143,13 @@ fn add_missing_impl_members_inner(
143 } 143 }
144 144
145 let sema = ctx.sema; 145 let sema = ctx.sema;
146 146 let target = impl_def.syntax().text_range();
147 ctx.add_assist(AssistId(assist_id), label, |edit| { 147 ctx.add_assist(AssistId(assist_id), label, target, |edit| {
148 let n_existing_items = impl_item_list.assoc_items().count(); 148 let n_existing_items = impl_item_list.assoc_items().count();
149 let source_scope = sema.scope_for_def(trait_); 149 let source_scope = sema.scope_for_def(trait_);
150 let target_scope = sema.scope(impl_item_list.syntax()); 150 let target_scope = sema.scope(impl_item_list.syntax());
151 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 151 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
152 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_node)); 152 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def));
153 let items = missing_items 153 let items = missing_items
154 .into_iter() 154 .into_iter()
155 .map(|it| ast_transform::apply(&*ast_transform, it)) 155 .map(|it| ast_transform::apply(&*ast_transform, it))
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs
index 1b5d604d1..1c3f8435a 100644
--- a/crates/ra_assists/src/handlers/add_new.rs
+++ b/crates/ra_assists/src/handlers/add_new.rs
@@ -41,9 +41,8 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> {
41 // Return early if we've found an existing new fn 41 // Return early if we've found an existing new fn
42 let impl_def = find_struct_impl(&ctx, &strukt)?; 42 let impl_def = find_struct_impl(&ctx, &strukt)?;
43 43
44 ctx.add_assist(AssistId("add_new"), "Add default constructor", |edit| { 44 let target = strukt.syntax().text_range();
45 edit.target(strukt.syntax().text_range()); 45 ctx.add_assist(AssistId("add_new"), "Add default constructor", target, |edit| {
46
47 let mut buf = String::with_capacity(512); 46 let mut buf = String::with_capacity(512);
48 47
49 if impl_def.is_some() { 48 if impl_def.is_some() {
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
index a0c48d872..a5b26e5b9 100644
--- a/crates/ra_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ra_assists/src/handlers/apply_demorgan.rs
@@ -39,8 +39,7 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> {
39 let rhs_range = rhs.syntax().text_range(); 39 let rhs_range = rhs.syntax().text_range();
40 let not_rhs = invert_boolean_expression(rhs); 40 let not_rhs = invert_boolean_expression(rhs);
41 41
42 ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| { 42 ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| {
43 edit.target(op_range);
44 edit.replace(op_range, opposite_op); 43 edit.replace(op_range, opposite_op);
45 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); 44 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
46 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); 45 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index 9e4171ccd..2224b9714 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -48,8 +48,7 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
48 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; 48 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
49 let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message()); 49 let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message());
50 for import in proposed_imports { 50 for import in proposed_imports {
51 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { 51 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), range, |edit| {
52 edit.target(range);
53 insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit); 52 insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit);
54 }); 53 });
55 } 54 }
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index 6ac1f8e69..489db83e6 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -66,11 +66,15 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> {
66 return None; 66 return None;
67 }; 67 };
68 68
69 ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| { 69 ctx.add_assist(
70 edit.target(target); 70 AssistId("change_visibility"),
71 edit.insert(offset, "pub(crate) "); 71 "Change visibility to pub(crate)",
72 edit.set_cursor(offset); 72 target,
73 }) 73 |edit| {
74 edit.insert(offset, "pub(crate) ");
75 edit.set_cursor(offset);
76 },
77 )
74} 78}
75 79
76fn vis_offset(node: &SyntaxNode) -> TextSize { 80fn vis_offset(node: &SyntaxNode) -> TextSize {
@@ -86,22 +90,28 @@ fn vis_offset(node: &SyntaxNode) -> TextSize {
86 90
87fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> { 91fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
88 if vis.syntax().text() == "pub" { 92 if vis.syntax().text() == "pub" {
93 let target = vis.syntax().text_range();
89 return ctx.add_assist( 94 return ctx.add_assist(
90 AssistId("change_visibility"), 95 AssistId("change_visibility"),
91 "Change Visibility to pub(crate)", 96 "Change Visibility to pub(crate)",
97 target,
92 |edit| { 98 |edit| {
93 edit.target(vis.syntax().text_range());
94 edit.replace(vis.syntax().text_range(), "pub(crate)"); 99 edit.replace(vis.syntax().text_range(), "pub(crate)");
95 edit.set_cursor(vis.syntax().text_range().start()) 100 edit.set_cursor(vis.syntax().text_range().start())
96 }, 101 },
97 ); 102 );
98 } 103 }
99 if vis.syntax().text() == "pub(crate)" { 104 if vis.syntax().text() == "pub(crate)" {
100 return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| { 105 let target = vis.syntax().text_range();
101 edit.target(vis.syntax().text_range()); 106 return ctx.add_assist(
102 edit.replace(vis.syntax().text_range(), "pub"); 107 AssistId("change_visibility"),
103 edit.set_cursor(vis.syntax().text_range().start()); 108 "Change visibility to pub",
104 }); 109 target,
110 |edit| {
111 edit.replace(vis.syntax().text_range(), "pub");
112 edit.set_cursor(vis.syntax().text_range().start());
113 },
114 );
105 } 115 }
106 None 116 None
107} 117}
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index 55ccc37b0..4bd6040b2 100644
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -95,89 +95,94 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
95 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; 95 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
96 let cursor_position = ctx.frange.range.start(); 96 let cursor_position = ctx.frange.range.start();
97 97
98 ctx.add_assist(AssistId("convert_to_guarded_return"), "Convert to guarded return", |edit| { 98 let target = if_expr.syntax().text_range();
99 let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); 99 ctx.add_assist(
100 let new_block = match if_let_pat { 100 AssistId("convert_to_guarded_return"),
101 None => { 101 "Convert to guarded return",
102 // If. 102 target,
103 let new_expr = { 103 |edit| {
104 let then_branch = 104 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
105 make::block_expr(once(make::expr_stmt(early_expression).into()), None); 105 let new_block = match if_let_pat {
106 let cond = invert_boolean_expression(cond_expr); 106 None => {
107 let e = make::expr_if(make::condition(cond, None), then_branch); 107 // If.
108 if_indent_level.increase_indent(e) 108 let new_expr = {
109 }; 109 let then_branch =
110 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) 110 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
111 } 111 let cond = invert_boolean_expression(cond_expr);
112 Some((path, bound_ident)) => { 112 let e = make::expr_if(make::condition(cond, None), then_branch);
113 // If-let. 113 if_indent_level.increase_indent(e)
114 let match_expr = {
115 let happy_arm = {
116 let pat = make::tuple_struct_pat(
117 path,
118 once(make::bind_pat(make::name("it")).into()),
119 );
120 let expr = {
121 let name_ref = make::name_ref("it");
122 let segment = make::path_segment(name_ref);
123 let path = make::path_unqualified(segment);
124 make::expr_path(path)
125 };
126 make::match_arm(once(pat.into()), expr)
127 }; 114 };
115 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
116 }
117 Some((path, bound_ident)) => {
118 // If-let.
119 let match_expr = {
120 let happy_arm = {
121 let pat = make::tuple_struct_pat(
122 path,
123 once(make::bind_pat(make::name("it")).into()),
124 );
125 let expr = {
126 let name_ref = make::name_ref("it");
127 let segment = make::path_segment(name_ref);
128 let path = make::path_unqualified(segment);
129 make::expr_path(path)
130 };
131 make::match_arm(once(pat.into()), expr)
132 };
128 133
129 let sad_arm = make::match_arm( 134 let sad_arm = make::match_arm(
130 // FIXME: would be cool to use `None` or `Err(_)` if appropriate 135 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
131 once(make::placeholder_pat().into()), 136 once(make::placeholder_pat().into()),
132 early_expression, 137 early_expression,
133 ); 138 );
134 139
135 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) 140 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
136 }; 141 };
137 142
138 let let_stmt = make::let_stmt( 143 let let_stmt = make::let_stmt(
139 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), 144 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
140 Some(match_expr), 145 Some(match_expr),
146 );
147 let let_stmt = if_indent_level.increase_indent(let_stmt);
148 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
149 }
150 };
151 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
152 edit.set_cursor(cursor_position);
153
154 fn replace(
155 new_expr: &SyntaxNode,
156 then_block: &ast::BlockExpr,
157 parent_block: &ast::BlockExpr,
158 if_expr: &ast::IfExpr,
159 ) -> SyntaxNode {
160 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
161 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
162 let end_of_then =
163 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
164 end_of_then.prev_sibling_or_token().unwrap()
165 } else {
166 end_of_then
167 };
168 let mut then_statements = new_expr.children_with_tokens().chain(
169 then_block_items
170 .syntax()
171 .children_with_tokens()
172 .skip(1)
173 .take_while(|i| *i != end_of_then),
141 ); 174 );
142 let let_stmt = if_indent_level.increase_indent(let_stmt); 175 replace_children(
143 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) 176 &parent_block.syntax(),
177 RangeInclusive::new(
178 if_expr.clone().syntax().clone().into(),
179 if_expr.syntax().clone().into(),
180 ),
181 &mut then_statements,
182 )
144 } 183 }
145 }; 184 },
146 edit.target(if_expr.syntax().text_range()); 185 )
147 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
148 edit.set_cursor(cursor_position);
149
150 fn replace(
151 new_expr: &SyntaxNode,
152 then_block: &ast::BlockExpr,
153 parent_block: &ast::BlockExpr,
154 if_expr: &ast::IfExpr,
155 ) -> SyntaxNode {
156 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
157 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
158 let end_of_then =
159 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
160 end_of_then.prev_sibling_or_token().unwrap()
161 } else {
162 end_of_then
163 };
164 let mut then_statements = new_expr.children_with_tokens().chain(
165 then_block_items
166 .syntax()
167 .children_with_tokens()
168 .skip(1)
169 .take_while(|i| *i != end_of_then),
170 );
171 replace_children(
172 &parent_block.syntax(),
173 RangeInclusive::new(
174 if_expr.clone().syntax().clone().into(),
175 if_expr.syntax().clone().into(),
176 ),
177 &mut then_statements,
178 )
179 }
180 })
181} 186}
182 187
183#[cfg(test)] 188#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index 1f9cd5585..7c8f8bdf2 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -92,10 +92,9 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
92 return None; 92 return None;
93 } 93 }
94 94
95 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { 95 let target = match_expr.syntax().text_range();
96 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", target, |edit| {
96 let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); 97 let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms);
97
98 edit.target(match_expr.syntax().text_range());
99 edit.set_cursor(expr.syntax().text_range().start()); 98 edit.set_cursor(expr.syntax().text_range().start());
100 edit.replace_ast(match_arm_list, new_arm_list); 99 edit.replace_ast(match_arm_list, new_arm_list);
101 }) 100 })
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs
index 41db963dc..cb7264d7b 100644
--- a/crates/ra_assists/src/handlers/flip_binexpr.rs
+++ b/crates/ra_assists/src/handlers/flip_binexpr.rs
@@ -33,8 +33,7 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> {
33 return None; 33 return None;
34 } 34 }
35 35
36 ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", |edit| { 36 ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| {
37 edit.target(op_range);
38 if let FlipAction::FlipAndReplaceOp(new_op) = action { 37 if let FlipAction::FlipAndReplaceOp(new_op) = action {
39 edit.replace(op_range, new_op); 38 edit.replace(op_range, new_op);
40 } 39 }
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs
index e65c9a41d..24982ae22 100644
--- a/crates/ra_assists/src/handlers/flip_comma.rs
+++ b/crates/ra_assists/src/handlers/flip_comma.rs
@@ -28,8 +28,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
28 return None; 28 return None;
29 } 29 }
30 30
31 ctx.add_assist(AssistId("flip_comma"), "Flip comma", |edit| { 31 ctx.add_assist(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| {
32 edit.target(comma.text_range());
33 edit.replace(prev.text_range(), next.to_string()); 32 edit.replace(prev.text_range(), next.to_string());
34 edit.replace(next.text_range(), prev.to_string()); 33 edit.replace(next.text_range(), prev.to_string());
35 }) 34 })
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs
index f186da585..6a3b2df67 100644
--- a/crates/ra_assists/src/handlers/flip_trait_bound.rs
+++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs
@@ -32,8 +32,8 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
32 non_trivia_sibling(plus.clone().into(), Direction::Next)?, 32 non_trivia_sibling(plus.clone().into(), Direction::Next)?,
33 ); 33 );
34 34
35 ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", |edit| { 35 let target = plus.text_range();
36 edit.target(plus.text_range()); 36 ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| {
37 edit.replace(before.text_range(), after.to_string()); 37 edit.replace(before.text_range(), after.to_string());
38 edit.replace(after.text_range(), before.to_string()); 38 edit.replace(after.text_range(), before.to_string());
39 }) 39 })
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs
index 5f3b8dfd1..e5765c845 100644
--- a/crates/ra_assists/src/handlers/inline_local_variable.rs
+++ b/crates/ra_assists/src/handlers/inline_local_variable.rs
@@ -106,9 +106,11 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
106 let init_str = initializer_expr.syntax().text().to_string(); 106 let init_str = initializer_expr.syntax().text().to_string();
107 let init_in_paren = format!("({})", &init_str); 107 let init_in_paren = format!("({})", &init_str);
108 108
109 let target = bind_pat.syntax().text_range();
109 ctx.add_assist( 110 ctx.add_assist(
110 AssistId("inline_local_variable"), 111 AssistId("inline_local_variable"),
111 "Inline variable", 112 "Inline variable",
113 target,
112 move |edit: &mut ActionBuilder| { 114 move |edit: &mut ActionBuilder| {
113 edit.delete(delete_range); 115 edit.delete(delete_range);
114 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { 116 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs
index 9c2c20b22..3c340ff3b 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/introduce_variable.rs
@@ -42,7 +42,8 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
42 if indent.kind() != WHITESPACE { 42 if indent.kind() != WHITESPACE {
43 return None; 43 return None;
44 } 44 }
45 ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", move |edit| { 45 let target = expr.syntax().text_range();
46 ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", target, move |edit| {
46 let mut buf = String::new(); 47 let mut buf = String::new();
47 48
48 let cursor_offset = if wrap_in_block { 49 let cursor_offset = if wrap_in_block {
@@ -79,7 +80,6 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
79 buf.push_str(text); 80 buf.push_str(text);
80 } 81 }
81 82
82 edit.target(expr.syntax().text_range());
83 edit.replace(expr.syntax().text_range(), "var_name".to_string()); 83 edit.replace(expr.syntax().text_range(), "var_name".to_string());
84 edit.insert(anchor_stmt.text_range().start(), buf); 84 edit.insert(anchor_stmt.text_range().start(), buf);
85 if wrap_in_block { 85 if wrap_in_block {
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs
index 303c1806d..b16271443 100644
--- a/crates/ra_assists/src/handlers/invert_if.rs
+++ b/crates/ra_assists/src/handlers/invert_if.rs
@@ -47,8 +47,7 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
47 let else_node = else_block.syntax(); 47 let else_node = else_block.syntax();
48 let else_range = else_node.text_range(); 48 let else_range = else_node.text_range();
49 let then_range = then_node.text_range(); 49 let then_range = then_node.text_range();
50 return ctx.add_assist(AssistId("invert_if"), "Invert if", |edit| { 50 return ctx.add_assist(AssistId("invert_if"), "Invert if", if_range, |edit| {
51 edit.target(if_range);
52 edit.replace(cond_range, flip_cond.syntax().text()); 51 edit.replace(cond_range, flip_cond.syntax().text());
53 edit.replace(else_range, then_node.text()); 52 edit.replace(else_range, then_node.text());
54 edit.replace(then_range, else_node.text()); 53 edit.replace(then_range, else_node.text());
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs
index 9a2083609..de74d83d8 100644
--- a/crates/ra_assists/src/handlers/merge_imports.rs
+++ b/crates/ra_assists/src/handlers/merge_imports.rs
@@ -52,7 +52,8 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
52 } 52 }
53 }; 53 };
54 54
55 ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| { 55 let target = tree.syntax().text_range();
56 ctx.add_assist(AssistId("merge_imports"), "Merge imports", target, |edit| {
56 edit.rewrite(rewriter); 57 edit.rewrite(rewriter);
57 // FIXME: we only need because our diff is imprecise 58 // FIXME: we only need because our diff is imprecise
58 edit.set_cursor(offset); 59 edit.set_cursor(offset);
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
index 9ae099b41..7c4d9d55d 100644
--- a/crates/ra_assists/src/handlers/merge_match_arms.rs
+++ b/crates/ra_assists/src/handlers/merge_match_arms.rs
@@ -70,7 +70,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
70 return None; 70 return None;
71 } 71 }
72 72
73 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { 73 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| {
74 let pats = if arms_to_merge.iter().any(contains_placeholder) { 74 let pats = if arms_to_merge.iter().any(contains_placeholder) {
75 "_".into() 75 "_".into()
76 } else { 76 } else {
@@ -87,7 +87,6 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
87 let start = arms_to_merge.first().unwrap().syntax().text_range().start(); 87 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
88 let end = arms_to_merge.last().unwrap().syntax().text_range().end(); 88 let end = arms_to_merge.last().unwrap().syntax().text_range().end();
89 89
90 edit.target(current_text_range);
91 edit.set_cursor(match cursor_pos { 90 edit.set_cursor(match cursor_pos {
92 CursorPos::InExpr(back_offset) => start + TextSize::of(&arm) - back_offset, 91 CursorPos::InExpr(back_offset) => start + TextSize::of(&arm) - back_offset,
93 CursorPos::InPat(offset) => offset, 92 CursorPos::InPat(offset) => offset,
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs
index 89956aea9..44e50cb6e 100644
--- a/crates/ra_assists/src/handlers/move_bounds.rs
+++ b/crates/ra_assists/src/handlers/move_bounds.rs
@@ -49,30 +49,37 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
49 } 49 }
50 }; 50 };
51 51
52 ctx.add_assist(AssistId("move_bounds_to_where_clause"), "Move to where clause", |edit| { 52 let target = type_param_list.syntax().text_range();
53 let new_params = type_param_list 53 ctx.add_assist(
54 .type_params() 54 AssistId("move_bounds_to_where_clause"),
55 .filter(|it| it.type_bound_list().is_some()) 55 "Move to where clause",
56 .map(|type_param| { 56 target,
57 let without_bounds = type_param.remove_bounds(); 57 |edit| {
58 (type_param, without_bounds) 58 let new_params = type_param_list
59 }); 59 .type_params()
60 60 .filter(|it| it.type_bound_list().is_some())
61 let new_type_param_list = type_param_list.replace_descendants(new_params); 61 .map(|type_param| {
62 edit.replace_ast(type_param_list.clone(), new_type_param_list); 62 let without_bounds = type_param.remove_bounds();
63 63 (type_param, without_bounds)
64 let where_clause = { 64 });
65 let predicates = type_param_list.type_params().filter_map(build_predicate); 65
66 make::where_clause(predicates) 66 let new_type_param_list = type_param_list.replace_descendants(new_params);
67 }; 67 edit.replace_ast(type_param_list.clone(), new_type_param_list);
68 68
69 let to_insert = match anchor.prev_sibling_or_token() { 69 let where_clause = {
70 Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), 70 let predicates = type_param_list.type_params().filter_map(build_predicate);
71 _ => format!(" {}", where_clause.syntax()), 71 make::where_clause(predicates)
72 }; 72 };
73 edit.insert(anchor.text_range().start(), to_insert); 73
74 edit.target(type_param_list.syntax().text_range()); 74 let to_insert = match anchor.prev_sibling_or_token() {
75 }) 75 Some(ref elem) if elem.kind() == WHITESPACE => {
76 format!("{} ", where_clause.syntax())
77 }
78 _ => format!(" {}", where_clause.syntax()),
79 };
80 edit.insert(anchor.text_range().start(), to_insert);
81 },
82 )
76} 83}
77 84
78fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { 85fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs
index f2aa7e594..29bc9a9ff 100644
--- a/crates/ra_assists/src/handlers/move_guard.rs
+++ b/crates/ra_assists/src/handlers/move_guard.rs
@@ -40,8 +40,8 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> {
40 let arm_expr = match_arm.expr()?; 40 let arm_expr = match_arm.expr()?;
41 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); 41 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
42 42
43 ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", |edit| { 43 let target = guard.syntax().text_range();
44 edit.target(guard.syntax().text_range()); 44 ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| {
45 let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { 45 let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) {
46 Some(tok) => { 46 Some(tok) => {
47 if ast::Whitespace::cast(tok.clone()).is_some() { 47 if ast::Whitespace::cast(tok.clone()).is_some() {
@@ -108,11 +108,12 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
108 108
109 let buf = format!(" if {}", cond.syntax().text()); 109 let buf = format!(" if {}", cond.syntax().text());
110 110
111 let target = if_expr.syntax().text_range();
111 ctx.add_assist( 112 ctx.add_assist(
112 AssistId("move_arm_cond_to_match_guard"), 113 AssistId("move_arm_cond_to_match_guard"),
113 "Move condition to match guard", 114 "Move condition to match guard",
115 target,
114 |edit| { 116 |edit| {
115 edit.target(if_expr.syntax().text_range());
116 let then_only_expr = then_block.statements().next().is_none(); 117 let then_only_expr = then_block.statements().next().is_none();
117 118
118 match &then_block.expr() { 119 match &then_block.expr() {
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs
index 542f7a637..155c679b4 100644
--- a/crates/ra_assists/src/handlers/raw_string.rs
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -25,8 +25,8 @@ use crate::{Assist, AssistCtx, AssistId};
25pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; 26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
27 let value = token.value()?; 27 let value = token.value()?;
28 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| { 28 let target = token.syntax().text_range();
29 edit.target(token.syntax().text_range()); 29 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| {
30 let max_hash_streak = count_hashes(&value); 30 let max_hash_streak = count_hashes(&value);
31 let mut hashes = String::with_capacity(max_hash_streak + 1); 31 let mut hashes = String::with_capacity(max_hash_streak + 1);
32 for _ in 0..hashes.capacity() { 32 for _ in 0..hashes.capacity() {
@@ -54,8 +54,8 @@ pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
54pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> { 54pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; 55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
56 let value = token.value()?; 56 let value = token.value()?;
57 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| { 57 let target = token.syntax().text_range();
58 edit.target(token.syntax().text_range()); 58 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| {
59 // parse inside string to escape `"` 59 // parse inside string to escape `"`
60 let escaped = value.escape_default().to_string(); 60 let escaped = value.escape_default().to_string();
61 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); 61 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
@@ -79,8 +79,8 @@ pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
79// ``` 79// ```
80pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> { 80pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
81 let token = ctx.find_token_at_offset(RAW_STRING)?; 81 let token = ctx.find_token_at_offset(RAW_STRING)?;
82 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| { 82 let target = token.text_range();
83 edit.target(token.text_range()); 83 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", target, |edit| {
84 edit.insert(token.text_range().start() + TextSize::of('r'), "#"); 84 edit.insert(token.text_range().start() + TextSize::of('r'), "#");
85 edit.insert(token.text_range().end(), "#"); 85 edit.insert(token.text_range().end(), "#");
86 }) 86 })
@@ -108,8 +108,8 @@ pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
108 // no hash to remove 108 // no hash to remove
109 return None; 109 return None;
110 } 110 }
111 ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", |edit| { 111 let target = token.text_range();
112 edit.target(token.text_range()); 112 ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| {
113 let result = &text[2..text.len() - 1]; 113 let result = &text[2..text.len() - 1];
114 let result = if result.starts_with('\"') { 114 let result = if result.starts_with('\"') {
115 // FIXME: this logic is wrong, not only the last has has to handled specially 115 // FIXME: this logic is wrong, not only the last has has to handled specially
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs
index ddfb21a7e..e6e02f2ae 100644
--- a/crates/ra_assists/src/handlers/remove_dbg.rs
+++ b/crates/ra_assists/src/handlers/remove_dbg.rs
@@ -57,8 +57,8 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> {
57 text.slice(without_parens).to_string() 57 text.slice(without_parens).to_string()
58 }; 58 };
59 59
60 ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", |edit| { 60 let target = macro_call.syntax().text_range();
61 edit.target(macro_call.syntax().text_range()); 61 ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| {
62 edit.replace(macro_range, macro_content); 62 edit.replace(macro_range, macro_content);
63 edit.set_cursor(cursor_pos); 63 edit.set_cursor(cursor_pos);
64 }) 64 })
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs
index e598023b2..9f72f879d 100644
--- a/crates/ra_assists/src/handlers/remove_mut.rs
+++ b/crates/ra_assists/src/handlers/remove_mut.rs
@@ -25,7 +25,8 @@ pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> {
25 _ => mut_token.text_range().end(), 25 _ => mut_token.text_range().end(),
26 }; 26 };
27 27
28 ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", |edit| { 28 let target = mut_token.text_range();
29 ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| {
29 edit.set_cursor(delete_from); 30 edit.set_cursor(delete_from);
30 edit.delete(TextRange::new(delete_from, delete_to)); 31 edit.delete(TextRange::new(delete_from, delete_to));
31 }) 32 })
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs
index a57e327b8..0b930dea2 100644
--- a/crates/ra_assists/src/handlers/reorder_fields.rs
+++ b/crates/ra_assists/src/handlers/reorder_fields.rs
@@ -50,11 +50,11 @@ fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
50 return None; 50 return None;
51 } 51 }
52 52
53 ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| { 53 let target = record.syntax().text_range();
54 ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", target, |edit| {
54 for (old, new) in fields.iter().zip(&sorted_fields) { 55 for (old, new) in fields.iter().zip(&sorted_fields) {
55 algo::diff(old, new).into_text_edit(edit.text_edit_builder()); 56 algo::diff(old, new).into_text_edit(edit.text_edit_builder());
56 } 57 }
57 edit.target(record.syntax().text_range())
58 }) 58 })
59} 59}
60 60
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
index d0df3b84e..2eb8348f8 100644
--- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
@@ -44,30 +44,35 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
44 }; 44 };
45 45
46 let sema = ctx.sema; 46 let sema = ctx.sema;
47 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| { 47 let target = if_expr.syntax().text_range();
48 let match_expr = { 48 ctx.add_assist(
49 let then_arm = { 49 AssistId("replace_if_let_with_match"),
50 let then_expr = unwrap_trivial_block(then_block); 50 "Replace with match",
51 make::match_arm(vec![pat.clone()], then_expr) 51 target,
52 move |edit| {
53 let match_expr = {
54 let then_arm = {
55 let then_expr = unwrap_trivial_block(then_block);
56 make::match_arm(vec![pat.clone()], then_expr)
57 };
58 let else_arm = {
59 let pattern = sema
60 .type_of_pat(&pat)
61 .and_then(|ty| TryEnum::from_ty(sema, &ty))
62 .map(|it| it.sad_pattern())
63 .unwrap_or_else(|| make::placeholder_pat().into());
64 let else_expr = unwrap_trivial_block(else_block);
65 make::match_arm(vec![pattern], else_expr)
66 };
67 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
52 }; 68 };
53 let else_arm = {
54 let pattern = sema
55 .type_of_pat(&pat)
56 .and_then(|ty| TryEnum::from_ty(sema, &ty))
57 .map(|it| it.sad_pattern())
58 .unwrap_or_else(|| make::placeholder_pat().into());
59 let else_expr = unwrap_trivial_block(else_block);
60 make::match_arm(vec![pattern], else_expr)
61 };
62 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
63 };
64 69
65 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr); 70 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
66 71
67 edit.target(if_expr.syntax().text_range()); 72 edit.set_cursor(if_expr.syntax().text_range().start());
68 edit.set_cursor(if_expr.syntax().text_range().start()); 73 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
69 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); 74 },
70 }) 75 )
71} 76}
72 77
73#[cfg(test)] 78#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
index dc4d16055..a5509a567 100644
--- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
@@ -47,7 +47,8 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
47 let ty = ctx.sema.type_of_expr(&init)?; 47 let ty = ctx.sema.type_of_expr(&init)?;
48 let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case()); 48 let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case());
49 49
50 ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| { 50 let target = let_kw.text_range();
51 ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| {
51 let with_placeholder: ast::Pat = match happy_variant { 52 let with_placeholder: ast::Pat = match happy_variant {
52 None => make::placeholder_pat().into(), 53 None => make::placeholder_pat().into(),
53 Some(var_name) => make::tuple_struct_pat( 54 Some(var_name) => make::tuple_struct_pat(
@@ -67,7 +68,6 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
67 let stmt = stmt.replace_descendant(placeholder.into(), original_pat); 68 let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
68 69
69 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); 70 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
70 edit.target(let_kw.text_range());
71 edit.set_cursor(target_offset); 71 edit.set_cursor(target_offset);
72 }) 72 })
73} 73}
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index 624178924..fd41da64b 100644
--- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -33,9 +33,11 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist>
33 return None; 33 return None;
34 } 34 }
35 35
36 let target = path.syntax().text_range();
36 ctx.add_assist( 37 ctx.add_assist(
37 AssistId("replace_qualified_name_with_use"), 38 AssistId("replace_qualified_name_with_use"),
38 "Replace qualified path with use", 39 "Replace qualified path with use",
40 target,
39 |edit| { 41 |edit| {
40 let path_to_import = hir_path.mod_path().clone(); 42 let path_to_import = hir_path.mod_path().clone();
41 insert_use_statement(path.syntax(), &path_to_import, edit); 43 insert_use_statement(path.syntax(), &path_to_import, edit);
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
index dcb471edb..c6b73da67 100644
--- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
@@ -38,26 +38,32 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
38 let caller = method_call.expr()?; 38 let caller = method_call.expr()?;
39 let ty = ctx.sema.type_of_expr(&caller)?; 39 let ty = ctx.sema.type_of_expr(&caller)?;
40 let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case(); 40 let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case();
41 let target = method_call.syntax().text_range();
42 ctx.add_assist(
43 AssistId("replace_unwrap_with_match"),
44 "Replace unwrap with match",
45 target,
46 |edit| {
47 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
48 let it = make::bind_pat(make::name("a")).into();
49 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
41 50
42 ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| { 51 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
43 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); 52 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
44 let it = make::bind_pat(make::name("a")).into();
45 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
46 53
47 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); 54 let unreachable_call = make::unreachable_macro_call().into();
48 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); 55 let err_arm =
56 make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
49 57
50 let unreachable_call = make::unreachable_macro_call().into(); 58 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
51 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); 59 let match_expr = make::expr_match(caller.clone(), match_arm_list);
60 let match_expr =
61 IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
52 62
53 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); 63 edit.set_cursor(caller.syntax().text_range().start());
54 let match_expr = make::expr_match(caller.clone(), match_arm_list); 64 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
55 let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); 65 },
56 66 )
57 edit.target(method_call.syntax().text_range());
58 edit.set_cursor(caller.syntax().text_range().start());
59 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
60 })
61} 67}
62 68
63#[cfg(test)] 69#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
index a59f2f76f..d49563974 100644
--- a/crates/ra_assists/src/handlers/split_import.rs
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -28,8 +28,8 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
28 } 28 }
29 let cursor = ctx.frange.range.start(); 29 let cursor = ctx.frange.range.start();
30 30
31 ctx.add_assist(AssistId("split_import"), "Split import", |edit| { 31 let target = colon_colon.text_range();
32 edit.target(colon_colon.text_range()); 32 ctx.add_assist(AssistId("split_import"), "Split import", target, |edit| {
33 edit.replace_ast(use_tree, new_tree); 33 edit.replace_ast(use_tree, new_tree);
34 edit.set_cursor(cursor); 34 edit.set_cursor(cursor);
35 }) 35 })
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs
index 89992117d..6df927abb 100644
--- a/crates/ra_assists/src/handlers/unwrap_block.rs
+++ b/crates/ra_assists/src/handlers/unwrap_block.rs
@@ -57,9 +57,9 @@ pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> {
57 } 57 }
58 }; 58 };
59 59
60 ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", |edit| { 60 let target = expr_to_unwrap.syntax().text_range();
61 ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", target, |edit| {
61 edit.set_cursor(expr.syntax().text_range().start()); 62 edit.set_cursor(expr.syntax().text_range().start());
62 edit.target(expr_to_unwrap.syntax().text_range());
63 63
64 let pat_start: &[_] = &[' ', '{', '\n']; 64 let pat_start: &[_] = &[' ', '{', '\n'];
65 let expr_to_unwrap = expr_to_unwrap.to_string(); 65 let expr_to_unwrap = expr_to_unwrap.to_string();
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index b794b021d..f4f37614f 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -36,16 +36,24 @@ pub struct AssistLabel {
36 /// Short description of the assist, as shown in the UI. 36 /// Short description of the assist, as shown in the UI.
37 pub label: String, 37 pub label: String,
38 pub group: Option<GroupLabel>, 38 pub group: Option<GroupLabel>,
39 /// Target ranges are used to sort assists: the smaller the target range,
40 /// the more specific assist is, and so it should be sorted first.
41 pub target: TextRange,
39} 42}
40 43
41#[derive(Clone, Debug)] 44#[derive(Clone, Debug)]
42pub struct GroupLabel(pub String); 45pub struct GroupLabel(pub String);
43 46
44impl AssistLabel { 47impl AssistLabel {
45 pub(crate) fn new(id: AssistId, label: String, group: Option<GroupLabel>) -> AssistLabel { 48 pub(crate) fn new(
49 id: AssistId,
50 label: String,
51 group: Option<GroupLabel>,
52 target: TextRange,
53 ) -> AssistLabel {
46 // FIXME: make fields private, so that this invariant can't be broken 54 // FIXME: make fields private, so that this invariant can't be broken
47 assert!(label.starts_with(|c: char| c.is_uppercase())); 55 assert!(label.starts_with(|c: char| c.is_uppercase()));
48 AssistLabel { id, label, group } 56 AssistLabel { id, label, group, target }
49 } 57 }
50} 58}
51 59
@@ -53,8 +61,6 @@ impl AssistLabel {
53pub struct AssistAction { 61pub struct AssistAction {
54 pub edit: TextEdit, 62 pub edit: TextEdit,
55 pub cursor_position: Option<TextSize>, 63 pub cursor_position: Option<TextSize>,
56 // FIXME: This belongs to `AssistLabel`
57 pub target: Option<TextRange>,
58 pub file: AssistFile, 64 pub file: AssistFile,
59} 65}
60 66
@@ -104,7 +110,7 @@ pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssi
104 .flat_map(|it| it.0) 110 .flat_map(|it| it.0)
105 .map(|it| it.into_resolved().unwrap()) 111 .map(|it| it.into_resolved().unwrap())
106 .collect::<Vec<_>>(); 112 .collect::<Vec<_>>();
107 a.sort_by_key(|it| it.action.target.map_or(TextSize::from(!0u32), |it| it.len())); 113 a.sort_by_key(|it| it.label.target.len());
108 a 114 a
109} 115}
110 116
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs
index 483e11931..dd9026df6 100644
--- a/crates/ra_assists/src/tests.rs
+++ b/crates/ra_assists/src/tests.rs
@@ -118,8 +118,7 @@ fn check(assist: Handler, before: &str, expected: ExpectedResult) {
118 assert_eq_text!(after, &actual); 118 assert_eq_text!(after, &actual);
119 } 119 }
120 (Some(assist), ExpectedResult::Target(target)) => { 120 (Some(assist), ExpectedResult::Target(target)) => {
121 let action = assist.0[0].action.clone().unwrap(); 121 let range = assist.0[0].label.target;
122 let range = action.target.expect("expected target on action");
123 assert_eq_text!(&text_without_caret[range], target); 122 assert_eq_text!(&text_without_caret[range], target);
124 } 123 }
125 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), 124 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),