From 0c5fd8f7cbf04eda763e55bc9a38dad5f7ec917d Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
Date: Sun, 3 Feb 2019 21:26:35 +0300
Subject: [PATCH] move assists to a separate crate

---
 Cargo.lock                                    |  14 ++
 crates/ra_assists/Cargo.toml                  |  17 ++
 .../assists => ra_assists/src}/add_derive.rs  |   7 +-
 .../assists => ra_assists/src}/add_impl.rs    |   7 +-
 crates/ra_assists/src/assist_ctx.rs           | 154 +++++++++++++
 .../src}/change_visibility.rs                 |  11 +-
 crates/ra_assists/src/fill_match_arms.rs      | 145 ++++++++++++
 .../assists => ra_assists/src}/flip_comma.rs  |   8 +-
 .../src}/introduce_variable.rs                |   7 +-
 crates/ra_assists/src/lib.rs                  | 170 ++++++++++++++
 .../src}/replace_if_let_with_match.rs         |  11 +-
 .../src}/split_import.rs                      |   7 +-
 crates/ra_db/src/lib.rs                       |   2 +-
 crates/ra_hir/src/lib.rs                      |   3 +-
 crates/ra_hir/src/mock.rs                     |  12 +-
 crates/ra_ide_api/Cargo.toml                  |   1 +
 crates/ra_ide_api/src/assists.rs              | 105 ++-------
 .../ra_ide_api/src/assists/fill_match_arm.rs  | 157 -------------
 .../snapshots/tests__fill_match_arm1.snap     |  20 --
 .../snapshots/tests__fill_match_arm2.snap     |  20 --
 crates/ra_ide_api/src/imp.rs                  |  11 +-
 crates/ra_ide_api/src/lib.rs                  |   2 +-
 crates/ra_ide_api_light/src/assists.rs        | 215 ------------------
 crates/ra_ide_api_light/src/formatting.rs     |  10 +-
 crates/ra_ide_api_light/src/lib.rs            |  11 +-
 crates/ra_ide_api_light/src/test_utils.rs     |  31 +--
 26 files changed, 580 insertions(+), 578 deletions(-)
 create mode 100644 crates/ra_assists/Cargo.toml
 rename crates/{ra_ide_api_light/src/assists => ra_assists/src}/add_derive.rs (92%)
 rename crates/{ra_ide_api_light/src/assists => ra_assists/src}/add_impl.rs (91%)
 create mode 100644 crates/ra_assists/src/assist_ctx.rs
 rename crates/{ra_ide_api_light/src/assists => ra_assists/src}/change_visibility.rs (92%)
 create mode 100644 crates/ra_assists/src/fill_match_arms.rs
 rename crates/{ra_ide_api_light/src/assists => ra_assists/src}/flip_comma.rs (77%)
 rename crates/{ra_ide_api_light/src/assists => ra_assists/src}/introduce_variable.rs (97%)
 create mode 100644 crates/ra_assists/src/lib.rs
 rename crates/{ra_ide_api_light/src/assists => ra_assists/src}/replace_if_let_with_match.rs (88%)
 rename crates/{ra_ide_api_light/src/assists => ra_assists/src}/split_import.rs (88%)
 delete mode 100644 crates/ra_ide_api/src/assists/fill_match_arm.rs
 delete mode 100644 crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm1.snap
 delete mode 100644 crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm2.snap
 delete mode 100644 crates/ra_ide_api_light/src/assists.rs

diff --git a/Cargo.lock b/Cargo.lock
index 957190fdb56..15cd4386c59 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -912,6 +912,19 @@ dependencies = [
 name = "ra_arena"
 version = "0.1.0"
 
+[[package]]
+name = "ra_assists"
+version = "0.1.0"
+dependencies = [
+ "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ra_db 0.1.0",
+ "ra_hir 0.1.0",
+ "ra_ide_api_light 0.1.0",
+ "ra_syntax 0.1.0",
+ "ra_text_edit 0.1.0",
+ "test_utils 0.1.0",
+]
+
 [[package]]
 name = "ra_cli"
 version = "0.1.0"
@@ -970,6 +983,7 @@ dependencies = [
  "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ra_assists 0.1.0",
  "ra_db 0.1.0",
  "ra_hir 0.1.0",
  "ra_ide_api_light 0.1.0",
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
new file mode 100644
index 00000000000..20bc253e30b
--- /dev/null
+++ b/crates/ra_assists/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+edition = "2018"
+name = "ra_assists"
+version = "0.1.0"
+authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
+
+[dependencies]
+join_to_string = "0.1.3"
+
+ra_ide_api_light = { path = "../ra_ide_api_light" }
+ra_syntax = { path = "../ra_syntax" }
+ra_text_edit = { path = "../ra_text_edit" }
+ra_db = { path = "../ra_db" }
+hir = { path = "../ra_hir", package = "ra_hir" }
+
+[dev-dependencies]
+test_utils = { path = "../test_utils" }
diff --git a/crates/ra_ide_api_light/src/assists/add_derive.rs b/crates/ra_assists/src/add_derive.rs
similarity index 92%
rename from crates/ra_ide_api_light/src/assists/add_derive.rs
rename to crates/ra_assists/src/add_derive.rs
index 6e964d011bd..01a4079f68b 100644
--- a/crates/ra_ide_api_light/src/assists/add_derive.rs
+++ b/crates/ra_assists/src/add_derive.rs
@@ -1,12 +1,13 @@
+use hir::db::HirDatabase;
 use ra_syntax::{
     ast::{self, AstNode, AttrsOwner},
     SyntaxKind::{WHITESPACE, COMMENT},
     TextUnit,
 };
 
-use crate::assists::{AssistCtx, Assist};
+use crate::{AssistCtx, Assist};
 
-pub fn add_derive(ctx: AssistCtx) -> Option<Assist> {
+pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
     let node_start = derive_insertion_offset(nominal)?;
     ctx.build("add `#[derive]`", |edit| {
@@ -39,7 +40,7 @@ fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextUnit> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::assists::check_assist;
+    use crate::helpers::check_assist;
 
     #[test]
     fn add_derive_new() {
diff --git a/crates/ra_ide_api_light/src/assists/add_impl.rs b/crates/ra_assists/src/add_impl.rs
similarity index 91%
rename from crates/ra_ide_api_light/src/assists/add_impl.rs
rename to crates/ra_assists/src/add_impl.rs
index 2eda7cae208..699508f9151 100644
--- a/crates/ra_ide_api_light/src/assists/add_impl.rs
+++ b/crates/ra_assists/src/add_impl.rs
@@ -1,12 +1,13 @@
 use join_to_string::join;
+use hir::db::HirDatabase;
 use ra_syntax::{
     ast::{self, AstNode, AstToken, NameOwner, TypeParamsOwner},
     TextUnit,
 };
 
-use crate::assists::{AssistCtx, Assist};
+use crate::{AssistCtx, Assist};
 
-pub fn add_impl(ctx: AssistCtx) -> Option<Assist> {
+pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
     let name = nominal.name()?;
     ctx.build("add impl", |edit| {
@@ -42,7 +43,7 @@ pub fn add_impl(ctx: AssistCtx) -> Option<Assist> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::assists::check_assist;
+    use crate::helpers::check_assist;
 
     #[test]
     fn test_add_impl() {
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
new file mode 100644
index 00000000000..6d09bde52af
--- /dev/null
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -0,0 +1,154 @@
+use hir::db::HirDatabase;
+use ra_text_edit::TextEditBuilder;
+use ra_db::FileRange;
+use ra_syntax::{
+    SourceFile, TextRange, AstNode, TextUnit, SyntaxNode,
+    algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset},
+};
+use ra_ide_api_light::formatting::{leading_indent, reindent};
+
+use crate::{AssistLabel, AssistAction};
+
+pub(crate) enum Assist {
+    Unresolved(AssistLabel),
+    Resolved(AssistLabel, AssistAction),
+}
+
+/// `AssistCtx` allows to apply an assist or check if it could be applied.
+///
+/// Assists use a somewhat overengineered approach, given the current needs. The
+/// assists workflow consists of two phases. In the first phase, a user asks for
+/// the list of available assists. In the second phase, the user picks a
+/// particular assist and it gets applied.
+///
+/// There are two peculiarities here:
+///
+/// * first, we ideally avoid computing more things then necessary to answer
+///   "is assist applicable" in the first phase.
+/// * second, when we are applying assist, we don't have a guarantee that there
+///   weren't any changes between the point when user asked for assists and when
+///   they applied a particular assist. So, when applying assist, we need to do
+///   all the checks from scratch.
+///
+/// To avoid repeating the same code twice for both "check" and "apply"
+/// functions, we use an approach reminiscent of that of Django's function based
+/// views dealing with forms. Each assist receives a runtime parameter,
+/// `should_compute_edit`. It first check if an edit is applicable (potentially
+/// computing info required to compute the actual edit). If it is applicable,
+/// and `should_compute_edit` is `true`, it then computes the actual edit.
+///
+/// So, to implement the original assists workflow, we can first apply each edit
+/// with `should_compute_edit = false`, and then applying the selected edit
+/// again, with `should_compute_edit = true` this time.
+///
+/// Note, however, that we don't actually use such two-phase logic at the
+/// moment, because the LSP API is pretty awkward in this place, and it's much
+/// easier to just compute the edit eagerly :-)#[derive(Debug, Clone)]
+#[derive(Debug)]
+pub(crate) struct AssistCtx<'a, DB> {
+    pub(crate) db: &'a DB,
+    pub(crate) frange: FileRange,
+    source_file: &'a SourceFile,
+    should_compute_edit: bool,
+}
+
+impl<'a, DB> Clone for AssistCtx<'a, DB> {
+    fn clone(&self) -> Self {
+        AssistCtx {
+            db: self.db,
+            frange: self.frange,
+            source_file: self.source_file,
+            should_compute_edit: self.should_compute_edit,
+        }
+    }
+}
+
+impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
+    pub(crate) fn with_ctx<F, T>(db: &DB, frange: FileRange, should_compute_edit: bool, f: F) -> T
+    where
+        F: FnOnce(AssistCtx<DB>) -> T,
+    {
+        let source_file = &db.parse(frange.file_id);
+        let ctx = AssistCtx {
+            db,
+            frange,
+            source_file,
+            should_compute_edit,
+        };
+        f(ctx)
+    }
+
+    pub(crate) fn build(
+        self,
+        label: impl Into<String>,
+        f: impl FnOnce(&mut AssistBuilder),
+    ) -> Option<Assist> {
+        let label = AssistLabel {
+            label: label.into(),
+        };
+        if !self.should_compute_edit {
+            return Some(Assist::Unresolved(label));
+        }
+        let action = {
+            let mut edit = AssistBuilder::default();
+            f(&mut edit);
+            edit.build()
+        };
+        Some(Assist::Resolved(label, action))
+    }
+
+    pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> {
+        find_leaf_at_offset(self.source_file.syntax(), self.frange.range.start())
+    }
+
+    pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> {
+        find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
+    }
+    pub(crate) fn covering_node(&self) -> &'a SyntaxNode {
+        find_covering_node(self.source_file.syntax(), self.frange.range)
+    }
+}
+
+#[derive(Default)]
+pub(crate) struct AssistBuilder {
+    edit: TextEditBuilder,
+    cursor_position: Option<TextUnit>,
+}
+
+impl AssistBuilder {
+    pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
+        self.edit.replace(range, replace_with.into())
+    }
+
+    pub(crate) fn replace_node_and_indent(
+        &mut self,
+        node: &SyntaxNode,
+        replace_with: impl Into<String>,
+    ) {
+        let mut replace_with = replace_with.into();
+        if let Some(indent) = leading_indent(node) {
+            replace_with = reindent(&replace_with, indent)
+        }
+        self.replace(node.range(), replace_with)
+    }
+
+    #[allow(unused)]
+    pub(crate) fn delete(&mut self, range: TextRange) {
+        self.edit.delete(range)
+    }
+
+    pub(crate) fn insert(&mut self, offset: TextUnit, text: impl Into<String>) {
+        self.edit.insert(offset, text.into())
+    }
+
+    pub(crate) fn set_cursor(&mut self, offset: TextUnit) {
+        self.cursor_position = Some(offset)
+    }
+
+    fn build(self) -> AssistAction {
+        AssistAction {
+            edit: self.edit.finish(),
+            cursor_position: self.cursor_position,
+        }
+    }
+}
diff --git a/crates/ra_ide_api_light/src/assists/change_visibility.rs b/crates/ra_assists/src/change_visibility.rs
similarity index 92%
rename from crates/ra_ide_api_light/src/assists/change_visibility.rs
rename to crates/ra_assists/src/change_visibility.rs
index 6e8bc263241..4cd32985e09 100644
--- a/crates/ra_ide_api_light/src/assists/change_visibility.rs
+++ b/crates/ra_assists/src/change_visibility.rs
@@ -1,19 +1,20 @@
+use hir::db::HirDatabase;
 use ra_syntax::{
     AstNode, SyntaxNode, TextUnit,
     ast::{self, VisibilityOwner, NameOwner},
     SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT, WHITESPACE, COMMENT, ATTR},
 };
 
-use crate::assists::{AssistCtx, Assist};
+use crate::{AssistCtx, Assist};
 
-pub fn change_visibility(ctx: AssistCtx) -> Option<Assist> {
+pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() {
         return change_vis(ctx, vis);
     }
     add_vis(ctx)
 }
 
-fn add_vis(ctx: AssistCtx) -> Option<Assist> {
+fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() {
         FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
         _ => false,
@@ -57,7 +58,7 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit {
         .unwrap_or(node.range().start())
 }
 
-fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option<Assist> {
+fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> {
     if vis.syntax().text() == "pub" {
         return ctx.build("chage to pub(crate)", |edit| {
             edit.replace(vis.syntax().range(), "pub(crate)");
@@ -76,7 +77,7 @@ fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option<Assist> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::assists::check_assist;
+    use crate::helpers::check_assist;
 
     #[test]
     fn change_visibility_adds_pub_crate_to_items() {
diff --git a/crates/ra_assists/src/fill_match_arms.rs b/crates/ra_assists/src/fill_match_arms.rs
new file mode 100644
index 00000000000..9aa37d94cdb
--- /dev/null
+++ b/crates/ra_assists/src/fill_match_arms.rs
@@ -0,0 +1,145 @@
+use std::fmt::Write;
+
+use hir::{
+    AdtDef, Ty, FieldSource, source_binder,
+    db::HirDatabase,
+};
+use ra_syntax::ast::{self, AstNode};
+
+use crate::{AssistCtx, Assist};
+
+pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+    let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?;
+
+    // We already have some match arms, so we don't provide any assists.
+    match match_expr.match_arm_list() {
+        Some(arm_list) if arm_list.arms().count() > 0 => {
+            return None;
+        }
+        _ => {}
+    }
+
+    let expr = match_expr.expr()?;
+    let function =
+        source_binder::function_from_child_node(ctx.db, ctx.frange.file_id, expr.syntax())?;
+    let infer_result = function.infer(ctx.db);
+    let syntax_mapping = function.body_syntax_mapping(ctx.db);
+    let node_expr = syntax_mapping.node_expr(expr)?;
+    let match_expr_ty = infer_result[node_expr].clone();
+    let enum_def = match match_expr_ty {
+        Ty::Adt {
+            def_id: AdtDef::Enum(e),
+            ..
+        } => e,
+        _ => return None,
+    };
+    let enum_name = enum_def.name(ctx.db)?;
+    let db = ctx.db;
+
+    ctx.build("fill match arms", |edit| {
+        let mut buf = format!("match {} {{\n", expr.syntax().text().to_string());
+        let variants = enum_def.variants(db);
+        for variant in variants {
+            let name = match variant.name(db) {
+                Some(it) => it,
+                None => continue,
+            };
+            write!(&mut buf, "    {}::{}", enum_name, name.to_string()).unwrap();
+
+            let pat = variant
+                .fields(db)
+                .into_iter()
+                .map(|field| {
+                    let name = field.name(db).to_string();
+                    let (_, source) = field.source(db);
+                    match source {
+                        FieldSource::Named(_) => name,
+                        FieldSource::Pos(_) => "_".to_string(),
+                    }
+                })
+                .collect::<Vec<_>>();
+
+            match pat.first().map(|s| s.as_str()) {
+                Some("_") => write!(&mut buf, "({})", pat.join(", ")).unwrap(),
+                Some(_) => write!(&mut buf, "{{{}}}", pat.join(", ")).unwrap(),
+                None => (),
+            };
+
+            buf.push_str(" => (),\n");
+        }
+        buf.push_str("}");
+        edit.set_cursor(expr.syntax().range().start());
+        edit.replace_node_and_indent(match_expr.syntax(), buf);
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::helpers::check_assist;
+
+    use super::fill_match_arms;
+
+    #[test]
+    fn fill_match_arms_empty_body() {
+        check_assist(
+            fill_match_arms,
+            r#"
+            enum A {
+                As,
+                Bs,
+                Cs(String),
+                Ds(String, String),
+                Es{x: usize, y: usize}
+            }
+
+            fn main() {
+                let a = A::As;
+                match a<|> {}
+            }
+            "#,
+            r#"
+            enum A {
+                As,
+                Bs,
+                Cs(String),
+                Ds(String, String),
+                Es{x: usize, y: usize}
+            }
+
+            fn main() {
+                let a = A::As;
+                match <|>a {
+                    A::As => (),
+                    A::Bs => (),
+                    A::Cs(_) => (),
+                    A::Ds(_, _) => (),
+                    A::Es{x, y} => (),
+                }
+            }
+            "#,
+        );
+    }
+    #[test]
+    fn fill_match_arms_no_body() {
+        check_assist(
+            fill_match_arms,
+            r#"
+            enum E { X, Y}
+
+            fn main() {
+                match E::X<|>
+            }
+            "#,
+            r#"
+            enum E { X, Y}
+
+            fn main() {
+                match <|>E::X {
+                    E::X => (),
+                    E::Y => (),
+                }
+            }
+            "#,
+        );
+    }
+}
diff --git a/crates/ra_ide_api_light/src/assists/flip_comma.rs b/crates/ra_assists/src/flip_comma.rs
similarity index 77%
rename from crates/ra_ide_api_light/src/assists/flip_comma.rs
rename to crates/ra_assists/src/flip_comma.rs
index a343413cc57..a49820c2957 100644
--- a/crates/ra_ide_api_light/src/assists/flip_comma.rs
+++ b/crates/ra_assists/src/flip_comma.rs
@@ -1,11 +1,12 @@
+use hir::db::HirDatabase;
 use ra_syntax::{
     Direction,
     SyntaxKind::COMMA,
 };
 
-use crate::assists::{non_trivia_sibling, AssistCtx, Assist};
+use crate::{AssistCtx, Assist, non_trivia_sibling};
 
-pub fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
+pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?;
     let prev = non_trivia_sibling(comma, Direction::Prev)?;
     let next = non_trivia_sibling(comma, Direction::Next)?;
@@ -18,7 +19,8 @@ pub fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::assists::check_assist;
+
+    use crate::helpers::check_assist;
 
     #[test]
     fn flip_comma_works_for_function_parameters() {
diff --git a/crates/ra_ide_api_light/src/assists/introduce_variable.rs b/crates/ra_assists/src/introduce_variable.rs
similarity index 97%
rename from crates/ra_ide_api_light/src/assists/introduce_variable.rs
rename to crates/ra_assists/src/introduce_variable.rs
index ed13bddc48e..c937a816cda 100644
--- a/crates/ra_ide_api_light/src/assists/introduce_variable.rs
+++ b/crates/ra_assists/src/introduce_variable.rs
@@ -1,3 +1,4 @@
+use hir::db::HirDatabase;
 use ra_syntax::{
     ast::{self, AstNode},
     SyntaxKind::{
@@ -5,9 +6,9 @@ use ra_syntax::{
     }, SyntaxNode, TextUnit,
 };
 
-use crate::assists::{AssistCtx, Assist};
+use crate::{AssistCtx, Assist};
 
-pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> {
+pub(crate) fn introduce_variable<'a>(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let node = ctx.covering_node();
     if !valid_covering_node(node) {
         return None;
@@ -103,7 +104,7 @@ fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::assists::{ check_assist, check_assist_not_applicable, check_assist_range };
+    use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range};
 
     #[test]
     fn test_introduce_var_simple() {
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
new file mode 100644
index 00000000000..4e97a84c20d
--- /dev/null
+++ b/crates/ra_assists/src/lib.rs
@@ -0,0 +1,170 @@
+//! `ra_assits` crate provides a bunch of code assists, aslo known as code
+//! actions (in LSP) or intentions (in IntelliJ).
+//!
+//! An assist is a micro-refactoring, which is automatically activated in
+//! certain context. For example, if the cursor is over `,`, a "swap `,`" assist
+//! becomes available.
+
+mod assist_ctx;
+
+use ra_text_edit::TextEdit;
+use ra_syntax::{TextUnit, SyntaxNode, Direction};
+use ra_db::FileRange;
+use hir::db::HirDatabase;
+
+pub(crate) use crate::assist_ctx::{AssistCtx, Assist};
+
+#[derive(Debug)]
+pub struct AssistLabel {
+    /// Short description of the assist, as shown in the UI.
+    pub label: String,
+}
+
+pub struct AssistAction {
+    pub edit: TextEdit,
+    pub cursor_position: Option<TextUnit>,
+}
+
+/// Return all the assists applicable at the given position.
+///
+/// Assists are returned in the "unresolved" state, that is only labels are
+/// returned, without actual edits.
+pub fn applicable_assists<H>(db: &H, range: FileRange) -> Vec<AssistLabel>
+where
+    H: HirDatabase + 'static,
+{
+    AssistCtx::with_ctx(db, range, false, |ctx| {
+        all_assists()
+            .iter()
+            .filter_map(|f| f(ctx.clone()))
+            .map(|a| match a {
+                Assist::Unresolved(label) => label,
+                Assist::Resolved(..) => unreachable!(),
+            })
+            .collect()
+    })
+}
+
+/// Return all the assists applicable at the given position.
+///
+/// Assists are returned in the "resolved" state, that is with edit fully
+/// computed.
+pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)>
+where
+    H: HirDatabase + 'static,
+{
+    AssistCtx::with_ctx(db, range, false, |ctx| {
+        all_assists()
+            .iter()
+            .filter_map(|f| f(ctx.clone()))
+            .map(|a| match a {
+                Assist::Resolved(label, action) => (label, action),
+                Assist::Unresolved(..) => unreachable!(),
+            })
+            .collect()
+    })
+}
+
+mod add_derive;
+mod add_impl;
+mod flip_comma;
+mod change_visibility;
+mod fill_match_arms;
+mod introduce_variable;
+mod replace_if_let_with_match;
+mod split_import;
+fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] {
+    &[
+        add_derive::add_derive,
+        add_impl::add_impl,
+        change_visibility::change_visibility,
+        fill_match_arms::fill_match_arms,
+        flip_comma::flip_comma,
+        introduce_variable::introduce_variable,
+        replace_if_let_with_match::replace_if_let_with_match,
+        split_import::split_import,
+    ]
+}
+
+fn non_trivia_sibling(node: &SyntaxNode, direction: Direction) -> Option<&SyntaxNode> {
+    node.siblings(direction)
+        .skip(1)
+        .find(|node| !node.kind().is_trivia())
+}
+
+#[cfg(test)]
+mod helpers {
+    use hir::mock::MockDatabase;
+    use ra_syntax::TextRange;
+    use ra_db::FileRange;
+    use test_utils::{extract_offset, assert_eq_text, add_cursor, extract_range};
+
+    use crate::{AssistCtx, Assist};
+
+    pub(crate) fn check_assist(
+        assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
+        before: &str,
+        after: &str,
+    ) {
+        let (before_cursor_pos, before) = extract_offset(before);
+        let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
+        let frange = 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(_, it) => it,
+        };
+
+        let actual = action.edit.apply(&before);
+        let actual_cursor_pos = match action.cursor_position {
+            None => action
+                .edit
+                .apply_to_offset(before_cursor_pos)
+                .expect("cursor position is affected by the edit"),
+            Some(off) => off,
+        };
+        let actual = add_cursor(&actual, actual_cursor_pos);
+        assert_eq_text!(after, &actual);
+    }
+
+    pub(crate) fn check_assist_range(
+        assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
+        before: &str,
+        after: &str,
+    ) {
+        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(_, it) => it,
+        };
+
+        let mut actual = action.edit.apply(&before);
+        if let Some(pos) = action.cursor_position {
+            actual = add_cursor(&actual, pos);
+        }
+        assert_eq_text!(after, &actual);
+    }
+
+    pub(crate) fn check_assist_not_applicable(
+        assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
+        before: &str,
+    ) {
+        let (before_cursor_pos, before) = extract_offset(before);
+        let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
+        let frange = FileRange {
+            file_id,
+            range: TextRange::offset_len(before_cursor_pos, 0.into()),
+        };
+        let assist = AssistCtx::with_ctx(&db, frange, true, assist);
+        assert!(assist.is_none());
+    }
+
+}
diff --git a/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/replace_if_let_with_match.rs
similarity index 88%
rename from crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs
rename to crates/ra_assists/src/replace_if_let_with_match.rs
index 71880b91982..f6af47ec913 100644
--- a/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/replace_if_let_with_match.rs
@@ -1,11 +1,10 @@
 use ra_syntax::{AstNode, ast};
+use ra_ide_api_light::formatting::extract_trivial_expression;
+use hir::db::HirDatabase;
 
-use crate::{
-    assists::{AssistCtx, Assist},
-    formatting::extract_trivial_expression,
-};
+use crate::{AssistCtx, Assist};
 
-pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
+pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let if_expr: &ast::IfExpr = ctx.node_at_offset()?;
     let cond = if_expr.condition()?;
     let pat = cond.pat()?;
@@ -51,7 +50,7 @@ fn format_arm(block: &ast::Block) -> String {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::assists::check_assist;
+    use crate::helpers::check_assist;
 
     #[test]
     fn test_replace_if_let_with_match_unwraps_simple_expressions() {
diff --git a/crates/ra_ide_api_light/src/assists/split_import.rs b/crates/ra_assists/src/split_import.rs
similarity index 88%
rename from crates/ra_ide_api_light/src/assists/split_import.rs
rename to crates/ra_assists/src/split_import.rs
index e4015f07da6..7e34be0878f 100644
--- a/crates/ra_ide_api_light/src/assists/split_import.rs
+++ b/crates/ra_assists/src/split_import.rs
@@ -1,12 +1,13 @@
+use hir::db::HirDatabase;
 use ra_syntax::{
     TextUnit, AstNode, SyntaxKind::COLONCOLON,
     ast,
     algo::generate,
 };
 
-use crate::assists::{AssistCtx, Assist};
+use crate::{AssistCtx, Assist};
 
-pub fn split_import(ctx: AssistCtx) -> Option<Assist> {
+pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
     let colon_colon = ctx
         .leaf_at_offset()
         .find(|leaf| leaf.kind() == COLONCOLON)?;
@@ -34,7 +35,7 @@ pub fn split_import(ctx: AssistCtx) -> Option<Assist> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::assists::check_assist;
+    use crate::helpers::check_assist;
 
     #[test]
     fn test_split_import() {
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs
index 926cf0bd598..66634e05b37 100644
--- a/crates/ra_db/src/lib.rs
+++ b/crates/ra_db/src/lib.rs
@@ -70,7 +70,7 @@ pub struct FileRange {
 /// Database which stores all significant input facts: source code and project
 /// model. Everything else in rust-analyzer is derived from these queries.
 #[salsa::query_group(SourceDatabaseStorage)]
-pub trait SourceDatabase: CheckCanceled {
+pub trait SourceDatabase: CheckCanceled + std::fmt::Debug {
     /// Text of the file.
     #[salsa::input]
     fn file_text(&self, file_id: FileId) -> Arc<String>;
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index 54da5559857..a9cd955cf05 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -18,8 +18,7 @@ macro_rules! impl_froms {
 }
 
 pub mod db;
-#[cfg(test)]
-mod mock;
+pub mod mock;
 mod query_definitions;
 mod path;
 pub mod source_binder;
diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs
index 00a07d1a18a..87095fb2192 100644
--- a/crates/ra_hir/src/mock.rs
+++ b/crates/ra_hir/src/mock.rs
@@ -17,7 +17,7 @@ pub const WORKSPACE: SourceRootId = SourceRootId(0);
     db::PersistentHirDatabaseStorage
 )]
 #[derive(Debug)]
-pub(crate) struct MockDatabase {
+pub struct MockDatabase {
     events: Mutex<Option<Vec<salsa::Event<MockDatabase>>>>,
     runtime: salsa::Runtime<MockDatabase>,
     interner: Arc<HirInterner>,
@@ -27,13 +27,13 @@ pub(crate) struct MockDatabase {
 impl panic::RefUnwindSafe for MockDatabase {}
 
 impl MockDatabase {
-    pub(crate) fn with_files(fixture: &str) -> (MockDatabase, SourceRoot) {
+    pub fn with_files(fixture: &str) -> (MockDatabase, SourceRoot) {
         let (db, source_root, position) = MockDatabase::from_fixture(fixture);
         assert!(position.is_none());
         (db, source_root)
     }
 
-    pub(crate) fn with_single_file(text: &str) -> (MockDatabase, SourceRoot, FileId) {
+    pub fn with_single_file(text: &str) -> (MockDatabase, SourceRoot, FileId) {
         let mut db = MockDatabase::default();
         let mut source_root = SourceRoot::default();
         let file_id = db.add_file(WORKSPACE, &mut source_root, "/main.rs", text);
@@ -41,7 +41,7 @@ impl MockDatabase {
         (db, source_root, file_id)
     }
 
-    pub(crate) fn with_position(fixture: &str) -> (MockDatabase, FilePosition) {
+    pub fn with_position(fixture: &str) -> (MockDatabase, FilePosition) {
         let (db, _, position) = MockDatabase::from_fixture(fixture);
         let position = position.expect("expected a marker ( <|> )");
         (db, position)
@@ -166,13 +166,13 @@ impl AsRef<HirInterner> for MockDatabase {
 }
 
 impl MockDatabase {
-    pub(crate) fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event<MockDatabase>> {
+    pub fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event<MockDatabase>> {
         *self.events.lock() = Some(Vec::new());
         f();
         self.events.lock().take().unwrap()
     }
 
-    pub(crate) fn log_executed(&self, f: impl FnOnce()) -> Vec<String> {
+    pub fn log_executed(&self, f: impl FnOnce()) -> Vec<String> {
         let events = self.log(f);
         events
             .into_iter()
diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml
index 54de9b2e3ab..95cccf8cf94 100644
--- a/crates/ra_ide_api/Cargo.toml
+++ b/crates/ra_ide_api/Cargo.toml
@@ -24,6 +24,7 @@ ra_text_edit = { path = "../ra_text_edit" }
 ra_db = { path = "../ra_db" }
 hir = { path = "../ra_hir", package = "ra_hir" }
 test_utils = { path = "../test_utils" }
+ra_assists = { path = "../ra_assists" }
 
 [dev-dependencies]
 insta = "0.6.1"
diff --git a/crates/ra_ide_api/src/assists.rs b/crates/ra_ide_api/src/assists.rs
index 2da251df5d1..2a96fdf471d 100644
--- a/crates/ra_ide_api/src/assists.rs
+++ b/crates/ra_ide_api/src/assists.rs
@@ -1,89 +1,24 @@
-mod fill_match_arm;
+use ra_db::{FileRange, FilePosition};
 
-use ra_syntax::{
-    TextRange, SourceFile, AstNode,
-    algo::find_node_at_offset,
-};
-use ra_ide_api_light::{
-    LocalEdit,
-    assists::{
-        Assist,
-        AssistBuilder
-    }
-};
-use crate::{
-    db::RootDatabase,
-    FileId
-};
+use crate::{SourceFileEdit, SourceChange, db::RootDatabase};
 
-/// Return all the assists applicable at the given position.
-pub(crate) fn assists(
-    db: &RootDatabase,
-    file_id: FileId,
-    file: &SourceFile,
-    range: TextRange,
-) -> Vec<LocalEdit> {
-    let ctx = AssistCtx::new(db, file_id, file, range);
-    [fill_match_arm::fill_match_arm]
-        .iter()
-        .filter_map(|&assist| ctx.clone().apply(assist))
+pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<SourceChange> {
+    ra_assists::assists(db, frange)
+        .into_iter()
+        .map(|(label, action)| {
+            let file_id = frange.file_id;
+            let file_edit = SourceFileEdit {
+                file_id,
+                edit: action.edit,
+            };
+            SourceChange {
+                label: label.label,
+                source_file_edits: vec![file_edit],
+                file_system_edits: vec![],
+                cursor_position: action
+                    .cursor_position
+                    .map(|offset| FilePosition { offset, file_id }),
+            }
+        })
         .collect()
 }
-
-#[derive(Debug, Clone)]
-pub struct AssistCtx<'a> {
-    file_id: FileId,
-    source_file: &'a SourceFile,
-    db: &'a RootDatabase,
-    range: TextRange,
-    should_compute_edit: bool,
-}
-
-impl<'a> AssistCtx<'a> {
-    pub(crate) fn new(
-        db: &'a RootDatabase,
-        file_id: FileId,
-        source_file: &'a SourceFile,
-        range: TextRange,
-    ) -> AssistCtx<'a> {
-        AssistCtx {
-            source_file,
-            file_id,
-            db,
-            range,
-            should_compute_edit: false,
-        }
-    }
-
-    pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> {
-        self.should_compute_edit = true;
-        match assist(self) {
-            None => None,
-            Some(Assist::Edit(e)) => Some(e),
-            Some(Assist::Applicable) => unreachable!(),
-        }
-    }
-
-    #[allow(unused)]
-    pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool {
-        self.should_compute_edit = false;
-        match assist(self) {
-            None => false,
-            Some(Assist::Edit(_)) => unreachable!(),
-            Some(Assist::Applicable) => true,
-        }
-    }
-
-    fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> {
-        if !self.should_compute_edit {
-            return Some(Assist::Applicable);
-        }
-        let mut edit = AssistBuilder::default();
-        f(&mut edit);
-        Some(edit.build(label))
-    }
-
-    pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> {
-        find_node_at_offset(self.source_file.syntax(), self.range.start())
-    }
-}
diff --git a/crates/ra_ide_api/src/assists/fill_match_arm.rs b/crates/ra_ide_api/src/assists/fill_match_arm.rs
deleted file mode 100644
index 6ae829d8569..00000000000
--- a/crates/ra_ide_api/src/assists/fill_match_arm.rs
+++ /dev/null
@@ -1,157 +0,0 @@
-use std::fmt::Write;
-use hir::{
-    AdtDef,
-    source_binder,
-    Ty,
-    FieldSource,
-};
-use ra_ide_api_light::{
-    assists::{
-        Assist,
-        AssistBuilder
-    }
-};
-use ra_syntax::{
-    ast::{
-        self,
-        AstNode,
-    }
-};
-
-use crate::assists::AssistCtx;
-
-pub fn fill_match_arm(ctx: AssistCtx) -> Option<Assist> {
-    let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?;
-
-    // We already have some match arms, so we don't provide any assists.
-    match match_expr.match_arm_list() {
-        Some(arm_list) if arm_list.arms().count() > 0 => {
-            return None;
-        }
-        _ => {}
-    }
-
-    let expr = match_expr.expr()?;
-    let function = source_binder::function_from_child_node(ctx.db, ctx.file_id, expr.syntax())?;
-    let infer_result = function.infer(ctx.db);
-    let syntax_mapping = function.body_syntax_mapping(ctx.db);
-    let node_expr = syntax_mapping.node_expr(expr)?;
-    let match_expr_ty = infer_result[node_expr].clone();
-    match match_expr_ty {
-        Ty::Adt { def_id, .. } => match def_id {
-            AdtDef::Enum(e) => {
-                let mut buf = format!("match {} {{\n", expr.syntax().text().to_string());
-                let variants = e.variants(ctx.db);
-                for variant in variants {
-                    let name = variant.name(ctx.db)?;
-                    write!(
-                        &mut buf,
-                        "    {}::{}",
-                        e.name(ctx.db)?.to_string(),
-                        name.to_string()
-                    )
-                    .expect("write fmt");
-
-                    let pat = variant
-                        .fields(ctx.db)
-                        .into_iter()
-                        .map(|field| {
-                            let name = field.name(ctx.db).to_string();
-                            let (_, source) = field.source(ctx.db);
-                            match source {
-                                FieldSource::Named(_) => name,
-                                FieldSource::Pos(_) => "_".to_string(),
-                            }
-                        })
-                        .collect::<Vec<_>>();
-
-                    match pat.first().map(|s| s.as_str()) {
-                        Some("_") => write!(&mut buf, "({})", pat.join(", ")).expect("write fmt"),
-                        Some(_) => write!(&mut buf, "{{{}}}", pat.join(", ")).expect("write fmt"),
-                        None => (),
-                    };
-
-                    buf.push_str(" => (),\n");
-                }
-                buf.push_str("}");
-                ctx.build("fill match arms", |edit: &mut AssistBuilder| {
-                    edit.replace_node_and_indent(match_expr.syntax(), buf);
-                })
-            }
-            _ => None,
-        },
-        _ => None,
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use insta::assert_debug_snapshot_matches;
-
-    use ra_syntax::{TextRange, TextUnit};
-
-    use crate::{
-        FileRange,
-        mock_analysis::{analysis_and_position, single_file_with_position}
-};
-    use ra_db::SourceDatabase;
-
-    fn test_assit(name: &str, code: &str) {
-        let (analysis, position) = if code.contains("//-") {
-            analysis_and_position(code)
-        } else {
-            single_file_with_position(code)
-        };
-        let frange = FileRange {
-            file_id: position.file_id,
-            range: TextRange::offset_len(position.offset, TextUnit::from(1)),
-        };
-        let source_file = analysis
-            .with_db(|db| db.parse(frange.file_id))
-            .expect("source file");
-        let ret = analysis
-            .with_db(|db| crate::assists::assists(db, frange.file_id, &source_file, frange.range))
-            .expect("assists");
-
-        assert_debug_snapshot_matches!(name, ret);
-    }
-
-    #[test]
-    fn test_fill_match_arm() {
-        test_assit(
-            "fill_match_arm1",
-            r#"
-        enum A {
-            As,
-            Bs,
-            Cs(String),
-            Ds(String, String),
-            Es{x: usize, y: usize}
-        }
-
-        fn main() {
-            let a = A::As;
-            match a<|>
-        }
-        "#,
-        );
-
-        test_assit(
-            "fill_match_arm2",
-            r#"
-        enum A {
-            As,
-            Bs,
-            Cs(String),
-            Ds(String, String),
-            Es{x: usize, y: usize}
-        }
-
-        fn main() {
-            let a = A::As;
-            match a<|> {}
-        }
-        "#,
-        );
-    }
-}
diff --git a/crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm1.snap b/crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm1.snap
deleted file mode 100644
index 980726d92f6..00000000000
--- a/crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm1.snap
+++ /dev/null
@@ -1,20 +0,0 @@
----
-created: "2019-02-03T15:38:46.094184+00:00"
-creator: insta@0.5.2
-expression: ret
-source: crates/ra_ide_api/src/assits/fill_match_arm.rs
----
-[
-    LocalEdit {
-        label: "fill match arms",
-        edit: TextEdit {
-            atoms: [
-                AtomTextEdit {
-                    delete: [211; 218),
-                    insert: "match a {\n                A::As => (),\n                A::Bs => (),\n                A::Cs(_) => (),\n                A::Ds(_, _) => (),\n                A::Es{x, y} => (),\n            }"
-                }
-            ]
-        },
-        cursor_position: None
-    }
-]
diff --git a/crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm2.snap b/crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm2.snap
deleted file mode 100644
index cee0efe74a6..00000000000
--- a/crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm2.snap
+++ /dev/null
@@ -1,20 +0,0 @@
----
-created: "2019-02-03T15:41:34.640074+00:00"
-creator: insta@0.5.2
-expression: ret
-source: crates/ra_ide_api/src/assits/fill_match_arm.rs
----
-[
-    LocalEdit {
-        label: "fill match arms",
-        edit: TextEdit {
-            atoms: [
-                AtomTextEdit {
-                    delete: [211; 221),
-                    insert: "match a {\n                A::As => (),\n                A::Bs => (),\n                A::Cs(_) => (),\n                A::Ds(_, _) => (),\n                A::Es{x, y} => (),\n            }"
-                }
-            ]
-        },
-        cursor_position: None
-    }
-]
diff --git a/crates/ra_ide_api/src/imp.rs b/crates/ra_ide_api/src/imp.rs
index fd8637ad249..b139efabf6d 100644
--- a/crates/ra_ide_api/src/imp.rs
+++ b/crates/ra_ide_api/src/imp.rs
@@ -19,7 +19,7 @@ use ra_syntax::{
 
 use crate::{
     AnalysisChange,
-    CrateId, db, Diagnostic, FileId, FilePosition, FileRange, FileSystemEdit,
+    CrateId, db, Diagnostic, FileId, FilePosition, FileSystemEdit,
     Query, RootChange, SourceChange, SourceFileEdit,
     symbol_index::{FileSymbol, SymbolsDatabase},
     status::syntax_tree_stats
@@ -236,15 +236,6 @@ impl db::RootDatabase {
         res
     }
 
-    pub(crate) fn assists(&self, frange: FileRange) -> Vec<SourceChange> {
-        let file = self.parse(frange.file_id);
-        ra_ide_api_light::assists::assists(&file, frange.range)
-            .into_iter()
-            .chain(crate::assists::assists(self, frange.file_id, &file, frange.range).into_iter())
-            .map(|local_edit| SourceChange::from_local_edit(frange.file_id, local_edit))
-            .collect()
-    }
-
     pub(crate) fn index_resolve(&self, name_ref: &ast::NameRef) -> Vec<FileSymbol> {
         let name = name_ref.text();
         let mut query = Query::new(name.to_string());
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index 3a187d7a542..8beaba5de3b 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -477,7 +477,7 @@ impl Analysis {
     /// Computes assists (aks code actons aka intentions) for the given
     /// position.
     pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<SourceChange>> {
-        self.with_db(|db| db.assists(frange))
+        self.with_db(|db| assists::assists(db, frange))
     }
 
     /// Computes the set of diagnostics for the given file.
diff --git a/crates/ra_ide_api_light/src/assists.rs b/crates/ra_ide_api_light/src/assists.rs
deleted file mode 100644
index e578805f107..00000000000
--- a/crates/ra_ide_api_light/src/assists.rs
+++ /dev/null
@@ -1,215 +0,0 @@
-//! This modules contains various "assists": suggestions for source code edits
-//! which are likely to occur at a given cursor position. For example, if the
-//! cursor is on the `,`, a possible assist is swapping the elements around the
-//! comma.
-
-mod flip_comma;
-mod add_derive;
-mod add_impl;
-mod introduce_variable;
-mod change_visibility;
-mod split_import;
-mod replace_if_let_with_match;
-
-use ra_text_edit::{TextEdit, TextEditBuilder};
-use ra_syntax::{
-    Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode,
-    algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset},
-};
-use itertools::Itertools;
-
-use crate::formatting::leading_indent;
-
-pub use self::{
-    flip_comma::flip_comma,
-    add_derive::add_derive,
-    add_impl::add_impl,
-    introduce_variable::introduce_variable,
-    change_visibility::change_visibility,
-    split_import::split_import,
-    replace_if_let_with_match::replace_if_let_with_match,
-};
-
-/// Return all the assists applicable at the given position.
-pub fn assists(file: &SourceFile, range: TextRange) -> Vec<LocalEdit> {
-    let ctx = AssistCtx::new(file, range);
-    [
-        flip_comma,
-        add_derive,
-        add_impl,
-        introduce_variable,
-        change_visibility,
-        split_import,
-        replace_if_let_with_match,
-    ]
-    .iter()
-    .filter_map(|&assist| ctx.clone().apply(assist))
-    .collect()
-}
-
-#[derive(Debug)]
-pub struct LocalEdit {
-    pub label: String,
-    pub edit: TextEdit,
-    pub cursor_position: Option<TextUnit>,
-}
-
-fn non_trivia_sibling(node: &SyntaxNode, direction: Direction) -> Option<&SyntaxNode> {
-    node.siblings(direction)
-        .skip(1)
-        .find(|node| !node.kind().is_trivia())
-}
-
-/// `AssistCtx` allows to apply an assist or check if it could be applied.
-///
-/// Assists use a somewhat overengineered approach, given the current needs. The
-/// assists workflow consists of two phases. In the first phase, a user asks for
-/// the list of available assists. In the second phase, the user picks a
-/// particular assist and it gets applied.
-///
-/// There are two peculiarities here:
-///
-/// * first, we ideally avoid computing more things then necessary to answer
-///   "is assist applicable" in the first phase.
-/// * second, when we are applying assist, we don't have a guarantee that there
-///   weren't any changes between the point when user asked for assists and when
-///   they applied a particular assist. So, when applying assist, we need to do
-///   all the checks from scratch.
-///
-/// To avoid repeating the same code twice for both "check" and "apply"
-/// functions, we use an approach reminiscent of that of Django's function based
-/// views dealing with forms. Each assist receives a runtime parameter,
-/// `should_compute_edit`. It first check if an edit is applicable (potentially
-/// computing info required to compute the actual edit). If it is applicable,
-/// and `should_compute_edit` is `true`, it then computes the actual edit.
-///
-/// So, to implement the original assists workflow, we can first apply each edit
-/// with `should_compute_edit = false`, and then applying the selected edit
-/// again, with `should_compute_edit = true` this time.
-///
-/// Note, however, that we don't actually use such two-phase logic at the
-/// moment, because the LSP API is pretty awkward in this place, and it's much
-/// easier to just compute the edit eagerly :-)
-#[derive(Debug, Clone)]
-pub struct AssistCtx<'a> {
-    source_file: &'a SourceFile,
-    range: TextRange,
-    should_compute_edit: bool,
-}
-
-#[derive(Debug)]
-pub enum Assist {
-    Applicable,
-    Edit(LocalEdit),
-}
-
-#[derive(Default)]
-pub struct AssistBuilder {
-    edit: TextEditBuilder,
-    cursor_position: Option<TextUnit>,
-}
-
-impl<'a> AssistCtx<'a> {
-    pub fn new(source_file: &'a SourceFile, range: TextRange) -> AssistCtx {
-        AssistCtx {
-            source_file,
-            range,
-            should_compute_edit: false,
-        }
-    }
-
-    pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> {
-        self.should_compute_edit = true;
-        match assist(self) {
-            None => None,
-            Some(Assist::Edit(e)) => Some(e),
-            Some(Assist::Applicable) => unreachable!(),
-        }
-    }
-
-    pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool {
-        self.should_compute_edit = false;
-        match assist(self) {
-            None => false,
-            Some(Assist::Edit(_)) => unreachable!(),
-            Some(Assist::Applicable) => true,
-        }
-    }
-
-    fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> {
-        if !self.should_compute_edit {
-            return Some(Assist::Applicable);
-        }
-        let mut edit = AssistBuilder::default();
-        f(&mut edit);
-        Some(edit.build(label))
-    }
-
-    pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> {
-        find_leaf_at_offset(self.source_file.syntax(), self.range.start())
-    }
-    pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> {
-        find_node_at_offset(self.source_file.syntax(), self.range.start())
-    }
-    pub(crate) fn covering_node(&self) -> &'a SyntaxNode {
-        find_covering_node(self.source_file.syntax(), self.range)
-    }
-}
-
-impl AssistBuilder {
-    fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
-        self.edit.replace(range, replace_with.into())
-    }
-    pub fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) {
-        let mut replace_with = replace_with.into();
-        if let Some(indent) = leading_indent(node) {
-            replace_with = reindent(&replace_with, indent)
-        }
-        self.replace(node.range(), replace_with)
-    }
-    #[allow(unused)]
-    fn delete(&mut self, range: TextRange) {
-        self.edit.delete(range)
-    }
-    fn insert(&mut self, offset: TextUnit, text: impl Into<String>) {
-        self.edit.insert(offset, text.into())
-    }
-    fn set_cursor(&mut self, offset: TextUnit) {
-        self.cursor_position = Some(offset)
-    }
-    pub fn build(self, label: impl Into<String>) -> Assist {
-        Assist::Edit(LocalEdit {
-            label: label.into(),
-            cursor_position: self.cursor_position,
-            edit: self.edit.finish(),
-        })
-    }
-}
-
-fn reindent(text: &str, indent: &str) -> String {
-    let indent = format!("\n{}", indent);
-    text.lines().intersperse(&indent).collect()
-}
-
-#[cfg(test)]
-fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) {
-    crate::test_utils::check_action(before, after, |file, off| {
-        let range = TextRange::offset_len(off, 0.into());
-        AssistCtx::new(file, range).apply(assist)
-    })
-}
-
-#[cfg(test)]
-fn check_assist_not_applicable(assist: fn(AssistCtx) -> Option<Assist>, text: &str) {
-    crate::test_utils::check_action_not_applicable(text, |file, off| {
-        let range = TextRange::offset_len(off, 0.into());
-        AssistCtx::new(file, range).apply(assist)
-    })
-}
-
-#[cfg(test)]
-fn check_assist_range(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) {
-    crate::test_utils::check_action_range(before, after, |file, range| {
-        AssistCtx::new(file, range).apply(assist)
-    })
-}
diff --git a/crates/ra_ide_api_light/src/formatting.rs b/crates/ra_ide_api_light/src/formatting.rs
index 1f34b85d693..46ffa7d9604 100644
--- a/crates/ra_ide_api_light/src/formatting.rs
+++ b/crates/ra_ide_api_light/src/formatting.rs
@@ -1,3 +1,4 @@
+use itertools::Itertools;
 use ra_syntax::{
     AstNode,
     SyntaxNode, SyntaxKind::*,
@@ -5,8 +6,13 @@ use ra_syntax::{
     algo::generate,
 };
 
+pub fn reindent(text: &str, indent: &str) -> String {
+    let indent = format!("\n{}", indent);
+    text.lines().intersperse(&indent).collect()
+}
+
 /// If the node is on the beginning of the line, calculate indent.
-pub(crate) fn leading_indent(node: &SyntaxNode) -> Option<&str> {
+pub fn leading_indent(node: &SyntaxNode) -> Option<&str> {
     for leaf in prev_leaves(node) {
         if let Some(ws) = ast::Whitespace::cast(leaf) {
             let ws_text = ws.text();
@@ -32,7 +38,7 @@ fn prev_leaf(node: &SyntaxNode) -> Option<&SyntaxNode> {
     .last()
 }
 
-pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> {
+pub fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> {
     let expr = block.expr()?;
     if expr.syntax().text().contains('\n') {
         return None;
diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs
index 9dd72701d70..17044270c60 100644
--- a/crates/ra_ide_api_light/src/lib.rs
+++ b/crates/ra_ide_api_light/src/lib.rs
@@ -3,7 +3,7 @@
 //! This usually means functions which take syntax tree as an input and produce
 //! an edit or some auxiliary info.
 
-pub mod assists;
+pub mod formatting;
 mod extend_selection;
 mod folding_ranges;
 mod line_index;
@@ -14,10 +14,15 @@ mod test_utils;
 mod join_lines;
 mod typing;
 mod diagnostics;
-pub(crate) mod formatting;
+
+#[derive(Debug)]
+pub struct LocalEdit {
+    pub label: String,
+    pub edit: ra_text_edit::TextEdit,
+    pub cursor_position: Option<TextUnit>,
+}
 
 pub use self::{
-    assists::LocalEdit,
     extend_selection::extend_selection,
     folding_ranges::{folding_ranges, Fold, FoldKind},
     line_index::{LineCol, LineIndex},
diff --git a/crates/ra_ide_api_light/src/test_utils.rs b/crates/ra_ide_api_light/src/test_utils.rs
index 22ded243553..bfac0fce369 100644
--- a/crates/ra_ide_api_light/src/test_utils.rs
+++ b/crates/ra_ide_api_light/src/test_utils.rs
@@ -1,4 +1,4 @@
-use ra_syntax::{SourceFile, TextRange, TextUnit};
+use ra_syntax::{SourceFile, TextUnit};
 
 use crate::LocalEdit;
 pub use test_utils::*;
@@ -22,32 +22,3 @@ pub fn check_action<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>(
     let actual = add_cursor(&actual, actual_cursor_pos);
     assert_eq_text!(after, &actual);
 }
-
-pub fn check_action_not_applicable<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>(
-    text: &str,
-    f: F,
-) {
-    let (text_cursor_pos, text) = extract_offset(text);
-    let file = SourceFile::parse(&text);
-    assert!(
-        f(&file, text_cursor_pos).is_none(),
-        "code action is applicable but it shouldn't"
-    );
-}
-
-pub fn check_action_range<F: Fn(&SourceFile, TextRange) -> Option<LocalEdit>>(
-    before: &str,
-    after: &str,
-    f: F,
-) {
-    let (range, before) = extract_range(before);
-    let file = SourceFile::parse(&before);
-    let result = f(&file, range).expect("code action is not applicable");
-    let actual = result.edit.apply(&before);
-    let actual_cursor_pos = match result.cursor_position {
-        None => result.edit.apply_to_offset(range.start()).unwrap(),
-        Some(off) => off,
-    };
-    let actual = add_cursor(&actual, actual_cursor_pos);
-    assert_eq_text!(after, &actual);
-}