Repository: rust-analyzer/rust-analyzer
Pull request #2099 (2019-10-27 18:12:16)

Fix panic on raw string assist
Strings that do not contain two quotation marks would cause a slice indexing panic because `find_usual_string_range` would return a range that only contained a single quotation mark.
Panic example:
```
fn main() {
    let s = "<|>
}
```

I noticed a lot of panics from the `make_raw_string` assist while working on another issue today.


Patch:
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 1908bdec90..c527366791 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -14,8 +14,8 @@ use crate::{AssistAction, AssistId, AssistLabel};
 
 #[derive(Clone, Debug)]
 pub(crate) enum Assist {
-    Unresolved { label: AssistLabel },
-    Resolved { label: AssistLabel, action: AssistAction },
+    Unresolved(Vec<AssistLabel>),
+    Resolved(Vec<(AssistLabel, AssistAction)>),
 }
 
 /// `AssistCtx` allows to apply an assist or check if it could be applied.
@@ -54,6 +54,7 @@ pub(crate) struct AssistCtx<'a, DB> {
     pub(crate) frange: FileRange,
     source_file: SourceFile,
     should_compute_edit: bool,
+    assist: Assist,
 }
 
 impl<'a, DB> Clone for AssistCtx<'a, DB> {
@@ -63,6 +64,7 @@ impl<'a, DB> Clone for AssistCtx<'a, DB> {
             frange: self.frange,
             source_file: self.source_file.clone(),
             should_compute_edit: self.should_compute_edit,
+            assist: self.assist.clone(),
         }
     }
 }
@@ -73,30 +75,36 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
         F: FnOnce(AssistCtx<DB>) -> T,
     {
         let parse = db.parse(frange.file_id);
+        let assist =
+            if should_compute_edit { Assist::Resolved(vec![]) } else { Assist::Unresolved(vec![]) };
 
-        let ctx = AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit };
+        let ctx = AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit, assist };
         f(ctx)
     }
 
-    pub(crate) fn add_assist(
-        self,
+    pub(crate) fn add_action(
+        &mut self,
         id: AssistId,
         label: impl Into<String>,
         f: impl FnOnce(&mut AssistBuilder),
-    ) -> Option<Assist> {
+    ) -> &mut Self {
         let label = AssistLabel { label: label.into(), id };
-        let assist = if self.should_compute_edit {
-            let action = {
-                let mut edit = AssistBuilder::default();
-                f(&mut edit);
-                edit.build()
-            };
-            Assist::Resolved { label, action }
-        } else {
-            Assist::Unresolved { label }
-        };
-
-        Some(assist)
+        match &mut self.assist {
+            Assist::Unresolved(labels) => labels.push(label),
+            Assist::Resolved(labels_actions) => {
+                let action = {
+                    let mut edit = AssistBuilder::default();
+                    f(&mut edit);
+                    edit.build()
+                };
+                labels_actions.push((label, action));
+            }
+        }
+        self
+    }
+
+    pub(crate) fn build(self) -> Option<Assist> {
+        Some(self.assist)
     }
 
     pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
diff --git a/crates/ra_assists/src/assists/add_derive.rs b/crates/ra_assists/src/assists/add_derive.rs
index 764b17bd8d..d1e925b71f 100644
--- a/crates/ra_assists/src/assists/add_derive.rs
+++ b/crates/ra_assists/src/assists/add_derive.rs
@@ -25,10 +25,10 @@ use crate::{Assist, AssistCtx, AssistId};
 //     y: u32,
 // }
 // ```
-pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> 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| {
+    ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| {
         let derive_attr = nominal
             .attrs()
             .filter_map(|x| x.as_simple_call())
@@ -44,7 +44,9 @@ pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
         };
         edit.target(nominal.syntax().text_range());
         edit.set_cursor(offset)
-    })
+    });
+
+    ctx.build()
 }
 
 // Insert `derive` after doc comments.
diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/assists/add_explicit_type.rs
index ddda1a0f2c..ffbdc0b629 100644
--- a/crates/ra_assists/src/assists/add_explicit_type.rs
+++ b/crates/ra_assists/src/assists/add_explicit_type.rs
@@ -21,7 +21,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     let x: i32 = 92;
 // }
 // ```
-pub(crate) fn add_explicit_type(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn add_explicit_type(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let stmt = ctx.find_node_at_offset::<LetStmt>()?;
     let expr = stmt.initializer()?;
     let pat = stmt.pat()?;
@@ -47,10 +47,11 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx<impl HirDatabase>) -> Option<Assi
         return None;
     }
 
-    ctx.add_assist(AssistId("add_explicit_type"), "add explicit type", |edit| {
+    ctx.add_action(AssistId("add_explicit_type"), "add explicit type", |edit| {
         edit.target(pat_range);
         edit.insert(name_range.end(), format!(": {}", ty.display(db)));
-    })
+    });
+    ctx.build()
 }
 
 /// Returns true if any type parameter is unknown
diff --git a/crates/ra_assists/src/assists/add_impl.rs b/crates/ra_assists/src/assists/add_impl.rs
index 7da0cfd0d3..fd3588d249 100644
--- a/crates/ra_assists/src/assists/add_impl.rs
+++ b/crates/ra_assists/src/assists/add_impl.rs
@@ -27,10 +27,10 @@ use crate::{Assist, AssistCtx, AssistId};
 //
 // }
 // ```
-pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
     let name = nominal.name()?;
-    ctx.add_assist(AssistId("add_impl"), "add impl", |edit| {
+    ctx.add_action(AssistId("add_impl"), "add impl", |edit| {
         edit.target(nominal.syntax().text_range());
         let type_params = nominal.type_param_list();
         let start_offset = nominal.syntax().text_range().end();
@@ -54,7 +54,9 @@ pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
         edit.set_cursor(start_offset + TextUnit::of_str(&buf));
         buf.push_str("\n}");
         edit.insert(start_offset, buf);
-    })
+    });
+
+    ctx.build()
 }
 
 #[cfg(test)]
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs
index 41de239219..2585f30454 100644
--- a/crates/ra_assists/src/assists/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs
@@ -91,7 +91,7 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> O
 }
 
 fn add_missing_impl_members_inner(
-    ctx: AssistCtx<impl HirDatabase>,
+    mut ctx: AssistCtx<impl HirDatabase>,
     mode: AddMissingImplMembersMode,
     assist_id: &'static str,
     label: &'static str,
@@ -133,7 +133,7 @@ fn add_missing_impl_members_inner(
         return None;
     }
 
-    ctx.add_assist(AssistId(assist_id), label, |edit| {
+    ctx.add_action(AssistId(assist_id), label, |edit| {
         let n_existing_items = impl_item_list.impl_items().count();
         let items = missing_items
             .into_iter()
@@ -150,7 +150,9 @@ fn add_missing_impl_members_inner(
 
         edit.replace_ast(impl_item_list, new_impl_item_list);
         edit.set_cursor(cursor_position);
-    })
+    });
+
+    ctx.build()
 }
 
 fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs
index 068da1774d..8d5984a583 100644
--- a/crates/ra_assists/src/assists/apply_demorgan.rs
+++ b/crates/ra_assists/src/assists/apply_demorgan.rs
@@ -23,7 +23,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     if !(x == 4 && y) {}
 // }
 // ```
-pub(crate) fn apply_demorgan(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn apply_demorgan(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
     let op = expr.op_kind()?;
     let op_range = expr.op_token()?.text_range();
@@ -39,12 +39,13 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist>
     let not_lhs = undo_negation(lhs)?;
     let not_rhs = undo_negation(rhs)?;
 
-    ctx.add_assist(AssistId("apply_demorgan"), "apply demorgan's law", |edit| {
+    ctx.add_action(AssistId("apply_demorgan"), "apply demorgan's law", |edit| {
         edit.target(op_range);
         edit.replace(op_range, opposite_op);
         edit.replace(lhs_range, format!("!({}", not_lhs));
         edit.replace(rhs_range, format!("{})", not_rhs));
-    })
+    });
+    ctx.build()
 }
 
 // Return the opposite text for a given logical operator, if it makes sense
diff --git a/crates/ra_assists/src/assists/add_import.rs b/crates/ra_assists/src/assists/auto_import.rs
similarity index 92%
rename from crates/ra_assists/src/assists/add_import.rs
rename to crates/ra_assists/src/assists/auto_import.rs
index e87fae1af0..a1c2aaa729 100644
--- a/crates/ra_assists/src/assists/add_import.rs
+++ b/crates/ra_assists/src/assists/auto_import.rs
@@ -1,81 +1,18 @@
+//! FIXME: write short doc here
+
 use hir::{self, db::HirDatabase};
-use ra_syntax::{
-    ast::{self, NameOwner},
-    AstNode, Direction, SmolStr,
-    SyntaxKind::{PATH, PATH_SEGMENT},
-    SyntaxNode, TextRange, T,
-};
 use ra_text_edit::TextEditBuilder;
 
 use crate::{
     assist_ctx::{Assist, AssistCtx},
     AssistId,
 };
-
-/// This function produces sequence of text edits into edit
-/// to import the target path in the most appropriate scope given
-/// the cursor position
-pub fn auto_import_text_edit(
-    // Ideally the position of the cursor, used to
-    position: &SyntaxNode,
-    // The statement to use as anchor (last resort)
-    anchor: &SyntaxNode,
-    // The path to import as a sequence of strings
-    target: &[SmolStr],
-    edit: &mut TextEditBuilder,
-) {
-    let container = position.ancestors().find_map(|n| {
-        if let Some(module) = ast::Module::cast(n.clone()) {
-            return module.item_list().map(|it| it.syntax().clone());
-        }
-        ast::SourceFile::cast(n).map(|it| it.syntax().clone())
-    });
-
-    if let Some(container) = container {
-        let action = best_action_for_target(container, anchor.clone(), target);
-        make_assist(&action, target, edit);
-    }
-}
-
-// Assist: add_import
-//
-// Adds a use statement for a given fully-qualified path.
-//
-// ```
-// fn process(map: std::collections::<|>HashMap<String, String>) {}
-// ```
-// ->
-// ```
-// use std::collections::HashMap;
-//
-// fn process(map: HashMap<String, String>) {}
-// ```
-pub(crate) fn add_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
-    let path: ast::Path = ctx.find_node_at_offset()?;
-    // We don't want to mess with use statements
-    if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
-        return None;
-    }
-
-    let hir_path = hir::Path::from_ast(path.clone())?;
-    let segments = collect_hir_path_segments(&hir_path)?;
-    if segments.len() < 2 {
-        return None;
-    }
-
-    let module = path.syntax().ancestors().find_map(ast::Module::cast);
-    let position = match module.and_then(|it| it.item_list()) {
-        Some(item_list) => item_list.syntax().clone(),
-        None => {
-            let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?;
-            current_file.syntax().clone()
-        }
-    };
-
-    ctx.add_assist(AssistId("add_import"), format!("import {}", fmt_segments(&segments)), |edit| {
-        apply_auto_import(&position, &path, &segments, edit.text_edit_builder());
-    })
-}
+use ra_syntax::{
+    ast::{self, NameOwner},
+    AstNode, Direction, SmolStr,
+    SyntaxKind::{PATH, PATH_SEGMENT},
+    SyntaxNode, TextRange, T,
+};
 
 fn collect_path_segments_raw(
     segments: &mut Vec<ast::PathSegment>,
@@ -568,7 +505,7 @@ fn apply_auto_import(
     }
 }
 
-fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
+pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
     let mut ps = Vec::<SmolStr>::with_capacity(10);
     match path.kind {
         hir::PathKind::Abs => ps.push("".into()),
@@ -584,16 +521,87 @@ fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
     Some(ps)
 }
 
+// This function produces sequence of text edits into edit
+// to import the target path in the most appropriate scope given
+// the cursor position
+pub fn auto_import_text_edit(
+    // Ideally the position of the cursor, used to
+    position: &SyntaxNode,
+    // The statement to use as anchor (last resort)
+    anchor: &SyntaxNode,
+    // The path to import as a sequence of strings
+    target: &[SmolStr],
+    edit: &mut TextEditBuilder,
+) {
+    let container = position.ancestors().find_map(|n| {
+        if let Some(module) = ast::Module::cast(n.clone()) {
+            return module.item_list().map(|it| it.syntax().clone());
+        }
+        ast::SourceFile::cast(n).map(|it| it.syntax().clone())
+    });
+
+    if let Some(container) = container {
+        let action = best_action_for_target(container, anchor.clone(), target);
+        make_assist(&action, target, edit);
+    }
+}
+
+pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+    let path: ast::Path = ctx.find_node_at_offset()?;
+    // We don't want to mess with use statements
+    if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
+        return None;
+    }
+
+    let hir_path = hir::Path::from_ast(path.clone())?;
+    let segments = collect_hir_path_segments(&hir_path)?;
+    if segments.len() < 2 {
+        return None;
+    }
+
+    if let Some(module) = path.syntax().ancestors().find_map(ast::Module::cast) {
+        if let (Some(item_list), Some(name)) = (module.item_list(), module.name()) {
+            ctx.add_action(
+                AssistId("auto_import"),
+                format!("import {} in mod {}", fmt_segments(&segments), name.text()),
+                |edit| {
+                    apply_auto_import(
+                        item_list.syntax(),
+                        &path,
+                        &segments,
+                        edit.text_edit_builder(),
+                    );
+                },
+            );
+        }
+    } else {
+        let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?;
+        ctx.add_action(
+            AssistId("auto_import"),
+            format!("import {} in the current file", fmt_segments(&segments)),
+            |edit| {
+                apply_auto_import(
+                    current_file.syntax(),
+                    &path,
+                    &segments,
+                    edit.text_edit_builder(),
+                );
+            },
+        );
+    }
+
+    ctx.build()
+}
+
 #[cfg(test)]
 mod tests {
-    use crate::helpers::{check_assist, check_assist_not_applicable};
-
     use super::*;
+    use crate::helpers::{check_assist, check_assist_not_applicable};
 
     #[test]
     fn test_auto_import_add_use_no_anchor() {
         check_assist(
-            add_import,
+            auto_import,
             "
 std::fmt::Debug<|>
     ",
@@ -607,7 +615,7 @@ Debug<|>
     #[test]
     fn test_auto_import_add_use_no_anchor_with_item_below() {
         check_assist(
-            add_import,
+            auto_import,
             "
 std::fmt::Debug<|>
 
@@ -628,7 +636,7 @@ fn main() {
     #[test]
     fn test_auto_import_add_use_no_anchor_with_item_above() {
         check_assist(
-            add_import,
+            auto_import,
             "
 fn main() {
 }
@@ -649,7 +657,7 @@ Debug<|>
     #[test]
     fn test_auto_import_add_use_no_anchor_2seg() {
         check_assist(
-            add_import,
+            auto_import,
             "
 std::fmt<|>::Debug
     ",
@@ -664,7 +672,7 @@ fmt<|>::Debug
     #[test]
     fn test_auto_import_add_use() {
         check_assist(
-            add_import,
+            auto_import,
             "
 use stdx;
 
@@ -684,7 +692,7 @@ impl Debug<|> for Foo {
     #[test]
     fn test_auto_import_file_use_other_anchor() {
         check_assist(
-            add_import,
+            auto_import,
             "
 impl std::fmt::Debug<|> for Foo {
 }
@@ -701,7 +709,7 @@ impl Debug<|> for Foo {
     #[test]
     fn test_auto_import_add_use_other_anchor_indent() {
         check_assist(
-            add_import,
+            auto_import,
             "
     impl std::fmt::Debug<|> for Foo {
     }
@@ -718,7 +726,7 @@ impl Debug<|> for Foo {
     #[test]
     fn test_auto_import_split_different() {
         check_assist(
-            add_import,
+            auto_import,
             "
 use std::fmt;
 
@@ -737,7 +745,7 @@ impl io<|> for Foo {
     #[test]
     fn test_auto_import_split_self_for_use() {
         check_assist(
-            add_import,
+            auto_import,
             "
 use std::fmt;
 
@@ -756,7 +764,7 @@ impl Debug<|> for Foo {
     #[test]
     fn test_auto_import_split_self_for_target() {
         check_assist(
-            add_import,
+            auto_import,
             "
 use std::fmt::Debug;
 
@@ -775,7 +783,7 @@ impl fmt<|> for Foo {
     #[test]
     fn test_auto_import_add_to_nested_self_nested() {
         check_assist(
-            add_import,
+            auto_import,
             "
 use std::fmt::{Debug, nested::{Display}};
 
@@ -794,7 +802,7 @@ impl nested<|> for Foo {
     #[test]
     fn test_auto_import_add_to_nested_self_already_included() {
         check_assist(
-            add_import,
+            auto_import,
             "
 use std::fmt::{Debug, nested::{self, Display}};
 
@@ -813,7 +821,7 @@ impl nested<|> for Foo {
     #[test]
     fn test_auto_import_add_to_nested_nested() {
         check_assist(
-            add_import,
+            auto_import,
             "
 use std::fmt::{Debug, nested::{Display}};
 
@@ -832,7 +840,7 @@ impl Debug<|> for Foo {
     #[test]
     fn test_auto_import_split_common_target_longer() {
         check_assist(
-            add_import,
+            auto_import,
             "
 use std::fmt::Debug;
 
@@ -851,7 +859,7 @@ impl Display<|> for Foo {
     #[test]
     fn test_auto_import_split_common_use_longer() {
         check_assist(
-            add_import,
+            auto_import,
             "
 use std::fmt::nested::Debug;
 
@@ -870,7 +878,7 @@ impl Display<|> for Foo {
     #[test]
     fn test_auto_import_alias() {
         check_assist(
-            add_import,
+            auto_import,
             "
 use std::fmt as foo;
 
@@ -889,7 +897,7 @@ impl Debug<|> for Foo {
     #[test]
     fn test_auto_import_not_applicable_one_segment() {
         check_assist_not_applicable(
-            add_import,
+            auto_import,
             "
 impl foo<|> for Foo {
 }
@@ -900,7 +908,7 @@ impl foo<|> for Foo {
     #[test]
     fn test_auto_import_not_applicable_in_use() {
         check_assist_not_applicable(
-            add_import,
+            auto_import,
             "
 use std::fmt<|>;
 ",
@@ -910,7 +918,7 @@ use std::fmt<|>;
     #[test]
     fn test_auto_import_add_use_no_anchor_in_mod_mod() {
         check_assist(
-            add_import,
+            auto_import,
             "
 mod foo {
     mod bar {
diff --git a/crates/ra_assists/src/assists/change_visibility.rs b/crates/ra_assists/src/assists/change_visibility.rs
index 132c9dc1d7..770ea04fa3 100644
--- a/crates/ra_assists/src/assists/change_visibility.rs
+++ b/crates/ra_assists/src/assists/change_visibility.rs
@@ -29,7 +29,7 @@ pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assi
     add_vis(ctx)
 }
 
-fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() {
         T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true,
         _ => false,
@@ -57,11 +57,13 @@ fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
         (vis_offset(field.syntax()), ident.text_range())
     };
 
-    ctx.add_assist(AssistId("change_visibility"), "make pub(crate)", |edit| {
+    ctx.add_action(AssistId("change_visibility"), "make pub(crate)", |edit| {
         edit.target(target);
         edit.insert(offset, "pub(crate) ");
         edit.set_cursor(offset);
-    })
+    });
+
+    ctx.build()
 }
 
 fn vis_offset(node: &SyntaxNode) -> TextUnit {
@@ -75,20 +77,24 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit {
         .unwrap_or_else(|| node.text_range().start())
 }
 
-fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> {
+fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> {
     if vis.syntax().text() == "pub" {
-        return ctx.add_assist(AssistId("change_visibility"), "change to pub(crate)", |edit| {
+        ctx.add_action(AssistId("change_visibility"), "change to pub(crate)", |edit| {
             edit.target(vis.syntax().text_range());
             edit.replace(vis.syntax().text_range(), "pub(crate)");
             edit.set_cursor(vis.syntax().text_range().start())
         });
+
+        return ctx.build();
     }
     if vis.syntax().text() == "pub(crate)" {
-        return ctx.add_assist(AssistId("change_visibility"), "change to pub", |edit| {
+        ctx.add_action(AssistId("change_visibility"), "change to pub", |edit| {
             edit.target(vis.syntax().text_range());
             edit.replace(vis.syntax().text_range(), "pub");
             edit.set_cursor(vis.syntax().text_range().start());
         });
+
+        return ctx.build();
     }
     None
 }
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs
index e839d831e0..75822dc65a 100644
--- a/crates/ra_assists/src/assists/early_return.rs
+++ b/crates/ra_assists/src/assists/early_return.rs
@@ -35,7 +35,7 @@ use crate::{
 //     bar();
 // }
 // ```
-pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
     let expr = if_expr.condition()?.expr()?;
     let then_block = if_expr.then_branch()?.block()?;
@@ -75,7 +75,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
     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| {
+    ctx.add_action(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| {
         let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
         let new_if_expr =
             if_indent_level.increase_indent(make::if_expression(&expr, early_expression));
@@ -105,7 +105,8 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
         edit.target(if_expr.syntax().text_range());
         edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap());
         edit.set_cursor(cursor_position);
-    })
+    });
+    ctx.build()
 }
 
 #[cfg(test)]
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs
index 2b74f355c6..c62c0efbef 100644
--- a/crates/ra_assists/src/assists/fill_match_arms.rs
+++ b/crates/ra_assists/src/assists/fill_match_arms.rs
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     }
 // }
 // ```
-pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
     let match_arm_list = match_expr.match_arm_list()?;
 
@@ -53,7 +53,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
     };
     let variant_list = enum_def.variant_list()?;
 
-    ctx.add_assist(AssistId("fill_match_arms"), "fill match arms", |edit| {
+    ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| {
         let indent_level = IndentLevel::from_node(match_arm_list.syntax());
 
         let new_arm_list = {
@@ -67,7 +67,9 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
         edit.target(match_expr.syntax().text_range());
         edit.set_cursor(expr.syntax().text_range().start());
         edit.replace_ast(match_arm_list, new_arm_list);
-    })
+    });
+
+    ctx.build()
 }
 
 fn is_trivial(arm: &ast::MatchArm) -> bool {
diff --git a/crates/ra_assists/src/assists/flip_binexpr.rs b/crates/ra_assists/src/assists/flip_binexpr.rs
index 386045eb0a..9765d5dddc 100644
--- a/crates/ra_assists/src/assists/flip_binexpr.rs
+++ b/crates/ra_assists/src/assists/flip_binexpr.rs
@@ -18,7 +18,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     let _ = 2 + 90;
 // }
 // ```
-pub(crate) fn flip_binexpr(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn flip_binexpr(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let expr = ctx.find_node_at_offset::<BinExpr>()?;
     let lhs = expr.lhs()?.syntax().clone();
     let rhs = expr.rhs()?.syntax().clone();
@@ -34,14 +34,16 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
         return None;
     }
 
-    ctx.add_assist(AssistId("flip_binexpr"), "flip binary expression", |edit| {
+    ctx.add_action(AssistId("flip_binexpr"), "flip binary expression", |edit| {
         edit.target(op_range);
         if let FlipAction::FlipAndReplaceOp(new_op) = action {
             edit.replace(op_range, new_op);
         }
         edit.replace(lhs.text_range(), rhs.text());
         edit.replace(rhs.text_range(), lhs.text());
-    })
+    });
+
+    ctx.build()
 }
 
 enum FlipAction {
diff --git a/crates/ra_assists/src/assists/flip_comma.rs b/crates/ra_assists/src/assists/flip_comma.rs
index 9be1c1dc69..53ba8011d7 100644
--- a/crates/ra_assists/src/assists/flip_comma.rs
+++ b/crates/ra_assists/src/assists/flip_comma.rs
@@ -18,7 +18,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     ((3, 4), (1, 2));
 // }
 // ```
-pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let comma = ctx.find_token_at_offset(T![,])?;
     let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
     let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
@@ -29,11 +29,13 @@ pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
         return None;
     }
 
-    ctx.add_assist(AssistId("flip_comma"), "flip comma", |edit| {
+    ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| {
         edit.target(comma.text_range());
         edit.replace(prev.text_range(), next.to_string());
         edit.replace(next.text_range(), prev.to_string());
-    })
+    });
+
+    ctx.build()
 }
 
 #[cfg(test)]
diff --git a/crates/ra_assists/src/assists/flip_trait_bound.rs b/crates/ra_assists/src/assists/flip_trait_bound.rs
deleted file mode 100644
index 6017b39dd8..0000000000
--- a/crates/ra_assists/src/assists/flip_trait_bound.rs
+++ /dev/null
@@ -1,117 +0,0 @@
-use hir::db::HirDatabase;
-use ra_syntax::{
-    algo::non_trivia_sibling,
-    ast::{self, AstNode},
-    Direction, T,
-};
-
-use crate::{Assist, AssistCtx, AssistId};
-
-// Assist: flip_trait_bound
-//
-// Flips two trait bounds.
-//
-// ```
-// fn foo<T: Clone +<|> Copy>() { }
-// ```
-// ->
-// ```
-// fn foo<T: Copy + Clone>() { }
-// ```
-pub(crate) fn flip_trait_bound(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
-    // We want to replicate the behavior of `flip_binexpr` by only suggesting
-    // the assist when the cursor is on a `+`
-    let plus = ctx.find_token_at_offset(T![+])?;
-
-    // Make sure we're in a `TypeBoundList`
-    if ast::TypeBoundList::cast(plus.parent()).is_none() {
-        return None;
-    }
-
-    let (before, after) = (
-        non_trivia_sibling(plus.clone().into(), Direction::Prev)?,
-        non_trivia_sibling(plus.clone().into(), Direction::Next)?,
-    );
-
-    ctx.add_assist(AssistId("flip_trait_bound"), "flip trait bound", |edit| {
-        edit.target(plus.text_range());
-        edit.replace(before.text_range(), after.to_string());
-        edit.replace(after.text_range(), before.to_string());
-    })
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
-
-    #[test]
-    fn flip_trait_bound_assist_available() {
-        check_assist_target(flip_trait_bound, "struct S<T> where T: A <|>+ B + C { }", "+")
-    }
-
-    #[test]
-    fn flip_trait_bound_not_applicable_for_single_trait_bound() {
-        check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: <|>A { }")
-    }
-
-    #[test]
-    fn flip_trait_bound_works_for_struct() {
-        check_assist(
-            flip_trait_bound,
-            "struct S<T> where T: A <|>+ B { }",
-            "struct S<T> where T: B <|>+ A { }",
-        )
-    }
-
-    #[test]
-    fn flip_trait_bound_works_for_trait_impl() {
-        check_assist(
-            flip_trait_bound,
-            "impl X for S<T> where T: A +<|> B { }",
-            "impl X for S<T> where T: B +<|> A { }",
-        )
-    }
-
-    #[test]
-    fn flip_trait_bound_works_for_fn() {
-        check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B <|>+ A>(t: T) { }")
-    }
-
-    #[test]
-    fn flip_trait_bound_works_for_fn_where_clause() {
-        check_assist(
-            flip_trait_bound,
-            "fn f<T>(t: T) where T: A +<|> B { }",
-            "fn f<T>(t: T) where T: B +<|> A { }",
-        )
-    }
-
-    #[test]
-    fn flip_trait_bound_works_for_lifetime() {
-        check_assist(
-            flip_trait_bound,
-            "fn f<T>(t: T) where T: A <|>+ 'static { }",
-            "fn f<T>(t: T) where T: 'static <|>+ A { }",
-        )
-    }
-
-    #[test]
-    fn flip_trait_bound_works_for_complex_bounds() {
-        check_assist(
-            flip_trait_bound,
-            "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }",
-            "struct S<T> where T: b_mod::B<T> <|>+ A<T> + C<T> { }",
-        )
-    }
-
-    #[test]
-    fn flip_trait_bound_works_for_long_bounds() {
-        check_assist(
-            flip_trait_bound,
-            "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }",
-            "struct S<T> where T: A + B + C + D + E + G +<|> F + H + I + J { }",
-        )
-    }
-}
diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/assists/inline_local_variable.rs
index f439105748..fe8fa2a868 100644
--- a/crates/ra_assists/src/assists/inline_local_variable.rs
+++ b/crates/ra_assists/src/assists/inline_local_variable.rs
@@ -23,7 +23,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     (1 + 2) * 4;
 // }
 // ```
-pub(crate) fn inline_local_varialbe(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
     let bind_pat = match let_stmt.pat()? {
         ast::Pat::BindPat(pat) => pat,
@@ -93,7 +93,7 @@ pub(crate) fn inline_local_varialbe(ctx: AssistCtx<impl HirDatabase>) -> Option<
     let init_str = initializer_expr.syntax().text().to_string();
     let init_in_paren = format!("({})", &init_str);
 
-    ctx.add_assist(
+    ctx.add_action(
         AssistId("inline_local_variable"),
         "inline local variable",
         move |edit: &mut AssistBuilder| {
@@ -107,7 +107,9 @@ pub(crate) fn inline_local_varialbe(ctx: AssistCtx<impl HirDatabase>) -> Option<
             }
             edit.set_cursor(delete_range.start())
         },
-    )
+    );
+
+    ctx.build()
 }
 
 #[cfg(test)]
diff --git a/crates/ra_assists/src/assists/introduce_variable.rs b/crates/ra_assists/src/assists/introduce_variable.rs
index 0623d44759..8245dc99f5 100644
--- a/crates/ra_assists/src/assists/introduce_variable.rs
+++ b/crates/ra_assists/src/assists/introduce_variable.rs
@@ -28,7 +28,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     var_name * 4;
 // }
 // ```
-pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     if ctx.frange.range.is_empty() {
         return None;
     }
@@ -43,7 +43,7 @@ pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Ass
     if indent.kind() != WHITESPACE {
         return None;
     }
-    ctx.add_assist(AssistId("introduce_variable"), "introduce variable", move |edit| {
+    ctx.add_action(AssistId("introduce_variable"), "introduce variable", move |edit| {
         let mut buf = String::new();
 
         let cursor_offset = if wrap_in_block {
@@ -88,7 +88,9 @@ pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Ass
             }
         }
         edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset);
-    })
+    });
+
+    ctx.build()
 }
 
 /// Check whether the node is a valid expression which can be extracted to a variable.
diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs
index e9f2cae91d..b0c4ee78b5 100644
--- a/crates/ra_assists/src/assists/merge_match_arms.rs
+++ b/crates/ra_assists/src/assists/merge_match_arms.rs
@@ -26,7 +26,7 @@ use ra_syntax::ast::{AstNode, MatchArm};
 //     }
 // }
 // ```
-pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let current_arm = ctx.find_node_at_offset::<MatchArm>()?;
 
     // We check if the following match arm matches this one. We could, but don't,
@@ -52,7 +52,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assis
 
     let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start();
 
-    ctx.add_assist(AssistId("merge_match_arms"), "merge match arms", |edit| {
+    ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| {
         fn contains_placeholder(a: &MatchArm) -> bool {
             a.pats().any(|x| match x {
                 ra_syntax::ast::Pat::PlaceholderPat(..) => true,
@@ -80,7 +80,9 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assis
         edit.target(current_arm.syntax().text_range());
         edit.replace(TextRange::from_to(start, end), arm);
         edit.set_cursor(start + offset);
-    })
+    });
+
+    ctx.build()
 }
 
 #[cfg(test)]
diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs
index 3145d7625d..edf2897c6c 100644
--- a/crates/ra_assists/src/assists/move_bounds.rs
+++ b/crates/ra_assists/src/assists/move_bounds.rs
@@ -22,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     f(x)
 // }
 // ```
-pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?;
 
     let mut type_params = type_param_list.type_params();
@@ -46,30 +46,38 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx<impl HirDatabase>) -> O
         _ => return None,
     };
 
-    ctx.add_assist(AssistId("move_bounds_to_where_clause"), "move_bounds_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 = edit::replace_descendants(&type_param_list, 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());
-    })
+    ctx.add_action(
+        AssistId("move_bounds_to_where_clause"),
+        "move_bounds_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 = edit::replace_descendants(&type_param_list, 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());
+        },
+    );
+
+    ctx.build()
 }
 
 fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
diff --git a/crates/ra_assists/src/assists/move_guard.rs b/crates/ra_assists/src/assists/move_guard.rs
index b49ec6172a..e820a73c8d 100644
--- a/crates/ra_assists/src/assists/move_guard.rs
+++ b/crates/ra_assists/src/assists/move_guard.rs
@@ -32,7 +32,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     }
 // }
 // ```
-pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
     let guard = match_arm.guard()?;
     let space_before_guard = guard.syntax().prev_sibling_or_token();
@@ -41,7 +41,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx<impl HirDatabase>) -> Option
     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| {
+    ctx.add_action(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| {
         edit.target(guard.syntax().text_range());
         let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) {
             Some(tok) => {
@@ -61,7 +61,8 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx<impl HirDatabase>) -> Option
         edit.set_cursor(
             arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount,
         );
-    })
+    });
+    ctx.build()
 }
 
 // Assist: move_arm_cond_to_match_guard
@@ -89,7 +90,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx<impl HirDatabase>) -> Option
 //     }
 // }
 // ```
-pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
     let last_match_pat = match_arm.pats().last()?;
 
@@ -109,7 +110,7 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx<impl HirDatabase>) ->
 
     let buf = format!(" if {}", cond.syntax().text());
 
-    ctx.add_assist(
+    ctx.add_action(
         AssistId("move_arm_cond_to_match_guard"),
         "move condition to match guard",
         |edit| {
@@ -126,7 +127,8 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx<impl HirDatabase>) ->
             edit.insert(last_match_pat.syntax().text_range().end(), buf);
             edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1));
         },
-    )
+    );
+    ctx.build()
 }
 
 #[cfg(test)]
diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs
index 86bbaeeeff..cb3a1e6e92 100644
--- a/crates/ra_assists/src/assists/raw_string.rs
+++ b/crates/ra_assists/src/assists/raw_string.rs
@@ -22,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     r#"Hello, World!"#;
 // }
 // ```
-pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let token = ctx.find_token_at_offset(STRING)?;
     let text = token.text().as_str();
     let usual_string_range = find_usual_string_range(text)?;
@@ -41,7 +41,7 @@ pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
     if error.is_err() {
         return None;
     }
-    ctx.add_assist(AssistId("make_raw_string"), "make raw string", |edit| {
+    ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| {
         edit.target(token.text_range());
         let max_hash_streak = count_hashes(&unescaped);
         let mut hashes = String::with_capacity(max_hash_streak + 1);
@@ -49,7 +49,8 @@ pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
             hashes.push('#');
         }
         edit.replace(token.text_range(), format!("r{}\"{}\"{}", hashes, unescaped, hashes));
-    })
+    });
+    ctx.build()
 }
 
 // Assist: make_usual_string
@@ -67,11 +68,11 @@ pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
 //     "Hello, \"World!\"";
 // }
 // ```
-pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let token = ctx.find_token_at_offset(RAW_STRING)?;
     let text = token.text().as_str();
     let usual_string_range = find_usual_string_range(text)?;
-    ctx.add_assist(AssistId("make_usual_string"), "make usual string", |edit| {
+    ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| {
         edit.target(token.text_range());
         // parse inside string to escape `"`
         let start_of_inside = usual_string_range.start().to_usize() + 1;
@@ -79,7 +80,8 @@ pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assi
         let inside_str = &text[start_of_inside..end_of_inside];
         let escaped = inside_str.escape_default().to_string();
         edit.replace(token.text_range(), format!("\"{}\"", escaped));
-    })
+    });
+    ctx.build()
 }
 
 // Assist: add_hash
@@ -97,13 +99,14 @@ pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assi
 //     r##"Hello, World!"##;
 // }
 // ```
-pub(crate) fn add_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let token = ctx.find_token_at_offset(RAW_STRING)?;
-    ctx.add_assist(AssistId("add_hash"), "add hash to raw string", |edit| {
+    ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| {
         edit.target(token.text_range());
         edit.insert(token.text_range().start() + TextUnit::of_char('r'), "#");
         edit.insert(token.text_range().end(), "#");
-    })
+    });
+    ctx.build()
 }
 
 // Assist: remove_hash
@@ -121,14 +124,14 @@ pub(crate) fn add_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
 //     r"Hello, World!";
 // }
 // ```
-pub(crate) fn remove_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let token = ctx.find_token_at_offset(RAW_STRING)?;
     let text = token.text().as_str();
     if text.starts_with("r\"") {
         // no hash to remove
         return None;
     }
-    ctx.add_assist(AssistId("remove_hash"), "remove hash from raw string", |edit| {
+    ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| {
         edit.target(token.text_range());
         let result = &text[2..text.len() - 1];
         let result = if result.starts_with("\"") {
@@ -139,7 +142,8 @@ pub(crate) fn remove_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
             result.to_owned()
         };
         edit.replace(token.text_range(), format!("r{}", result));
-    })
+    });
+    ctx.build()
 }
 
 fn count_hashes(s: &str) -> usize {
@@ -155,10 +159,17 @@ fn count_hashes(s: &str) -> usize {
 }
 
 fn find_usual_string_range(s: &str) -> Option<TextRange> {
-    Some(TextRange::from_to(
-        TextUnit::from(s.find('"')? as u32),
-        TextUnit::from(s.rfind('"')? as u32),
-    ))
+    let left_quote = s.find('"')?;
+    let right_quote = s.rfind('"')?;
+    if left_quote == right_quote {
+        // `s` only contains one quote
+        None
+    } else {
+        Some(TextRange::from_to(
+            TextUnit::from(left_quote as u32),
+            TextUnit::from(right_quote as u32),
+        ))
+    }
 }
 
 #[cfg(test)]
@@ -267,6 +278,30 @@ string"###;
         )
     }
 
+    #[test]
+    fn make_raw_string_not_works_on_partial_string() {
+        check_assist_not_applicable(
+            make_raw_string,
+            r#"
+            fn f() {
+                let s = "foo<|>
+            }
+            "#,
+        )
+    }
+
+    #[test]
+    fn make_usual_string_not_works_on_partial_string() {
+        check_assist_not_applicable(
+            make_usual_string,
+            r#"
+            fn main() {
+                let s = r#"bar<|>
+            }
+            "#,
+        )
+    }
+
     #[test]
     fn add_hash_target() {
         check_assist_target(
diff --git a/crates/ra_assists/src/assists/remove_dbg.rs b/crates/ra_assists/src/assists/remove_dbg.rs
index aedf8747f2..44b8de8145 100644
--- a/crates/ra_assists/src/assists/remove_dbg.rs
+++ b/crates/ra_assists/src/assists/remove_dbg.rs
@@ -21,7 +21,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     92;
 // }
 // ```
-pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
 
     if !is_valid_macrocall(&macro_call, "dbg")? {
@@ -58,11 +58,13 @@ pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
         text.slice(without_parens).to_string()
     };
 
-    ctx.add_assist(AssistId("remove_dbg"), "remove dbg!()", |edit| {
+    ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| {
         edit.target(macro_call.syntax().text_range());
         edit.replace(macro_range, macro_content);
         edit.set_cursor(cursor_pos);
-    })
+    });
+
+    ctx.build()
 }
 
 /// Verifies that the given macro_call actually matches the given name
diff --git a/crates/ra_assists/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/assists/replace_if_let_with_match.rs
index dff84d8651..58ef2ff206 100644
--- a/crates/ra_assists/src/assists/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/assists/replace_if_let_with_match.rs
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId};
 //     }
 // }
 // ```
-pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
     let cond = if_expr.condition()?;
     let pat = cond.pat()?;
@@ -42,12 +42,14 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Opt
         ast::ElseBranch::IfExpr(_) => return None,
     };
 
-    ctx.add_assist(AssistId("replace_if_let_with_match"), "replace with match", |edit| {
+    ctx.add_action(AssistId("replace_if_let_with_match"), "replace with match", |edit| {
         let match_expr = build_match_expr(expr, pat, then_block, else_block);
         edit.target(if_expr.syntax().text_range());
         edit.replace_node_and_indent(if_expr.syntax(), match_expr);
         edit.set_cursor(if_expr.syntax().text_range().start())
-    })
+    });
+
+    ctx.build()
 }
 
 fn build_match_expr(
diff --git a/crates/ra_assists/src/assists/split_import.rs b/crates/ra_assists/src/assists/split_import.rs
index 5f8d6b0be1..8d8a289878 100644
--- a/crates/ra_assists/src/assists/split_import.rs
+++ b/crates/ra_assists/src/assists/split_import.rs
@@ -16,7 +16,7 @@ use crate::{Assist, AssistCtx, AssistId};
 // ```
 // use std::{collections::HashMap};
 // ```
-pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let colon_colon = ctx.find_token_at_offset(T![::])?;
     let path = ast::Path::cast(colon_colon.parent())?;
     let top_path = successors(Some(path), |it| it.parent_path()).last()?;
@@ -32,12 +32,14 @@ pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
         None => top_path.syntax().text_range().end(),
     };
 
-    ctx.add_assist(AssistId("split_import"), "split import", |edit| {
+    ctx.add_action(AssistId("split_import"), "split import", |edit| {
         edit.target(colon_colon.text_range());
         edit.insert(l_curly, "{");
         edit.insert(r_curly, "}");
         edit.set_cursor(l_curly + TextUnit::of_str("{"));
-    })
+    });
+
+    ctx.build()
 }
 
 #[cfg(test)]
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 38599d4f17..de576324fb 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -11,12 +11,13 @@ mod marks;
 mod doc_tests;
 
 use hir::db::HirDatabase;
+use itertools::Itertools;
 use ra_db::FileRange;
 use ra_syntax::{TextRange, TextUnit};
 use ra_text_edit::TextEdit;
 
 pub(crate) use crate::assist_ctx::{Assist, AssistCtx};
-pub use crate::assists::add_import::auto_import_text_edit;
+pub use crate::assists::auto_import::auto_import_text_edit;
 
 /// Unique identifier of the assist, should not be shown to the user
 /// directly.
@@ -50,10 +51,10 @@ where
             .iter()
             .filter_map(|f| f(ctx.clone()))
             .map(|a| match a {
-                Assist::Unresolved { label } => label,
-                Assist::Resolved { .. } => unreachable!(),
+                Assist::Unresolved(labels) => labels,
+                Assist::Resolved(..) => unreachable!(),
             })
-            .collect()
+            .concat()
     })
 }
 
@@ -72,10 +73,10 @@ where
             .iter()
             .filter_map(|f| f(ctx.clone()))
             .map(|a| match a {
-                Assist::Resolved { label, action } => (label, action),
-                Assist::Unresolved { .. } => unreachable!(),
+                Assist::Resolved(labels_actions) => labels_actions,
+                Assist::Unresolved(..) => unreachable!(),
             })
-            .collect::<Vec<_>>();
+            .concat();
         a.sort_by(|a, b| match (a.1.target, b.1.target) {
             (Some(a), Some(b)) => a.len().cmp(&b.len()),
             (Some(_), None) => Ordering::Less,
@@ -96,7 +97,6 @@ mod assists {
     mod apply_demorgan;
     mod flip_comma;
     mod flip_binexpr;
-    mod flip_trait_bound;
     mod change_visibility;
     mod fill_match_arms;
     mod merge_match_arms;
@@ -106,7 +106,7 @@ mod assists {
     mod replace_if_let_with_match;
     mod split_import;
     mod remove_dbg;
-    pub(crate) mod add_import;
+    pub(crate) mod auto_import;
     mod add_missing_impl_members;
     mod move_guard;
     mod move_bounds;
@@ -123,12 +123,11 @@ mod assists {
             merge_match_arms::merge_match_arms,
             flip_comma::flip_comma,
             flip_binexpr::flip_binexpr,
-            flip_trait_bound::flip_trait_bound,
             introduce_variable::introduce_variable,
             replace_if_let_with_match::replace_if_let_with_match,
             split_import::split_import,
             remove_dbg::remove_dbg,
-            add_import::add_import,
+            auto_import::auto_import,
             add_missing_impl_members::add_missing_impl_members,
             add_missing_impl_members::add_missing_default_members,
             inline_local_variable::inline_local_varialbe,
@@ -157,6 +156,39 @@ mod helpers {
         assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
         before: &str,
         after: &str,
+    ) {
+        check_assist_nth_action(assist, before, after, 0)
+    }
+
+    pub(crate) fn check_assist_range(
+        assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
+        before: &str,
+        after: &str,
+    ) {
+        check_assist_range_nth_action(assist, before, after, 0)
+    }
+
+    pub(crate) fn check_assist_target(
+        assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
+        before: &str,
+        target: &str,
+    ) {
+        check_assist_target_nth_action(assist, before, target, 0)
+    }
+
+    pub(crate) fn check_assist_range_target(
+        assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
+        before: &str,
+        target: &str,
+    ) {
+        check_assist_range_target_nth_action(assist, before, target, 0)
+    }
+
+    pub(crate) fn check_assist_nth_action(
+        assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
+        before: &str,
+        after: &str,
+        index: usize,
     ) {
         let (before_cursor_pos, before) = extract_offset(before);
         let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
@@ -164,11 +196,12 @@ mod helpers {
             FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
         let assist =
             AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
-        let action = match assist {
-            Assist::Unresolved { .. } => unreachable!(),
-            Assist::Resolved { action, .. } => action,
+        let labels_actions = match assist {
+            Assist::Unresolved(_) => unreachable!(),
+            Assist::Resolved(labels_actions) => labels_actions,
         };
 
+        let (_, action) = labels_actions.get(index).expect("expect assist action at index");
         let actual = action.edit.apply(&before);
         let actual_cursor_pos = match action.cursor_position {
             None => action
@@ -181,21 +214,23 @@ mod helpers {
         assert_eq_text!(after, &actual);
     }
 
-    pub(crate) fn check_assist_range(
+    pub(crate) fn check_assist_range_nth_action(
         assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
         before: &str,
         after: &str,
+        index: usize,
     ) {
         let (range, before) = extract_range(before);
         let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
         let frange = FileRange { file_id, range };
         let assist =
             AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
-        let action = match assist {
-            Assist::Unresolved { .. } => unreachable!(),
-            Assist::Resolved { action, .. } => action,
+        let labels_actions = match assist {
+            Assist::Unresolved(_) => unreachable!(),
+            Assist::Resolved(labels_actions) => labels_actions,
         };
 
+        let (_, action) = labels_actions.get(index).expect("expect assist action at index");
         let mut actual = action.edit.apply(&before);
         if let Some(pos) = action.cursor_position {
             actual = add_cursor(&actual, pos);
@@ -203,10 +238,11 @@ mod helpers {
         assert_eq_text!(after, &actual);
     }
 
-    pub(crate) fn check_assist_target(
+    pub(crate) fn check_assist_target_nth_action(
         assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
         before: &str,
         target: &str,
+        index: usize,
     ) {
         let (before_cursor_pos, before) = extract_offset(before);
         let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
@@ -214,30 +250,33 @@ mod helpers {
             FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
         let assist =
             AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
-        let action = match assist {
-            Assist::Unresolved { .. } => unreachable!(),
-            Assist::Resolved { action, .. } => action,
+        let labels_actions = match assist {
+            Assist::Unresolved(_) => unreachable!(),
+            Assist::Resolved(labels_actions) => labels_actions,
         };
 
+        let (_, action) = labels_actions.get(index).expect("expect assist action at index");
         let range = action.target.expect("expected target on action");
         assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
     }
 
-    pub(crate) fn check_assist_range_target(
+    pub(crate) fn check_assist_range_target_nth_action(
         assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
         before: &str,
         target: &str,
+        index: usize,
     ) {
         let (range, before) = extract_range(before);
         let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
         let frange = FileRange { file_id, range };
         let assist =
             AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
-        let action = match assist {
-            Assist::Unresolved { .. } => unreachable!(),
-            Assist::Resolved { action, .. } => action,
+        let labels_actions = match assist {
+            Assist::Unresolved(_) => unreachable!(),
+            Assist::Resolved(labels_actions) => labels_actions,
         };
 
+        let (_, action) = labels_actions.get(index).expect("expect assist action at index");
         let range = action.target.expect("expected target on action");
         assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
     }
diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs
index 4b7bfc0b11..602757e92e 100644
--- a/crates/ra_ide_api/src/extend_selection.rs
+++ b/crates/ra_ide_api/src/extend_selection.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
     algo::find_covering_element,
     ast::{self, AstNode, AstToken},
     Direction, NodeOrToken,
-    SyntaxKind::{self, *},
+    SyntaxKind::*,
     SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T,
 };
 
@@ -29,12 +29,10 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
         USE_TREE_LIST,
         TYPE_PARAM_LIST,
         TYPE_ARG_LIST,
-        TYPE_BOUND_LIST,
         PARAM_LIST,
         ARG_LIST,
         ARRAY_EXPR,
         TUPLE_EXPR,
-        WHERE_CLAUSE,
     ];
 
     if range.is_empty() {
@@ -148,17 +146,13 @@ fn pick_best<'a>(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
     }
 }
 
-/// Extend list item selection to include nearby delimiter and whitespace.
+/// Extend list item selection to include nearby comma and whitespace.
 fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
     fn is_single_line_ws(node: &SyntaxToken) -> bool {
         node.kind() == WHITESPACE && !node.text().contains('\n')
     }
 
-    fn nearby_delimiter(
-        delimiter_kind: SyntaxKind,
-        node: &SyntaxNode,
-        dir: Direction,
-    ) -> Option<SyntaxToken> {
+    fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<SyntaxToken> {
         node.siblings_with_tokens(dir)
             .skip(1)
             .skip_while(|node| match node {
@@ -167,26 +161,19 @@ fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
             })
             .next()
             .and_then(|it| it.into_token())
-            .filter(|node| node.kind() == delimiter_kind)
+            .filter(|node| node.kind() == T![,])
     }
 
-    let delimiter = match node.kind() {
-        TYPE_BOUND => T![+],
-        _ => T![,],
-    };
-    if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) {
-        return Some(TextRange::from_to(
-            delimiter_node.text_range().start(),
-            node.text_range().end(),
-        ));
+    if let Some(comma_node) = nearby_comma(node, Direction::Prev) {
+        return Some(TextRange::from_to(comma_node.text_range().start(), node.text_range().end()));
     }
-    if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) {
-        // Include any following whitespace when delimiter is after list item.
-        let final_node = delimiter_node
+    if let Some(comma_node) = nearby_comma(node, Direction::Next) {
+        // Include any following whitespace when comma if after list item.
+        let final_node = comma_node
             .next_sibling_or_token()
             .and_then(|it| it.into_token())
             .filter(|node| is_single_line_ws(node))
-            .unwrap_or(delimiter_node);
+            .unwrap_or(comma_node);
 
         return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end()));
     }
@@ -400,53 +387,4 @@ fn bar(){}
             &["foo", "\" fn foo() {\""],
         );
     }
-
-    #[test]
-    fn test_extend_trait_bounds_list_in_where_clause() {
-        do_check(
-            r#"
-fn foo<R>() 
-    where 
-        R: req::Request + 'static,
-        R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static,
-        R::Result: Serialize + 'static,
-"#,
-            &[
-                "DeserializeOwned",
-                "DeserializeOwned + ",
-                "DeserializeOwned + panic::UnwindSafe + 'static",
-                "R::Params: DeserializeOwned + panic::UnwindSafe + 'static",
-                "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,",
-            ],
-        );
-        do_check(r#"fn foo<T>() where T: <|>Copy"#, &["Copy"]);
-        do_check(r#"fn foo<T>() where T: <|>Copy + Display"#, &["Copy", "Copy + "]);
-        do_check(r#"fn foo<T>() where T: <|>Copy +Display"#, &["Copy", "Copy +"]);
-        do_check(r#"fn foo<T>() where T: <|>Copy+Display"#, &["Copy", "Copy+"]);
-        do_check(r#"fn foo<T>() where T: Copy + <|>Display"#, &["Display", "+ Display"]);
-        do_check(r#"fn foo<T>() where T: Copy + <|>Display + Sync"#, &["Display", "+ Display"]);
-        do_check(r#"fn foo<T>() where T: Copy +<|>Display"#, &["Display", "+Display"]);
-    }
-
-    #[test]
-    fn test_extend_trait_bounds_list_inline() {
-        do_check(r#"fn foo<T: <|>Copy>() {}"#, &["Copy"]);
-        do_check(r#"fn foo<T: <|>Copy + Display>() {}"#, &["Copy", "Copy + "]);
-        do_check(r#"fn foo<T: <|>Copy +Display>() {}"#, &["Copy", "Copy +"]);
-        do_check(r#"fn foo<T: <|>Copy+Display>() {}"#, &["Copy", "Copy+"]);
-        do_check(r#"fn foo<T: Copy + <|>Display>() {}"#, &["Display", "+ Display"]);
-        do_check(r#"fn foo<T: Copy + <|>Display + Sync>() {}"#, &["Display", "+ Display"]);
-        do_check(r#"fn foo<T: Copy +<|>Display>() {}"#, &["Display", "+Display"]);
-        do_check(
-            r#"fn foo<T: Copy<|> + Display, U: Copy>() {}"#,
-            &[
-                "Copy",
-                "Copy + ",
-                "Copy + Display",
-                "T: Copy + Display",
-                "T: Copy + Display, ",
-                "<T: Copy + Display, U: Copy>",
-            ],
-        );
-    }
 }
diff --git a/docs/user/assists.md b/docs/user/assists.md
index 303353e742..e4d08a7dca 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -136,20 +136,6 @@ impl T for () {
 }
 ```
 
-## `add_import`
-
-Adds a use statement for a given fully-qualified path.
-
-```rust
-// BEFORE
-fn process(map: std::collections::┃HashMap<String, String>) {}
-
-// AFTER
-use std::collections::HashMap;
-
-fn process(map: HashMap<String, String>) {}
-```
-
 ## `apply_demorgan`
 
 Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws).
@@ -262,18 +248,6 @@ fn main() {
 }
 ```
 
-## `flip_trait_bound`
-
-Flips two trait bounds.
-
-```rust
-// BEFORE
-fn foo<T: Clone +┃ Copy>() { }
-
-// AFTER
-fn foo<T: Copy + Clone>() { }
-```
-
 ## `inline_local_variable`
 
 Inlines local variable.
diff --git a/docs/user/features.md b/docs/user/features.md
index c160dd70bd..7ae2ca7b6e 100644
--- a/docs/user/features.md
+++ b/docs/user/features.md
@@ -99,10 +99,24 @@ Stop `cargo watch`
 
 ### Assists (Code Actions)
 
-Assists, or code actions, are small local refactorings, available in a particular context.
-They are usually triggered by a shortcut or by clicking a light bulb icon in the editor.
+These are triggered in a particular context via light bulb. We use custom code on
+the VS Code side to be able to position cursor. `<|>` signifies cursor
 
-See [assists.md](./assists.md) for the list of available assists.
+See [assists.md](./assists.md)
+
+- Import path
+
+```rust
+// before:
+impl std::fmt::Debug<|> for Foo {
+}
+
+// after:
+use std::fmt::Debug;
+
+impl Debug<|> for Foo {
+}
+```
 
 ### Magic Completions
 


Test patch:
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs
index 6e1e3de843..0ccf9d7308 100644
--- a/crates/ra_assists/src/doc_tests.rs
+++ b/crates/ra_assists/src/doc_tests.rs
@@ -17,17 +17,7 @@ fn check(assist_id: &str, before: &str, after: &str) {
     let (_assist_id, action) = crate::assists(&db, frange)
         .into_iter()
         .find(|(id, _)| id.id.0 == assist_id)
-        .unwrap_or_else(|| {
-            panic!(
-                "\n\nAssist is not applicable: {}\nAvailable assists: {}",
-                assist_id,
-                crate::assists(&db, frange)
-                    .into_iter()
-                    .map(|(id, _)| id.id.0)
-                    .collect::<Vec<_>>()
-                    .join(", ")
-            )
-        });
+        .unwrap_or_else(|| panic!("Assist {:?} is not applicable", assist_id));
 
     let actual = action.edit.apply(&before);
     assert_eq_text!(after, &actual);
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index 1bee76f59a..b8d335911f 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -141,21 +141,6 @@ impl T for () {
     )
 }
 
-#[test]
-fn doctest_add_import() {
-    check(
-        "add_import",
-        r#####"
-fn process(map: std::collections::<|>HashMap<String, String>) {}
-"#####,
-        r#####"
-use std::collections::HashMap;
-
-fn process(map: HashMap<String, String>) {}
-"#####,
-    )
-}
-
 #[test]
 fn doctest_apply_demorgan() {
     check(
@@ -270,19 +255,6 @@ fn main() {
     )
 }
 
-#[test]
-fn doctest_flip_trait_bound() {
-    check(
-        "flip_trait_bound",
-        r#####"
-fn foo<T: Clone +<|> Copy>() { }
-"#####,
-        r#####"
-fn foo<T: Copy + Clone>() { }
-"#####,
-    )
-}
-
 #[test]
 fn doctest_inline_local_variable() {
     check(