Repository: rust-analyzer/rust-analyzer
Pull request #4341 (2020-05-06 10:52:42)

Move target to AssistLabel
Target is used for assists sorting, so we need it before we compute
the action.



bors r+
🤖


Patch:
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 83dd270c6c..600e5689cc 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> {
         self,
         id: AssistId,
         label: impl Into<String>,
+        target: TextRange,
         f: impl FnOnce(&mut ActionBuilder),
     ) -> Option<Assist> {
-        let label = AssistLabel::new(id, label.into(), None);
+        let label = AssistLabel::new(id, label.into(), None, target);
 
         let mut info = AssistInfo::new(label);
         if self.should_compute_edit {
@@ -152,9 +153,10 @@ impl<'a> AssistGroup<'a> {
         &mut self,
         id: AssistId,
         label: impl Into<String>,
+        target: TextRange,
         f: impl FnOnce(&mut ActionBuilder),
     ) {
-        let label = AssistLabel::new(id, label.into(), Some(self.group.clone()));
+        let label = AssistLabel::new(id, label.into(), Some(self.group.clone()), target);
 
         let mut info = AssistInfo::new(label).with_group(self.group.clone());
         if self.ctx.should_compute_edit {
@@ -181,7 +183,6 @@ impl<'a> AssistGroup<'a> {
 pub(crate) struct ActionBuilder<'a, 'b> {
     edit: TextEditBuilder,
     cursor_position: Option<TextSize>,
-    target: Option<TextRange>,
     file: AssistFile,
     ctx: &'a AssistCtx<'b>,
 }
@@ -191,7 +192,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
         Self {
             edit: TextEditBuilder::default(),
             cursor_position: None,
-            target: None,
             file: AssistFile::default(),
             ctx,
         }
@@ -237,14 +237,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
         self.cursor_position = Some(offset)
     }
 
-    /// Specify that the assist should be active withing the `target` range.
-    ///
-    /// Target ranges are used to sort assists: the smaller the target range,
-    /// the more specific assist is, and so it should be sorted first.
-    pub(crate) fn target(&mut self, target: TextRange) {
-        self.target = Some(target)
-    }
-
     /// Get access to the raw `TextEditBuilder`.
     pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
         &mut self.edit
@@ -267,7 +259,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
         AssistAction {
             edit: self.edit.finish(),
             cursor_position: self.cursor_position,
-            target: self.target,
             file: self.file,
         }
     }
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index b72f7aeac8..869d4dc045 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> {
     let label =
         format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name);
 
-    ctx.add_assist(AssistId("add_custom_impl"), label, |edit| {
-        edit.target(attr.syntax().text_range());
-
+    let target = attr.syntax().text_range();
+    ctx.add_assist(AssistId("add_custom_impl"), label, target, |edit| {
         let new_attr_input = input
             .syntax()
             .descendants_with_tokens()
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs
index 3629dac6bf..2a6bb1caed 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};
 pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
     let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
     let node_start = derive_insertion_offset(&nominal)?;
-    ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", |edit| {
+    let target = nominal.syntax().text_range();
+    ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", target, |edit| {
         let derive_attr = nominal
             .attrs()
             .filter_map(|x| x.as_simple_call())
@@ -41,7 +42,6 @@ pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
             }
             Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'),
         };
-        edit.target(nominal.syntax().text_range());
         edit.set_cursor(offset)
     })
 }
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
index e39e1f4f3c..a59ec16b2d 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> {
     ctx.add_assist(
         AssistId("add_explicit_type"),
         format!("Insert explicit type '{}'", new_type_string),
+        pat_range,
         |edit| {
-            edit.target(pat_range);
             if let Some(ascribed_ty) = ascribed_ty {
                 edit.replace(ascribed_ty.syntax().text_range(), new_type_string);
             } 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 ee0d5ce986..81deb3dfa3 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> {
         return None;
     }
 
+    let target = variant.syntax().text_range();
     ctx.add_assist(
         AssistId("add_from_impl_for_enum"),
         "Add From impl for this enum variant",
+        target,
         |edit| {
             let start_offset = variant.parent_enum().syntax().text_range().end();
             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 cb2afc863d..76c0f9783b 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> {
 
     let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
 
-    ctx.add_assist(AssistId("add_function"), "Add function", |edit| {
-        edit.target(call.syntax().text_range());
-
+    let target = call.syntax().text_range();
+    // TODO: assert here?
+    ctx.add_assist(AssistId("add_function"), "Add function", target, |edit| {
         if let Some(function_template) = function_builder.render() {
             edit.set_file(function_template.file);
             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 3d390c20bc..557344ebb8 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};
 pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
     let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
     let name = nominal.name()?;
-    ctx.add_assist(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), |edit| {
-        edit.target(nominal.syntax().text_range());
-        let type_params = nominal.type_param_list();
-        let start_offset = nominal.syntax().text_range().end();
-        let mut buf = String::new();
-        buf.push_str("\n\nimpl");
-        if let Some(type_params) = &type_params {
-            format_to!(buf, "{}", type_params.syntax());
-        }
-        buf.push_str(" ");
-        buf.push_str(name.text().as_str());
-        if let Some(type_params) = type_params {
-            let lifetime_params = type_params
-                .lifetime_params()
-                .filter_map(|it| it.lifetime_token())
-                .map(|it| it.text().clone());
-            let type_params =
-                type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
+    let target = nominal.syntax().text_range();
+    ctx.add_assist(
+        AssistId("add_impl"),
+        format!("Implement {}", name.text().as_str()),
+        target,
+        |edit| {
+            let type_params = nominal.type_param_list();
+            let start_offset = nominal.syntax().text_range().end();
+            let mut buf = String::new();
+            buf.push_str("\n\nimpl");
+            if let Some(type_params) = &type_params {
+                format_to!(buf, "{}", type_params.syntax());
+            }
+            buf.push_str(" ");
+            buf.push_str(name.text().as_str());
+            if let Some(type_params) = type_params {
+                let lifetime_params = type_params
+                    .lifetime_params()
+                    .filter_map(|it| it.lifetime_token())
+                    .map(|it| it.text().clone());
+                let type_params = type_params
+                    .type_params()
+                    .filter_map(|it| it.name())
+                    .map(|it| it.text().clone());
 
-            let generic_params = lifetime_params.chain(type_params).sep_by(", ");
-            format_to!(buf, "<{}>", generic_params)
-        }
-        buf.push_str(" {\n");
-        edit.set_cursor(start_offset + TextSize::of(&buf));
-        buf.push_str("\n}");
-        edit.insert(start_offset, buf);
-    })
+                let generic_params = lifetime_params.chain(type_params).sep_by(", ");
+                format_to!(buf, "<{}>", generic_params)
+            }
+            buf.push_str(" {\n");
+            edit.set_cursor(start_offset + TextSize::of(&buf));
+            buf.push_str("\n}");
+            edit.insert(start_offset, buf);
+        },
+    )
 }
 
 #[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 f7a1015033..7df7865909 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(
     label: &'static str,
 ) -> Option<Assist> {
     let _p = ra_prof::profile("add_missing_impl_members_inner");
-    let impl_node = ctx.find_node_at_offset::<ast::ImplDef>()?;
-    let impl_item_list = impl_node.item_list()?;
+    let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?;
+    let impl_item_list = impl_def.item_list()?;
 
-    let trait_ = resolve_target_trait(&ctx.sema, &impl_node)?;
+    let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
 
     let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
         match item {
@@ -121,7 +121,7 @@ fn add_missing_impl_members_inner(
         .map(|it| it.text().clone())
     };
 
-    let missing_items = get_missing_assoc_items(&ctx.sema, &impl_node)
+    let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
         .iter()
         .map(|i| match i {
             hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value),
@@ -143,13 +143,13 @@ fn add_missing_impl_members_inner(
     }
 
     let sema = ctx.sema;
-
-    ctx.add_assist(AssistId(assist_id), label, |edit| {
+    let target = impl_def.syntax().text_range();
+    ctx.add_assist(AssistId(assist_id), label, target, |edit| {
         let n_existing_items = impl_item_list.assoc_items().count();
         let source_scope = sema.scope_for_def(trait_);
         let target_scope = sema.scope(impl_item_list.syntax());
         let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
-            .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_node));
+            .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def));
         let items = missing_items
             .into_iter()
             .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 1b5d604d18..1c3f8435ab 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> {
     // Return early if we've found an existing new fn
     let impl_def = find_struct_impl(&ctx, &strukt)?;
 
-    ctx.add_assist(AssistId("add_new"), "Add default constructor", |edit| {
-        edit.target(strukt.syntax().text_range());
-
+    let target = strukt.syntax().text_range();
+    ctx.add_assist(AssistId("add_new"), "Add default constructor", target, |edit| {
         let mut buf = String::with_capacity(512);
 
         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 a0c48d8729..a5b26e5b93 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> {
     let rhs_range = rhs.syntax().text_range();
     let not_rhs = invert_boolean_expression(rhs);
 
-    ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| {
-        edit.target(op_range);
+    ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| {
         edit.replace(op_range, opposite_op);
         edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
         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 9e4171ccd8..2224b9714b 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> {
     let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
     let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message());
     for import in proposed_imports {
-        group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| {
-            edit.target(range);
+        group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), range, |edit| {
             insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit);
         });
     }
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index 6ac1f8e69e..489db83e6e 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> {
         return None;
     };
 
-    ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| {
-        edit.target(target);
-        edit.insert(offset, "pub(crate) ");
-        edit.set_cursor(offset);
-    })
+    ctx.add_assist(
+        AssistId("change_visibility"),
+        "Change visibility to pub(crate)",
+        target,
+        |edit| {
+            edit.insert(offset, "pub(crate) ");
+            edit.set_cursor(offset);
+        },
+    )
 }
 
 fn vis_offset(node: &SyntaxNode) -> TextSize {
@@ -86,22 +90,28 @@ fn vis_offset(node: &SyntaxNode) -> TextSize {
 
 fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
     if vis.syntax().text() == "pub" {
+        let target = vis.syntax().text_range();
         return ctx.add_assist(
             AssistId("change_visibility"),
             "Change Visibility to pub(crate)",
+            target,
             |edit| {
-                edit.target(vis.syntax().text_range());
                 edit.replace(vis.syntax().text_range(), "pub(crate)");
                 edit.set_cursor(vis.syntax().text_range().start())
             },
         );
     }
     if vis.syntax().text() == "pub(crate)" {
-        return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| {
-            edit.target(vis.syntax().text_range());
-            edit.replace(vis.syntax().text_range(), "pub");
-            edit.set_cursor(vis.syntax().text_range().start());
-        });
+        let target = vis.syntax().text_range();
+        return ctx.add_assist(
+            AssistId("change_visibility"),
+            "Change visibility to pub",
+            target,
+            |edit| {
+                edit.replace(vis.syntax().text_range(), "pub");
+                edit.set_cursor(vis.syntax().text_range().start());
+            },
+        );
     }
     None
 }
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index 55ccc37b00..4bd6040b2a 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> {
     then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
     let cursor_position = ctx.frange.range.start();
 
-    ctx.add_assist(AssistId("convert_to_guarded_return"), "Convert to guarded return", |edit| {
-        let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
-        let new_block = match if_let_pat {
-            None => {
-                // If.
-                let new_expr = {
-                    let then_branch =
-                        make::block_expr(once(make::expr_stmt(early_expression).into()), None);
-                    let cond = invert_boolean_expression(cond_expr);
-                    let e = make::expr_if(make::condition(cond, None), then_branch);
-                    if_indent_level.increase_indent(e)
-                };
-                replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
-            }
-            Some((path, bound_ident)) => {
-                // If-let.
-                let match_expr = {
-                    let happy_arm = {
-                        let pat = make::tuple_struct_pat(
-                            path,
-                            once(make::bind_pat(make::name("it")).into()),
-                        );
-                        let expr = {
-                            let name_ref = make::name_ref("it");
-                            let segment = make::path_segment(name_ref);
-                            let path = make::path_unqualified(segment);
-                            make::expr_path(path)
-                        };
-                        make::match_arm(once(pat.into()), expr)
+    let target = if_expr.syntax().text_range();
+    ctx.add_assist(
+        AssistId("convert_to_guarded_return"),
+        "Convert to guarded return",
+        target,
+        |edit| {
+            let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
+            let new_block = match if_let_pat {
+                None => {
+                    // If.
+                    let new_expr = {
+                        let then_branch =
+                            make::block_expr(once(make::expr_stmt(early_expression).into()), None);
+                        let cond = invert_boolean_expression(cond_expr);
+                        let e = make::expr_if(make::condition(cond, None), then_branch);
+                        if_indent_level.increase_indent(e)
                     };
+                    replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
+                }
+                Some((path, bound_ident)) => {
+                    // If-let.
+                    let match_expr = {
+                        let happy_arm = {
+                            let pat = make::tuple_struct_pat(
+                                path,
+                                once(make::bind_pat(make::name("it")).into()),
+                            );
+                            let expr = {
+                                let name_ref = make::name_ref("it");
+                                let segment = make::path_segment(name_ref);
+                                let path = make::path_unqualified(segment);
+                                make::expr_path(path)
+                            };
+                            make::match_arm(once(pat.into()), expr)
+                        };
 
-                    let sad_arm = make::match_arm(
-                        // FIXME: would be cool to use `None` or `Err(_)` if appropriate
-                        once(make::placeholder_pat().into()),
-                        early_expression,
-                    );
+                        let sad_arm = make::match_arm(
+                            // FIXME: would be cool to use `None` or `Err(_)` if appropriate
+                            once(make::placeholder_pat().into()),
+                            early_expression,
+                        );
 
-                    make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
-                };
+                        make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
+                    };
 
-                let let_stmt = make::let_stmt(
-                    make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
-                    Some(match_expr),
+                    let let_stmt = make::let_stmt(
+                        make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
+                        Some(match_expr),
+                    );
+                    let let_stmt = if_indent_level.increase_indent(let_stmt);
+                    replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
+                }
+            };
+            edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
+            edit.set_cursor(cursor_position);
+
+            fn replace(
+                new_expr: &SyntaxNode,
+                then_block: &ast::BlockExpr,
+                parent_block: &ast::BlockExpr,
+                if_expr: &ast::IfExpr,
+            ) -> SyntaxNode {
+                let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
+                let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
+                let end_of_then =
+                    if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
+                        end_of_then.prev_sibling_or_token().unwrap()
+                    } else {
+                        end_of_then
+                    };
+                let mut then_statements = new_expr.children_with_tokens().chain(
+                    then_block_items
+                        .syntax()
+                        .children_with_tokens()
+                        .skip(1)
+                        .take_while(|i| *i != end_of_then),
                 );
-                let let_stmt = if_indent_level.increase_indent(let_stmt);
-                replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
+                replace_children(
+                    &parent_block.syntax(),
+                    RangeInclusive::new(
+                        if_expr.clone().syntax().clone().into(),
+                        if_expr.syntax().clone().into(),
+                    ),
+                    &mut then_statements,
+                )
             }
-        };
-        edit.target(if_expr.syntax().text_range());
-        edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
-        edit.set_cursor(cursor_position);
-
-        fn replace(
-            new_expr: &SyntaxNode,
-            then_block: &ast::BlockExpr,
-            parent_block: &ast::BlockExpr,
-            if_expr: &ast::IfExpr,
-        ) -> SyntaxNode {
-            let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
-            let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
-            let end_of_then =
-                if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
-                    end_of_then.prev_sibling_or_token().unwrap()
-                } else {
-                    end_of_then
-                };
-            let mut then_statements = new_expr.children_with_tokens().chain(
-                then_block_items
-                    .syntax()
-                    .children_with_tokens()
-                    .skip(1)
-                    .take_while(|i| *i != end_of_then),
-            );
-            replace_children(
-                &parent_block.syntax(),
-                RangeInclusive::new(
-                    if_expr.clone().syntax().clone().into(),
-                    if_expr.syntax().clone().into(),
-                ),
-                &mut then_statements,
-            )
-        }
-    })
+        },
+    )
 }
 
 #[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 1f9cd55852..7c8f8bdf24 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> {
         return None;
     }
 
-    ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| {
+    let target = match_expr.syntax().text_range();
+    ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", target, |edit| {
         let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms);
-
-        edit.target(match_expr.syntax().text_range());
         edit.set_cursor(expr.syntax().text_range().start());
         edit.replace_ast(match_arm_list, new_arm_list);
     })
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs
index 41db963dc6..cb7264d7bb 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> {
         return None;
     }
 
-    ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", |edit| {
-        edit.target(op_range);
+    ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| {
         if let FlipAction::FlipAndReplaceOp(new_op) = action {
             edit.replace(op_range, new_op);
         }
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs
index e65c9a41db..24982ae225 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> {
         return None;
     }
 
-    ctx.add_assist(AssistId("flip_comma"), "Flip comma", |edit| {
-        edit.target(comma.text_range());
+    ctx.add_assist(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| {
         edit.replace(prev.text_range(), next.to_string());
         edit.replace(next.text_range(), prev.to_string());
     })
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs
index f186da5858..6a3b2df679 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> {
         non_trivia_sibling(plus.clone().into(), Direction::Next)?,
     );
 
-    ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", |edit| {
-        edit.target(plus.text_range());
+    let target = plus.text_range();
+    ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| {
         edit.replace(before.text_range(), after.to_string());
         edit.replace(after.text_range(), before.to_string());
     })
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs
index 5f3b8dfd11..e5765c845e 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> {
     let init_str = initializer_expr.syntax().text().to_string();
     let init_in_paren = format!("({})", &init_str);
 
+    let target = bind_pat.syntax().text_range();
     ctx.add_assist(
         AssistId("inline_local_variable"),
         "Inline variable",
+        target,
         move |edit: &mut ActionBuilder| {
             edit.delete(delete_range);
             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 9c2c20b22c..3c340ff3b9 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> {
     if indent.kind() != WHITESPACE {
         return None;
     }
-    ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", move |edit| {
+    let target = expr.syntax().text_range();
+    ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", target, move |edit| {
         let mut buf = String::new();
 
         let cursor_offset = if wrap_in_block {
@@ -79,7 +80,6 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
                 buf.push_str(text);
             }
 
-            edit.target(expr.syntax().text_range());
             edit.replace(expr.syntax().text_range(), "var_name".to_string());
             edit.insert(anchor_stmt.text_range().start(), buf);
             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 303c1806d2..b16271443e 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> {
         let else_node = else_block.syntax();
         let else_range = else_node.text_range();
         let then_range = then_node.text_range();
-        return ctx.add_assist(AssistId("invert_if"), "Invert if", |edit| {
-            edit.target(if_range);
+        return ctx.add_assist(AssistId("invert_if"), "Invert if", if_range, |edit| {
             edit.replace(cond_range, flip_cond.syntax().text());
             edit.replace(else_range, then_node.text());
             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 9a2083609d..de74d83d85 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> {
         }
     };
 
-    ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| {
+    let target = tree.syntax().text_range();
+    ctx.add_assist(AssistId("merge_imports"), "Merge imports", target, |edit| {
         edit.rewrite(rewriter);
         // FIXME: we only need because our diff is imprecise
         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 9ae099b413..7c4d9d55d7 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> {
         return None;
     }
 
-    ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| {
+    ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| {
         let pats = if arms_to_merge.iter().any(contains_placeholder) {
             "_".into()
         } else {
@@ -87,7 +87,6 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
         let start = arms_to_merge.first().unwrap().syntax().text_range().start();
         let end = arms_to_merge.last().unwrap().syntax().text_range().end();
 
-        edit.target(current_text_range);
         edit.set_cursor(match cursor_pos {
             CursorPos::InExpr(back_offset) => start + TextSize::of(&arm) - back_offset,
             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 89956aea95..44e50cb6e6 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> {
         }
     };
 
-    ctx.add_assist(AssistId("move_bounds_to_where_clause"), "Move to where clause", |edit| {
-        let new_params = type_param_list
-            .type_params()
-            .filter(|it| it.type_bound_list().is_some())
-            .map(|type_param| {
-                let without_bounds = type_param.remove_bounds();
-                (type_param, without_bounds)
-            });
-
-        let new_type_param_list = type_param_list.replace_descendants(new_params);
-        edit.replace_ast(type_param_list.clone(), new_type_param_list);
-
-        let where_clause = {
-            let predicates = type_param_list.type_params().filter_map(build_predicate);
-            make::where_clause(predicates)
-        };
-
-        let to_insert = match anchor.prev_sibling_or_token() {
-            Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()),
-            _ => format!(" {}", where_clause.syntax()),
-        };
-        edit.insert(anchor.text_range().start(), to_insert);
-        edit.target(type_param_list.syntax().text_range());
-    })
+    let target = type_param_list.syntax().text_range();
+    ctx.add_assist(
+        AssistId("move_bounds_to_where_clause"),
+        "Move to where clause",
+        target,
+        |edit| {
+            let new_params = type_param_list
+                .type_params()
+                .filter(|it| it.type_bound_list().is_some())
+                .map(|type_param| {
+                    let without_bounds = type_param.remove_bounds();
+                    (type_param, without_bounds)
+                });
+
+            let new_type_param_list = type_param_list.replace_descendants(new_params);
+            edit.replace_ast(type_param_list.clone(), new_type_param_list);
+
+            let where_clause = {
+                let predicates = type_param_list.type_params().filter_map(build_predicate);
+                make::where_clause(predicates)
+            };
+
+            let to_insert = match anchor.prev_sibling_or_token() {
+                Some(ref elem) if elem.kind() == WHITESPACE => {
+                    format!("{} ", where_clause.syntax())
+                }
+                _ => format!(" {}", where_clause.syntax()),
+            };
+            edit.insert(anchor.text_range().start(), to_insert);
+        },
+    )
 }
 
 fn 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 f2aa7e5940..29bc9a9ffb 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> {
     let arm_expr = match_arm.expr()?;
     let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
 
-    ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", |edit| {
-        edit.target(guard.syntax().text_range());
+    let target = guard.syntax().text_range();
+    ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| {
         let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) {
             Some(tok) => {
                 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> {
 
     let buf = format!(" if {}", cond.syntax().text());
 
+    let target = if_expr.syntax().text_range();
     ctx.add_assist(
         AssistId("move_arm_cond_to_match_guard"),
         "Move condition to match guard",
+        target,
         |edit| {
-            edit.target(if_expr.syntax().text_range());
             let then_only_expr = then_block.statements().next().is_none();
 
             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 542f7a6379..155c679b4b 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};
 pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
     let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
     let value = token.value()?;
-    ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| {
-        edit.target(token.syntax().text_range());
+    let target = token.syntax().text_range();
+    ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| {
         let max_hash_streak = count_hashes(&value);
         let mut hashes = String::with_capacity(max_hash_streak + 1);
         for _ in 0..hashes.capacity() {
@@ -54,8 +54,8 @@ pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
 pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
     let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
     let value = token.value()?;
-    ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| {
-        edit.target(token.syntax().text_range());
+    let target = token.syntax().text_range();
+    ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| {
         // parse inside string to escape `"`
         let escaped = value.escape_default().to_string();
         edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
@@ -79,8 +79,8 @@ pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
 // ```
 pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
     let token = ctx.find_token_at_offset(RAW_STRING)?;
-    ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| {
-        edit.target(token.text_range());
+    let target = token.text_range();
+    ctx.add_assist(AssistId("add_hash"), "Add # to raw string", target, |edit| {
         edit.insert(token.text_range().start() + TextSize::of('r'), "#");
         edit.insert(token.text_range().end(), "#");
     })
@@ -108,8 +108,8 @@ pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
         // no hash to remove
         return None;
     }
-    ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", |edit| {
-        edit.target(token.text_range());
+    let target = token.text_range();
+    ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| {
         let result = &text[2..text.len() - 1];
         let result = if result.starts_with('\"') {
             // 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 ddfb21a7e3..e6e02f2aec 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> {
         text.slice(without_parens).to_string()
     };
 
-    ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", |edit| {
-        edit.target(macro_call.syntax().text_range());
+    let target = macro_call.syntax().text_range();
+    ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| {
         edit.replace(macro_range, macro_content);
         edit.set_cursor(cursor_pos);
     })
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs
index e598023b25..9f72f879d5 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> {
         _ => mut_token.text_range().end(),
     };
 
-    ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", |edit| {
+    let target = mut_token.text_range();
+    ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| {
         edit.set_cursor(delete_from);
         edit.delete(TextRange::new(delete_from, delete_to));
     })
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs
index a57e327b8f..0b930dea21 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> {
         return None;
     }
 
-    ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| {
+    let target = record.syntax().text_range();
+    ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", target, |edit| {
         for (old, new) in fields.iter().zip(&sorted_fields) {
             algo::diff(old, new).into_text_edit(edit.text_edit_builder());
         }
-        edit.target(record.syntax().text_range())
     })
 }
 
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 d0df3b84e3..2eb8348f82 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> {
     };
 
     let sema = ctx.sema;
-    ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| {
-        let match_expr = {
-            let then_arm = {
-                let then_expr = unwrap_trivial_block(then_block);
-                make::match_arm(vec![pat.clone()], then_expr)
+    let target = if_expr.syntax().text_range();
+    ctx.add_assist(
+        AssistId("replace_if_let_with_match"),
+        "Replace with match",
+        target,
+        move |edit| {
+            let match_expr = {
+                let then_arm = {
+                    let then_expr = unwrap_trivial_block(then_block);
+                    make::match_arm(vec![pat.clone()], then_expr)
+                };
+                let else_arm = {
+                    let pattern = sema
+                        .type_of_pat(&pat)
+                        .and_then(|ty| TryEnum::from_ty(sema, &ty))
+                        .map(|it| it.sad_pattern())
+                        .unwrap_or_else(|| make::placeholder_pat().into());
+                    let else_expr = unwrap_trivial_block(else_block);
+                    make::match_arm(vec![pattern], else_expr)
+                };
+                make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
             };
-            let else_arm = {
-                let pattern = sema
-                    .type_of_pat(&pat)
-                    .and_then(|ty| TryEnum::from_ty(sema, &ty))
-                    .map(|it| it.sad_pattern())
-                    .unwrap_or_else(|| make::placeholder_pat().into());
-                let else_expr = unwrap_trivial_block(else_block);
-                make::match_arm(vec![pattern], else_expr)
-            };
-            make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
-        };
 
-        let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
+            let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
 
-        edit.target(if_expr.syntax().text_range());
-        edit.set_cursor(if_expr.syntax().text_range().start());
-        edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
-    })
+            edit.set_cursor(if_expr.syntax().text_range().start());
+            edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
+        },
+    )
 }
 
 #[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 dc4d16055b..a5509a5673 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> {
     let ty = ctx.sema.type_of_expr(&init)?;
     let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case());
 
-    ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| {
+    let target = let_kw.text_range();
+    ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| {
         let with_placeholder: ast::Pat = match happy_variant {
             None => make::placeholder_pat().into(),
             Some(var_name) => make::tuple_struct_pat(
@@ -67,7 +68,6 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
         let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
 
         edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
-        edit.target(let_kw.text_range());
         edit.set_cursor(target_offset);
     })
 }
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 6241789240..fd41da64b8 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>
         return None;
     }
 
+    let target = path.syntax().text_range();
     ctx.add_assist(
         AssistId("replace_qualified_name_with_use"),
         "Replace qualified path with use",
+        target,
         |edit| {
             let path_to_import = hir_path.mod_path().clone();
             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 dcb471edb0..c6b73da67b 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> {
     let caller = method_call.expr()?;
     let ty = ctx.sema.type_of_expr(&caller)?;
     let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case();
+    let target = method_call.syntax().text_range();
+    ctx.add_assist(
+        AssistId("replace_unwrap_with_match"),
+        "Replace unwrap with match",
+        target,
+        |edit| {
+            let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
+            let it = make::bind_pat(make::name("a")).into();
+            let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
 
-    ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| {
-        let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
-        let it = make::bind_pat(make::name("a")).into();
-        let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
+            let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
+            let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
 
-        let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
-        let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
+            let unreachable_call = make::unreachable_macro_call().into();
+            let err_arm =
+                make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
 
-        let unreachable_call = make::unreachable_macro_call().into();
-        let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
+            let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
+            let match_expr = make::expr_match(caller.clone(), match_arm_list);
+            let match_expr =
+                IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
 
-        let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
-        let match_expr = make::expr_match(caller.clone(), match_arm_list);
-        let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
-
-        edit.target(method_call.syntax().text_range());
-        edit.set_cursor(caller.syntax().text_range().start());
-        edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
-    })
+            edit.set_cursor(caller.syntax().text_range().start());
+            edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
+        },
+    )
 }
 
 #[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
index a59f2f76f6..d495639746 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> {
     }
     let cursor = ctx.frange.range.start();
 
-    ctx.add_assist(AssistId("split_import"), "Split import", |edit| {
-        edit.target(colon_colon.text_range());
+    let target = colon_colon.text_range();
+    ctx.add_assist(AssistId("split_import"), "Split import", target, |edit| {
         edit.replace_ast(use_tree, new_tree);
         edit.set_cursor(cursor);
     })
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs
index 89992117d8..6df927abbd 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> {
         }
     };
 
-    ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", |edit| {
+    let target = expr_to_unwrap.syntax().text_range();
+    ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", target, |edit| {
         edit.set_cursor(expr.syntax().text_range().start());
-        edit.target(expr_to_unwrap.syntax().text_range());
 
         let pat_start: &[_] = &[' ', '{', '\n'];
         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 b794b021d7..f4f37614ff 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -36,16 +36,24 @@ pub struct AssistLabel {
     /// Short description of the assist, as shown in the UI.
     pub label: String,
     pub group: Option<GroupLabel>,
+    /// Target ranges are used to sort assists: the smaller the target range,
+    /// the more specific assist is, and so it should be sorted first.
+    pub target: TextRange,
 }
 
 #[derive(Clone, Debug)]
 pub struct GroupLabel(pub String);
 
 impl AssistLabel {
-    pub(crate) fn new(id: AssistId, label: String, group: Option<GroupLabel>) -> AssistLabel {
+    pub(crate) fn new(
+        id: AssistId,
+        label: String,
+        group: Option<GroupLabel>,
+        target: TextRange,
+    ) -> AssistLabel {
         // FIXME: make fields private, so that this invariant can't be broken
         assert!(label.starts_with(|c: char| c.is_uppercase()));
-        AssistLabel { id, label, group }
+        AssistLabel { id, label, group, target }
     }
 }
 
@@ -53,8 +61,6 @@ impl AssistLabel {
 pub struct AssistAction {
     pub edit: TextEdit,
     pub cursor_position: Option<TextSize>,
-    // FIXME: This belongs to `AssistLabel`
-    pub target: Option<TextRange>,
     pub file: AssistFile,
 }
 
@@ -104,7 +110,7 @@ pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssi
         .flat_map(|it| it.0)
         .map(|it| it.into_resolved().unwrap())
         .collect::<Vec<_>>();
-    a.sort_by_key(|it| it.action.target.map_or(TextSize::from(!0u32), |it| it.len()));
+    a.sort_by_key(|it| it.label.target.len());
     a
 }
 


Test patch:
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs
index 483e119319..dd9026df61 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) {
             assert_eq_text!(after, &actual);
         }
         (Some(assist), ExpectedResult::Target(target)) => {
-            let action = assist.0[0].action.clone().unwrap();
-            let range = action.target.expect("expected target on action");
+            let range = assist.0[0].label.target;
             assert_eq_text!(&text_without_caret[range], target);
         }
         (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),