aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/Cargo.toml17
-rw-r--r--crates/ra_assists/src/add_derive.rs (renamed from crates/ra_ide_api_light/src/assists/add_derive.rs)7
-rw-r--r--crates/ra_assists/src/add_impl.rs (renamed from crates/ra_ide_api_light/src/assists/add_impl.rs)7
-rw-r--r--crates/ra_assists/src/assist_ctx.rs154
-rw-r--r--crates/ra_assists/src/change_visibility.rs (renamed from crates/ra_ide_api_light/src/assists/change_visibility.rs)11
-rw-r--r--crates/ra_assists/src/fill_match_arms.rs145
-rw-r--r--crates/ra_assists/src/flip_comma.rs (renamed from crates/ra_ide_api_light/src/assists/flip_comma.rs)8
-rw-r--r--crates/ra_assists/src/introduce_variable.rs (renamed from crates/ra_ide_api_light/src/assists/introduce_variable.rs)11
-rw-r--r--crates/ra_assists/src/lib.rs170
-rw-r--r--crates/ra_assists/src/replace_if_let_with_match.rs (renamed from crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs)11
-rw-r--r--crates/ra_assists/src/split_import.rs (renamed from crates/ra_ide_api_light/src/assists/split_import.rs)7
-rw-r--r--crates/ra_db/src/lib.rs2
-rw-r--r--crates/ra_hir/src/expr.rs6
-rw-r--r--crates/ra_hir/src/expr/scope.rs4
-rw-r--r--crates/ra_hir/src/impl_block.rs2
-rw-r--r--crates/ra_hir/src/lib.rs3
-rw-r--r--crates/ra_hir/src/mock.rs12
-rw-r--r--crates/ra_hir/src/resolve.rs2
-rw-r--r--crates/ra_hir/src/ty.rs8
-rw-r--r--crates/ra_hir/src/ty/method_resolution.rs2
-rw-r--r--crates/ra_ide_api/Cargo.toml1
-rw-r--r--crates/ra_ide_api/src/assists.rs109
-rw-r--r--crates/ra_ide_api/src/assists/fill_match_arm.rs157
-rw-r--r--crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm1.snap20
-rw-r--r--crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm2.snap20
-rw-r--r--crates/ra_ide_api/src/completion/completion_context.rs9
-rw-r--r--crates/ra_ide_api/src/completion/completion_item.rs6
-rw-r--r--crates/ra_ide_api/src/goto_definition.rs6
-rw-r--r--crates/ra_ide_api/src/imp.rs11
-rw-r--r--crates/ra_ide_api/src/lib.rs4
-rw-r--r--crates/ra_ide_api/src/rename.rs8
-rw-r--r--crates/ra_ide_api/src/symbol_index.rs2
-rw-r--r--crates/ra_ide_api_light/src/assists.rs215
-rw-r--r--crates/ra_ide_api_light/src/formatting.rs10
-rw-r--r--crates/ra_ide_api_light/src/lib.rs11
-rw-r--r--crates/ra_ide_api_light/src/test_utils.rs31
-rw-r--r--crates/ra_lsp_server/src/conv.rs7
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs34
38 files changed, 616 insertions, 634 deletions
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
new file mode 100644
index 000000000..20bc253e3
--- /dev/null
+++ b/crates/ra_assists/Cargo.toml
@@ -0,0 +1,17 @@
1[package]
2edition = "2018"
3name = "ra_assists"
4version = "0.1.0"
5authors = ["Aleksey Kladov <[email protected]>"]
6
7[dependencies]
8join_to_string = "0.1.3"
9
10ra_ide_api_light = { path = "../ra_ide_api_light" }
11ra_syntax = { path = "../ra_syntax" }
12ra_text_edit = { path = "../ra_text_edit" }
13ra_db = { path = "../ra_db" }
14hir = { path = "../ra_hir", package = "ra_hir" }
15
16[dev-dependencies]
17test_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
index 6e964d011..01a4079f6 100644
--- a/crates/ra_ide_api_light/src/assists/add_derive.rs
+++ b/crates/ra_assists/src/add_derive.rs
@@ -1,12 +1,13 @@
1use hir::db::HirDatabase;
1use ra_syntax::{ 2use ra_syntax::{
2 ast::{self, AstNode, AttrsOwner}, 3 ast::{self, AstNode, AttrsOwner},
3 SyntaxKind::{WHITESPACE, COMMENT}, 4 SyntaxKind::{WHITESPACE, COMMENT},
4 TextUnit, 5 TextUnit,
5}; 6};
6 7
7use crate::assists::{AssistCtx, Assist}; 8use crate::{AssistCtx, Assist};
8 9
9pub fn add_derive(ctx: AssistCtx) -> Option<Assist> { 10pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
10 let nominal = ctx.node_at_offset::<ast::NominalDef>()?; 11 let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
11 let node_start = derive_insertion_offset(nominal)?; 12 let node_start = derive_insertion_offset(nominal)?;
12 ctx.build("add `#[derive]`", |edit| { 13 ctx.build("add `#[derive]`", |edit| {
@@ -39,7 +40,7 @@ fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextUnit> {
39#[cfg(test)] 40#[cfg(test)]
40mod tests { 41mod tests {
41 use super::*; 42 use super::*;
42 use crate::assists::check_assist; 43 use crate::helpers::check_assist;
43 44
44 #[test] 45 #[test]
45 fn add_derive_new() { 46 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
index 2eda7cae2..699508f91 100644
--- a/crates/ra_ide_api_light/src/assists/add_impl.rs
+++ b/crates/ra_assists/src/add_impl.rs
@@ -1,12 +1,13 @@
1use join_to_string::join; 1use join_to_string::join;
2use hir::db::HirDatabase;
2use ra_syntax::{ 3use ra_syntax::{
3 ast::{self, AstNode, AstToken, NameOwner, TypeParamsOwner}, 4 ast::{self, AstNode, AstToken, NameOwner, TypeParamsOwner},
4 TextUnit, 5 TextUnit,
5}; 6};
6 7
7use crate::assists::{AssistCtx, Assist}; 8use crate::{AssistCtx, Assist};
8 9
9pub fn add_impl(ctx: AssistCtx) -> Option<Assist> { 10pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
10 let nominal = ctx.node_at_offset::<ast::NominalDef>()?; 11 let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
11 let name = nominal.name()?; 12 let name = nominal.name()?;
12 ctx.build("add impl", |edit| { 13 ctx.build("add impl", |edit| {
@@ -42,7 +43,7 @@ pub fn add_impl(ctx: AssistCtx) -> Option<Assist> {
42#[cfg(test)] 43#[cfg(test)]
43mod tests { 44mod tests {
44 use super::*; 45 use super::*;
45 use crate::assists::check_assist; 46 use crate::helpers::check_assist;
46 47
47 #[test] 48 #[test]
48 fn test_add_impl() { 49 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 000000000..6d09bde52
--- /dev/null
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -0,0 +1,154 @@
1use hir::db::HirDatabase;
2use ra_text_edit::TextEditBuilder;
3use ra_db::FileRange;
4use ra_syntax::{
5 SourceFile, TextRange, AstNode, TextUnit, SyntaxNode,
6 algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset},
7};
8use ra_ide_api_light::formatting::{leading_indent, reindent};
9
10use crate::{AssistLabel, AssistAction};
11
12pub(crate) enum Assist {
13 Unresolved(AssistLabel),
14 Resolved(AssistLabel, AssistAction),
15}
16
17/// `AssistCtx` allows to apply an assist or check if it could be applied.
18///
19/// Assists use a somewhat overengineered approach, given the current needs. The
20/// assists workflow consists of two phases. In the first phase, a user asks for
21/// the list of available assists. In the second phase, the user picks a
22/// particular assist and it gets applied.
23///
24/// There are two peculiarities here:
25///
26/// * first, we ideally avoid computing more things then necessary to answer
27/// "is assist applicable" in the first phase.
28/// * second, when we are applying assist, we don't have a guarantee that there
29/// weren't any changes between the point when user asked for assists and when
30/// they applied a particular assist. So, when applying assist, we need to do
31/// all the checks from scratch.
32///
33/// To avoid repeating the same code twice for both "check" and "apply"
34/// functions, we use an approach reminiscent of that of Django's function based
35/// views dealing with forms. Each assist receives a runtime parameter,
36/// `should_compute_edit`. It first check if an edit is applicable (potentially
37/// computing info required to compute the actual edit). If it is applicable,
38/// and `should_compute_edit` is `true`, it then computes the actual edit.
39///
40/// So, to implement the original assists workflow, we can first apply each edit
41/// with `should_compute_edit = false`, and then applying the selected edit
42/// again, with `should_compute_edit = true` this time.
43///
44/// Note, however, that we don't actually use such two-phase logic at the
45/// moment, because the LSP API is pretty awkward in this place, and it's much
46/// easier to just compute the edit eagerly :-)#[derive(Debug, Clone)]
47#[derive(Debug)]
48pub(crate) struct AssistCtx<'a, DB> {
49 pub(crate) db: &'a DB,
50 pub(crate) frange: FileRange,
51 source_file: &'a SourceFile,
52 should_compute_edit: bool,
53}
54
55impl<'a, DB> Clone for AssistCtx<'a, DB> {
56 fn clone(&self) -> Self {
57 AssistCtx {
58 db: self.db,
59 frange: self.frange,
60 source_file: self.source_file,
61 should_compute_edit: self.should_compute_edit,
62 }
63 }
64}
65
66impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
67 pub(crate) fn with_ctx<F, T>(db: &DB, frange: FileRange, should_compute_edit: bool, f: F) -> T
68 where
69 F: FnOnce(AssistCtx<DB>) -> T,
70 {
71 let source_file = &db.parse(frange.file_id);
72 let ctx = AssistCtx {
73 db,
74 frange,
75 source_file,
76 should_compute_edit,
77 };
78 f(ctx)
79 }
80
81 pub(crate) fn build(
82 self,
83 label: impl Into<String>,
84 f: impl FnOnce(&mut AssistBuilder),
85 ) -> Option<Assist> {
86 let label = AssistLabel {
87 label: label.into(),
88 };
89 if !self.should_compute_edit {
90 return Some(Assist::Unresolved(label));
91 }
92 let action = {
93 let mut edit = AssistBuilder::default();
94 f(&mut edit);
95 edit.build()
96 };
97 Some(Assist::Resolved(label, action))
98 }
99
100 pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> {
101 find_leaf_at_offset(self.source_file.syntax(), self.frange.range.start())
102 }
103
104 pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> {
105 find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
106 }
107 pub(crate) fn covering_node(&self) -> &'a SyntaxNode {
108 find_covering_node(self.source_file.syntax(), self.frange.range)
109 }
110}
111
112#[derive(Default)]
113pub(crate) struct AssistBuilder {
114 edit: TextEditBuilder,
115 cursor_position: Option<TextUnit>,
116}
117
118impl AssistBuilder {
119 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
120 self.edit.replace(range, replace_with.into())
121 }
122
123 pub(crate) fn replace_node_and_indent(
124 &mut self,
125 node: &SyntaxNode,
126 replace_with: impl Into<String>,
127 ) {
128 let mut replace_with = replace_with.into();
129 if let Some(indent) = leading_indent(node) {
130 replace_with = reindent(&replace_with, indent)
131 }
132 self.replace(node.range(), replace_with)
133 }
134
135 #[allow(unused)]
136 pub(crate) fn delete(&mut self, range: TextRange) {
137 self.edit.delete(range)
138 }
139
140 pub(crate) fn insert(&mut self, offset: TextUnit, text: impl Into<String>) {
141 self.edit.insert(offset, text.into())
142 }
143
144 pub(crate) fn set_cursor(&mut self, offset: TextUnit) {
145 self.cursor_position = Some(offset)
146 }
147
148 fn build(self) -> AssistAction {
149 AssistAction {
150 edit: self.edit.finish(),
151 cursor_position: self.cursor_position,
152 }
153 }
154}
diff --git a/crates/ra_ide_api_light/src/assists/change_visibility.rs b/crates/ra_assists/src/change_visibility.rs
index 6e8bc2632..4cd32985e 100644
--- a/crates/ra_ide_api_light/src/assists/change_visibility.rs
+++ b/crates/ra_assists/src/change_visibility.rs
@@ -1,19 +1,20 @@
1use hir::db::HirDatabase;
1use ra_syntax::{ 2use ra_syntax::{
2 AstNode, SyntaxNode, TextUnit, 3 AstNode, SyntaxNode, TextUnit,
3 ast::{self, VisibilityOwner, NameOwner}, 4 ast::{self, VisibilityOwner, NameOwner},
4 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}, 5 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},
5}; 6};
6 7
7use crate::assists::{AssistCtx, Assist}; 8use crate::{AssistCtx, Assist};
8 9
9pub fn change_visibility(ctx: AssistCtx) -> Option<Assist> { 10pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
10 if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() { 11 if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() {
11 return change_vis(ctx, vis); 12 return change_vis(ctx, vis);
12 } 13 }
13 add_vis(ctx) 14 add_vis(ctx)
14} 15}
15 16
16fn add_vis(ctx: AssistCtx) -> Option<Assist> { 17fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
17 let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() { 18 let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() {
18 FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, 19 FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
19 _ => false, 20 _ => false,
@@ -57,7 +58,7 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit {
57 .unwrap_or(node.range().start()) 58 .unwrap_or(node.range().start())
58} 59}
59 60
60fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option<Assist> { 61fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> {
61 if vis.syntax().text() == "pub" { 62 if vis.syntax().text() == "pub" {
62 return ctx.build("chage to pub(crate)", |edit| { 63 return ctx.build("chage to pub(crate)", |edit| {
63 edit.replace(vis.syntax().range(), "pub(crate)"); 64 edit.replace(vis.syntax().range(), "pub(crate)");
@@ -76,7 +77,7 @@ fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option<Assist> {
76#[cfg(test)] 77#[cfg(test)]
77mod tests { 78mod tests {
78 use super::*; 79 use super::*;
79 use crate::assists::check_assist; 80 use crate::helpers::check_assist;
80 81
81 #[test] 82 #[test]
82 fn change_visibility_adds_pub_crate_to_items() { 83 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 000000000..9aa37d94c
--- /dev/null
+++ b/crates/ra_assists/src/fill_match_arms.rs
@@ -0,0 +1,145 @@
1use std::fmt::Write;
2
3use hir::{
4 AdtDef, Ty, FieldSource, source_binder,
5 db::HirDatabase,
6};
7use ra_syntax::ast::{self, AstNode};
8
9use crate::{AssistCtx, Assist};
10
11pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
12 let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?;
13
14 // We already have some match arms, so we don't provide any assists.
15 match match_expr.match_arm_list() {
16 Some(arm_list) if arm_list.arms().count() > 0 => {
17 return None;
18 }
19 _ => {}
20 }
21
22 let expr = match_expr.expr()?;
23 let function =
24 source_binder::function_from_child_node(ctx.db, ctx.frange.file_id, expr.syntax())?;
25 let infer_result = function.infer(ctx.db);
26 let syntax_mapping = function.body_syntax_mapping(ctx.db);
27 let node_expr = syntax_mapping.node_expr(expr)?;
28 let match_expr_ty = infer_result[node_expr].clone();
29 let enum_def = match match_expr_ty {
30 Ty::Adt {
31 def_id: AdtDef::Enum(e),
32 ..
33 } => e,
34 _ => return None,
35 };
36 let enum_name = enum_def.name(ctx.db)?;
37 let db = ctx.db;
38
39 ctx.build("fill match arms", |edit| {
40 let mut buf = format!("match {} {{\n", expr.syntax().text().to_string());
41 let variants = enum_def.variants(db);
42 for variant in variants {
43 let name = match variant.name(db) {
44 Some(it) => it,
45 None => continue,
46 };
47 write!(&mut buf, " {}::{}", enum_name, name.to_string()).unwrap();
48
49 let pat = variant
50 .fields(db)
51 .into_iter()
52 .map(|field| {
53 let name = field.name(db).to_string();
54 let (_, source) = field.source(db);
55 match source {
56 FieldSource::Named(_) => name,
57 FieldSource::Pos(_) => "_".to_string(),
58 }
59 })
60 .collect::<Vec<_>>();
61
62 match pat.first().map(|s| s.as_str()) {
63 Some("_") => write!(&mut buf, "({})", pat.join(", ")).unwrap(),
64 Some(_) => write!(&mut buf, "{{{}}}", pat.join(", ")).unwrap(),
65 None => (),
66 };
67
68 buf.push_str(" => (),\n");
69 }
70 buf.push_str("}");
71 edit.set_cursor(expr.syntax().range().start());
72 edit.replace_node_and_indent(match_expr.syntax(), buf);
73 })
74}
75
76#[cfg(test)]
77mod tests {
78 use crate::helpers::check_assist;
79
80 use super::fill_match_arms;
81
82 #[test]
83 fn fill_match_arms_empty_body() {
84 check_assist(
85 fill_match_arms,
86 r#"
87 enum A {
88 As,
89 Bs,
90 Cs(String),
91 Ds(String, String),
92 Es{x: usize, y: usize}
93 }
94
95 fn main() {
96 let a = A::As;
97 match a<|> {}
98 }
99 "#,
100 r#"
101 enum A {
102 As,
103 Bs,
104 Cs(String),
105 Ds(String, String),
106 Es{x: usize, y: usize}
107 }
108
109 fn main() {
110 let a = A::As;
111 match <|>a {
112 A::As => (),
113 A::Bs => (),
114 A::Cs(_) => (),
115 A::Ds(_, _) => (),
116 A::Es{x, y} => (),
117 }
118 }
119 "#,
120 );
121 }
122 #[test]
123 fn fill_match_arms_no_body() {
124 check_assist(
125 fill_match_arms,
126 r#"
127 enum E { X, Y}
128
129 fn main() {
130 match E::X<|>
131 }
132 "#,
133 r#"
134 enum E { X, Y}
135
136 fn main() {
137 match <|>E::X {
138 E::X => (),
139 E::Y => (),
140 }
141 }
142 "#,
143 );
144 }
145}
diff --git a/crates/ra_ide_api_light/src/assists/flip_comma.rs b/crates/ra_assists/src/flip_comma.rs
index a343413cc..a49820c29 100644
--- a/crates/ra_ide_api_light/src/assists/flip_comma.rs
+++ b/crates/ra_assists/src/flip_comma.rs
@@ -1,11 +1,12 @@
1use hir::db::HirDatabase;
1use ra_syntax::{ 2use ra_syntax::{
2 Direction, 3 Direction,
3 SyntaxKind::COMMA, 4 SyntaxKind::COMMA,
4}; 5};
5 6
6use crate::assists::{non_trivia_sibling, AssistCtx, Assist}; 7use crate::{AssistCtx, Assist, non_trivia_sibling};
7 8
8pub fn flip_comma(ctx: AssistCtx) -> Option<Assist> { 9pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
9 let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?; 10 let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?;
10 let prev = non_trivia_sibling(comma, Direction::Prev)?; 11 let prev = non_trivia_sibling(comma, Direction::Prev)?;
11 let next = non_trivia_sibling(comma, Direction::Next)?; 12 let next = non_trivia_sibling(comma, Direction::Next)?;
@@ -18,7 +19,8 @@ pub fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
18#[cfg(test)] 19#[cfg(test)]
19mod tests { 20mod tests {
20 use super::*; 21 use super::*;
21 use crate::assists::check_assist; 22
23 use crate::helpers::check_assist;
22 24
23 #[test] 25 #[test]
24 fn flip_comma_works_for_function_parameters() { 26 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
index ed13bddc4..f587b4fe6 100644
--- a/crates/ra_ide_api_light/src/assists/introduce_variable.rs
+++ b/crates/ra_assists/src/introduce_variable.rs
@@ -1,3 +1,4 @@
1use hir::db::HirDatabase;
1use ra_syntax::{ 2use ra_syntax::{
2 ast::{self, AstNode}, 3 ast::{self, AstNode},
3 SyntaxKind::{ 4 SyntaxKind::{
@@ -5,9 +6,9 @@ use ra_syntax::{
5 }, SyntaxNode, TextUnit, 6 }, SyntaxNode, TextUnit,
6}; 7};
7 8
8use crate::assists::{AssistCtx, Assist}; 9use crate::{AssistCtx, Assist};
9 10
10pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> { 11pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let node = ctx.covering_node(); 12 let node = ctx.covering_node();
12 if !valid_covering_node(node) { 13 if !valid_covering_node(node) {
13 return None; 14 return None;
@@ -60,13 +61,13 @@ fn valid_covering_node(node: &SyntaxNode) -> bool {
60/// Check wether the node is a valid expression which can be extracted to a variable. 61/// Check wether the node is a valid expression which can be extracted to a variable.
61/// In general that's true for any expression, but in some cases that would produce invalid code. 62/// In general that's true for any expression, but in some cases that would produce invalid code.
62fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> { 63fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> {
63 return match node.kind() { 64 match node.kind() {
64 PATH_EXPR => None, 65 PATH_EXPR => None,
65 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()), 66 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
66 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), 67 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
67 LOOP_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), 68 LOOP_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
68 _ => ast::Expr::cast(node), 69 _ => ast::Expr::cast(node),
69 }; 70 }
70} 71}
71 72
72/// Returns the syntax node which will follow the freshly introduced var 73/// Returns the syntax node which will follow the freshly introduced var
@@ -103,7 +104,7 @@ fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> {
103#[cfg(test)] 104#[cfg(test)]
104mod tests { 105mod tests {
105 use super::*; 106 use super::*;
106 use crate::assists::{ check_assist, check_assist_not_applicable, check_assist_range }; 107 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range};
107 108
108 #[test] 109 #[test]
109 fn test_introduce_var_simple() { 110 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 000000000..062dd8804
--- /dev/null
+++ b/crates/ra_assists/src/lib.rs
@@ -0,0 +1,170 @@
1//! `ra_assits` crate provides a bunch of code assists, aslo known as code
2//! actions (in LSP) or intentions (in IntelliJ).
3//!
4//! An assist is a micro-refactoring, which is automatically activated in
5//! certain context. For example, if the cursor is over `,`, a "swap `,`" assist
6//! becomes available.
7
8mod assist_ctx;
9
10use ra_text_edit::TextEdit;
11use ra_syntax::{TextUnit, SyntaxNode, Direction};
12use ra_db::FileRange;
13use hir::db::HirDatabase;
14
15pub(crate) use crate::assist_ctx::{AssistCtx, Assist};
16
17#[derive(Debug)]
18pub struct AssistLabel {
19 /// Short description of the assist, as shown in the UI.
20 pub label: String,
21}
22
23pub struct AssistAction {
24 pub edit: TextEdit,
25 pub cursor_position: Option<TextUnit>,
26}
27
28/// Return all the assists applicable at the given position.
29///
30/// Assists are returned in the "unresolved" state, that is only labels are
31/// returned, without actual edits.
32pub fn applicable_assists<H>(db: &H, range: FileRange) -> Vec<AssistLabel>
33where
34 H: HirDatabase + 'static,
35{
36 AssistCtx::with_ctx(db, range, false, |ctx| {
37 all_assists()
38 .iter()
39 .filter_map(|f| f(ctx.clone()))
40 .map(|a| match a {
41 Assist::Unresolved(label) => label,
42 Assist::Resolved(..) => unreachable!(),
43 })
44 .collect()
45 })
46}
47
48/// Return all the assists applicable at the given position.
49///
50/// Assists are returned in the "resolved" state, that is with edit fully
51/// computed.
52pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)>
53where
54 H: HirDatabase + 'static,
55{
56 AssistCtx::with_ctx(db, range, true, |ctx| {
57 all_assists()
58 .iter()
59 .filter_map(|f| f(ctx.clone()))
60 .map(|a| match a {
61 Assist::Resolved(label, action) => (label, action),
62 Assist::Unresolved(..) => unreachable!(),
63 })
64 .collect()
65 })
66}
67
68mod add_derive;
69mod add_impl;
70mod flip_comma;
71mod change_visibility;
72mod fill_match_arms;
73mod introduce_variable;
74mod replace_if_let_with_match;
75mod split_import;
76fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] {
77 &[
78 add_derive::add_derive,
79 add_impl::add_impl,
80 change_visibility::change_visibility,
81 fill_match_arms::fill_match_arms,
82 flip_comma::flip_comma,
83 introduce_variable::introduce_variable,
84 replace_if_let_with_match::replace_if_let_with_match,
85 split_import::split_import,
86 ]
87}
88
89fn non_trivia_sibling(node: &SyntaxNode, direction: Direction) -> Option<&SyntaxNode> {
90 node.siblings(direction)
91 .skip(1)
92 .find(|node| !node.kind().is_trivia())
93}
94
95#[cfg(test)]
96mod helpers {
97 use hir::mock::MockDatabase;
98 use ra_syntax::TextRange;
99 use ra_db::FileRange;
100 use test_utils::{extract_offset, assert_eq_text, add_cursor, extract_range};
101
102 use crate::{AssistCtx, Assist};
103
104 pub(crate) fn check_assist(
105 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
106 before: &str,
107 after: &str,
108 ) {
109 let (before_cursor_pos, before) = extract_offset(before);
110 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
111 let frange = FileRange {
112 file_id,
113 range: TextRange::offset_len(before_cursor_pos, 0.into()),
114 };
115 let assist =
116 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
117 let action = match assist {
118 Assist::Unresolved(_) => unreachable!(),
119 Assist::Resolved(_, it) => it,
120 };
121
122 let actual = action.edit.apply(&before);
123 let actual_cursor_pos = match action.cursor_position {
124 None => action
125 .edit
126 .apply_to_offset(before_cursor_pos)
127 .expect("cursor position is affected by the edit"),
128 Some(off) => off,
129 };
130 let actual = add_cursor(&actual, actual_cursor_pos);
131 assert_eq_text!(after, &actual);
132 }
133
134 pub(crate) fn check_assist_range(
135 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
136 before: &str,
137 after: &str,
138 ) {
139 let (range, before) = extract_range(before);
140 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
141 let frange = FileRange { file_id, range };
142 let assist =
143 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
144 let action = match assist {
145 Assist::Unresolved(_) => unreachable!(),
146 Assist::Resolved(_, it) => it,
147 };
148
149 let mut actual = action.edit.apply(&before);
150 if let Some(pos) = action.cursor_position {
151 actual = add_cursor(&actual, pos);
152 }
153 assert_eq_text!(after, &actual);
154 }
155
156 pub(crate) fn check_assist_not_applicable(
157 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
158 before: &str,
159 ) {
160 let (before_cursor_pos, before) = extract_offset(before);
161 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
162 let frange = FileRange {
163 file_id,
164 range: TextRange::offset_len(before_cursor_pos, 0.into()),
165 };
166 let assist = AssistCtx::with_ctx(&db, frange, true, assist);
167 assert!(assist.is_none());
168 }
169
170}
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
index 71880b919..f6af47ec9 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 @@
1use ra_syntax::{AstNode, ast}; 1use ra_syntax::{AstNode, ast};
2use ra_ide_api_light::formatting::extract_trivial_expression;
3use hir::db::HirDatabase;
2 4
3use crate::{ 5use crate::{AssistCtx, Assist};
4 assists::{AssistCtx, Assist},
5 formatting::extract_trivial_expression,
6};
7 6
8pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { 7pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
9 let if_expr: &ast::IfExpr = ctx.node_at_offset()?; 8 let if_expr: &ast::IfExpr = ctx.node_at_offset()?;
10 let cond = if_expr.condition()?; 9 let cond = if_expr.condition()?;
11 let pat = cond.pat()?; 10 let pat = cond.pat()?;
@@ -51,7 +50,7 @@ fn format_arm(block: &ast::Block) -> String {
51#[cfg(test)] 50#[cfg(test)]
52mod tests { 51mod tests {
53 use super::*; 52 use super::*;
54 use crate::assists::check_assist; 53 use crate::helpers::check_assist;
55 54
56 #[test] 55 #[test]
57 fn test_replace_if_let_with_match_unwraps_simple_expressions() { 56 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
index e4015f07d..7e34be087 100644
--- a/crates/ra_ide_api_light/src/assists/split_import.rs
+++ b/crates/ra_assists/src/split_import.rs
@@ -1,12 +1,13 @@
1use hir::db::HirDatabase;
1use ra_syntax::{ 2use ra_syntax::{
2 TextUnit, AstNode, SyntaxKind::COLONCOLON, 3 TextUnit, AstNode, SyntaxKind::COLONCOLON,
3 ast, 4 ast,
4 algo::generate, 5 algo::generate,
5}; 6};
6 7
7use crate::assists::{AssistCtx, Assist}; 8use crate::{AssistCtx, Assist};
8 9
9pub fn split_import(ctx: AssistCtx) -> Option<Assist> { 10pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
10 let colon_colon = ctx 11 let colon_colon = ctx
11 .leaf_at_offset() 12 .leaf_at_offset()
12 .find(|leaf| leaf.kind() == COLONCOLON)?; 13 .find(|leaf| leaf.kind() == COLONCOLON)?;
@@ -34,7 +35,7 @@ pub fn split_import(ctx: AssistCtx) -> Option<Assist> {
34#[cfg(test)] 35#[cfg(test)]
35mod tests { 36mod tests {
36 use super::*; 37 use super::*;
37 use crate::assists::check_assist; 38 use crate::helpers::check_assist;
38 39
39 #[test] 40 #[test]
40 fn test_split_import() { 41 fn test_split_import() {
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs
index 926cf0bd5..66634e05b 100644
--- a/crates/ra_db/src/lib.rs
+++ b/crates/ra_db/src/lib.rs
@@ -70,7 +70,7 @@ pub struct FileRange {
70/// Database which stores all significant input facts: source code and project 70/// Database which stores all significant input facts: source code and project
71/// model. Everything else in rust-analyzer is derived from these queries. 71/// model. Everything else in rust-analyzer is derived from these queries.
72#[salsa::query_group(SourceDatabaseStorage)] 72#[salsa::query_group(SourceDatabaseStorage)]
73pub trait SourceDatabase: CheckCanceled { 73pub trait SourceDatabase: CheckCanceled + std::fmt::Debug {
74 /// Text of the file. 74 /// Text of the file.
75 #[salsa::input] 75 #[salsa::input]
76 fn file_text(&self, file_id: FileId) -> Arc<String>; 76 fn file_text(&self, file_id: FileId) -> Arc<String>;
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs
index f9f702ae2..6826e966b 100644
--- a/crates/ra_hir/src/expr.rs
+++ b/crates/ra_hir/src/expr.rs
@@ -805,7 +805,7 @@ impl ExprCollector {
805 let lit = match child.flavor() { 805 let lit = match child.flavor() {
806 LiteralFlavor::IntNumber { suffix } => { 806 LiteralFlavor::IntNumber { suffix } => {
807 let known_name = suffix 807 let known_name = suffix
808 .map(|s| Name::new(s)) 808 .map(Name::new)
809 .and_then(|name| UncertainIntTy::from_name(&name)); 809 .and_then(|name| UncertainIntTy::from_name(&name));
810 810
811 Literal::Int( 811 Literal::Int(
@@ -815,7 +815,7 @@ impl ExprCollector {
815 } 815 }
816 LiteralFlavor::FloatNumber { suffix } => { 816 LiteralFlavor::FloatNumber { suffix } => {
817 let known_name = suffix 817 let known_name = suffix
818 .map(|s| Name::new(s)) 818 .map(Name::new)
819 .and_then(|name| UncertainFloatTy::from_name(&name)); 819 .and_then(|name| UncertainFloatTy::from_name(&name));
820 820
821 Literal::Float( 821 Literal::Float(
@@ -910,7 +910,7 @@ impl ExprCollector {
910 } 910 }
911 ast::PatKind::PathPat(p) => { 911 ast::PatKind::PathPat(p) => {
912 let path = p.path().and_then(Path::from_ast); 912 let path = p.path().and_then(Path::from_ast);
913 path.map(|path| Pat::Path(path)).unwrap_or(Pat::Missing) 913 path.map(Pat::Path).unwrap_or(Pat::Missing)
914 } 914 }
915 ast::PatKind::TuplePat(p) => { 915 ast::PatKind::TuplePat(p) => {
916 let args = p.args().map(|p| self.collect_pat(p)).collect(); 916 let args = p.args().map(|p| self.collect_pat(p)).collect();
diff --git a/crates/ra_hir/src/expr/scope.rs b/crates/ra_hir/src/expr/scope.rs
index 9202e3671..368994bf7 100644
--- a/crates/ra_hir/src/expr/scope.rs
+++ b/crates/ra_hir/src/expr/scope.rs
@@ -105,7 +105,7 @@ impl ExprScopes {
105 fn add_params_bindings(&mut self, scope: ScopeId, params: &[PatId]) { 105 fn add_params_bindings(&mut self, scope: ScopeId, params: &[PatId]) {
106 let body = Arc::clone(&self.body); 106 let body = Arc::clone(&self.body);
107 params 107 params
108 .into_iter() 108 .iter()
109 .for_each(|pat| self.add_bindings(&body, scope, *pat)); 109 .for_each(|pat| self.add_bindings(&body, scope, *pat));
110 } 110 }
111 111
@@ -147,7 +147,7 @@ impl ScopesWithSyntaxMapping {
147 }) 147 })
148 } 148 }
149 149
150 pub fn scope_for_offset<'a>(&'a self, offset: TextUnit) -> Option<ScopeId> { 150 pub fn scope_for_offset(&self, offset: TextUnit) -> Option<ScopeId> {
151 self.scopes 151 self.scopes
152 .scope_for 152 .scope_for
153 .iter() 153 .iter()
diff --git a/crates/ra_hir/src/impl_block.rs b/crates/ra_hir/src/impl_block.rs
index 738c58fbe..094dbedb3 100644
--- a/crates/ra_hir/src/impl_block.rs
+++ b/crates/ra_hir/src/impl_block.rs
@@ -72,7 +72,7 @@ impl ImplBlock {
72 } 72 }
73 73
74 pub fn module(&self) -> Module { 74 pub fn module(&self) -> Module {
75 self.module_impl_blocks.module.clone() 75 self.module_impl_blocks.module
76 } 76 }
77 77
78 pub fn target_trait_ref(&self) -> Option<&TypeRef> { 78 pub fn target_trait_ref(&self) -> Option<&TypeRef> {
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index 54da55598..a9cd955cf 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -18,8 +18,7 @@ macro_rules! impl_froms {
18} 18}
19 19
20pub mod db; 20pub mod db;
21#[cfg(test)] 21pub mod mock;
22mod mock;
23mod query_definitions; 22mod query_definitions;
24mod path; 23mod path;
25pub mod source_binder; 24pub mod source_binder;
diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs
index 00a07d1a1..87095fb21 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);
17 db::PersistentHirDatabaseStorage 17 db::PersistentHirDatabaseStorage
18)] 18)]
19#[derive(Debug)] 19#[derive(Debug)]
20pub(crate) struct MockDatabase { 20pub struct MockDatabase {
21 events: Mutex<Option<Vec<salsa::Event<MockDatabase>>>>, 21 events: Mutex<Option<Vec<salsa::Event<MockDatabase>>>>,
22 runtime: salsa::Runtime<MockDatabase>, 22 runtime: salsa::Runtime<MockDatabase>,
23 interner: Arc<HirInterner>, 23 interner: Arc<HirInterner>,
@@ -27,13 +27,13 @@ pub(crate) struct MockDatabase {
27impl panic::RefUnwindSafe for MockDatabase {} 27impl panic::RefUnwindSafe for MockDatabase {}
28 28
29impl MockDatabase { 29impl MockDatabase {
30 pub(crate) fn with_files(fixture: &str) -> (MockDatabase, SourceRoot) { 30 pub fn with_files(fixture: &str) -> (MockDatabase, SourceRoot) {
31 let (db, source_root, position) = MockDatabase::from_fixture(fixture); 31 let (db, source_root, position) = MockDatabase::from_fixture(fixture);
32 assert!(position.is_none()); 32 assert!(position.is_none());
33 (db, source_root) 33 (db, source_root)
34 } 34 }
35 35
36 pub(crate) fn with_single_file(text: &str) -> (MockDatabase, SourceRoot, FileId) { 36 pub fn with_single_file(text: &str) -> (MockDatabase, SourceRoot, FileId) {
37 let mut db = MockDatabase::default(); 37 let mut db = MockDatabase::default();
38 let mut source_root = SourceRoot::default(); 38 let mut source_root = SourceRoot::default();
39 let file_id = db.add_file(WORKSPACE, &mut source_root, "/main.rs", text); 39 let file_id = db.add_file(WORKSPACE, &mut source_root, "/main.rs", text);
@@ -41,7 +41,7 @@ impl MockDatabase {
41 (db, source_root, file_id) 41 (db, source_root, file_id)
42 } 42 }
43 43
44 pub(crate) fn with_position(fixture: &str) -> (MockDatabase, FilePosition) { 44 pub fn with_position(fixture: &str) -> (MockDatabase, FilePosition) {
45 let (db, _, position) = MockDatabase::from_fixture(fixture); 45 let (db, _, position) = MockDatabase::from_fixture(fixture);
46 let position = position.expect("expected a marker ( <|> )"); 46 let position = position.expect("expected a marker ( <|> )");
47 (db, position) 47 (db, position)
@@ -166,13 +166,13 @@ impl AsRef<HirInterner> for MockDatabase {
166} 166}
167 167
168impl MockDatabase { 168impl MockDatabase {
169 pub(crate) fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event<MockDatabase>> { 169 pub fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event<MockDatabase>> {
170 *self.events.lock() = Some(Vec::new()); 170 *self.events.lock() = Some(Vec::new());
171 f(); 171 f();
172 self.events.lock().take().unwrap() 172 self.events.lock().take().unwrap()
173 } 173 }
174 174
175 pub(crate) fn log_executed(&self, f: impl FnOnce()) -> Vec<String> { 175 pub fn log_executed(&self, f: impl FnOnce()) -> Vec<String> {
176 let events = self.log(f); 176 let events = self.log(f);
177 events 177 events
178 .into_iter() 178 .into_iter()
diff --git a/crates/ra_hir/src/resolve.rs b/crates/ra_hir/src/resolve.rs
index 5ca7bacb5..0f60d4742 100644
--- a/crates/ra_hir/src/resolve.rs
+++ b/crates/ra_hir/src/resolve.rs
@@ -78,7 +78,7 @@ impl Resolver {
78 _ => return PerNs::none(), 78 _ => return PerNs::none(),
79 }; 79 };
80 let module_res = item_map.resolve_path(db, module, path); 80 let module_res = item_map.resolve_path(db, module, path);
81 module_res.map(|def| Resolution::Def(def)) 81 module_res.map(Resolution::Def)
82 } 82 }
83 } 83 }
84 84
diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs
index cc5afad75..86a7f8b83 100644
--- a/crates/ra_hir/src/ty.rs
+++ b/crates/ra_hir/src/ty.rs
@@ -1225,7 +1225,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
1225 Ty::Tuple(ref tuple_args) => &**tuple_args, 1225 Ty::Tuple(ref tuple_args) => &**tuple_args,
1226 _ => &[], 1226 _ => &[],
1227 }; 1227 };
1228 let expectations_iter = expectations.into_iter().chain(repeat(&Ty::Unknown)); 1228 let expectations_iter = expectations.iter().chain(repeat(&Ty::Unknown));
1229 1229
1230 let inner_tys = args 1230 let inner_tys = args
1231 .iter() 1231 .iter()
@@ -1398,10 +1398,10 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
1398 let method_ty = self.insert_type_vars(method_ty); 1398 let method_ty = self.insert_type_vars(method_ty);
1399 let (expected_receiver_ty, param_tys, ret_ty) = match &method_ty { 1399 let (expected_receiver_ty, param_tys, ret_ty) = match &method_ty {
1400 Ty::FnPtr(sig) => { 1400 Ty::FnPtr(sig) => {
1401 if sig.input.len() > 0 { 1401 if !sig.input.is_empty() {
1402 ( 1402 (
1403 sig.input[0].clone(), 1403 sig.input[0].clone(),
1404 sig.input[1..].iter().cloned().collect(), 1404 sig.input[1..].to_vec(),
1405 sig.output.clone(), 1405 sig.output.clone(),
1406 ) 1406 )
1407 } else { 1407 } else {
@@ -1411,7 +1411,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
1411 Ty::FnDef { substs, sig, .. } => { 1411 Ty::FnDef { substs, sig, .. } => {
1412 let ret_ty = sig.output.clone().subst(&substs); 1412 let ret_ty = sig.output.clone().subst(&substs);
1413 1413
1414 if sig.input.len() > 0 { 1414 if !sig.input.is_empty() {
1415 let mut arg_iter = sig.input.iter().map(|ty| ty.clone().subst(&substs)); 1415 let mut arg_iter = sig.input.iter().map(|ty| ty.clone().subst(&substs));
1416 let receiver_ty = arg_iter.next().unwrap(); 1416 let receiver_ty = arg_iter.next().unwrap();
1417 (receiver_ty, arg_iter.collect(), ret_ty) 1417 (receiver_ty, arg_iter.collect(), ret_ty)
diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs
index 2282286b0..a7d4517ee 100644
--- a/crates/ra_hir/src/ty/method_resolution.rs
+++ b/crates/ra_hir/src/ty/method_resolution.rs
@@ -113,7 +113,7 @@ impl CrateImplBlocks {
113 krate: Crate, 113 krate: Crate,
114 ) -> Arc<CrateImplBlocks> { 114 ) -> Arc<CrateImplBlocks> {
115 let mut crate_impl_blocks = CrateImplBlocks { 115 let mut crate_impl_blocks = CrateImplBlocks {
116 krate: krate.clone(), 116 krate,
117 impls: FxHashMap::default(), 117 impls: FxHashMap::default(),
118 impls_by_trait: FxHashMap::default(), 118 impls_by_trait: FxHashMap::default(),
119 }; 119 };
diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml
index 54de9b2e3..95cccf8cf 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" }
24ra_db = { path = "../ra_db" } 24ra_db = { path = "../ra_db" }
25hir = { path = "../ra_hir", package = "ra_hir" } 25hir = { path = "../ra_hir", package = "ra_hir" }
26test_utils = { path = "../test_utils" } 26test_utils = { path = "../test_utils" }
27ra_assists = { path = "../ra_assists" }
27 28
28[dev-dependencies] 29[dev-dependencies]
29insta = "0.6.1" 30insta = "0.6.1"
diff --git a/crates/ra_ide_api/src/assists.rs b/crates/ra_ide_api/src/assists.rs
index 2da251df5..2a96fdf47 100644
--- a/crates/ra_ide_api/src/assists.rs
+++ b/crates/ra_ide_api/src/assists.rs
@@ -1,89 +1,24 @@
1mod fill_match_arm; 1use ra_db::{FileRange, FilePosition};
2 2
3use ra_syntax::{ 3use crate::{SourceFileEdit, SourceChange, db::RootDatabase};
4 TextRange, SourceFile, AstNode, 4
5 algo::find_node_at_offset, 5pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<SourceChange> {
6}; 6 ra_assists::assists(db, frange)
7use ra_ide_api_light::{ 7 .into_iter()
8 LocalEdit, 8 .map(|(label, action)| {
9 assists::{ 9 let file_id = frange.file_id;
10 Assist, 10 let file_edit = SourceFileEdit {
11 AssistBuilder 11 file_id,
12 } 12 edit: action.edit,
13}; 13 };
14use crate::{ 14 SourceChange {
15 db::RootDatabase, 15 label: label.label,
16 FileId 16 source_file_edits: vec![file_edit],
17}; 17 file_system_edits: vec![],
18 18 cursor_position: action
19/// Return all the assists applicable at the given position. 19 .cursor_position
20pub(crate) fn assists( 20 .map(|offset| FilePosition { offset, file_id }),
21 db: &RootDatabase, 21 }
22 file_id: FileId, 22 })
23 file: &SourceFile,
24 range: TextRange,
25) -> Vec<LocalEdit> {
26 let ctx = AssistCtx::new(db, file_id, file, range);
27 [fill_match_arm::fill_match_arm]
28 .iter()
29 .filter_map(|&assist| ctx.clone().apply(assist))
30 .collect() 23 .collect()
31} 24}
32
33#[derive(Debug, Clone)]
34pub struct AssistCtx<'a> {
35 file_id: FileId,
36 source_file: &'a SourceFile,
37 db: &'a RootDatabase,
38 range: TextRange,
39 should_compute_edit: bool,
40}
41
42impl<'a> AssistCtx<'a> {
43 pub(crate) fn new(
44 db: &'a RootDatabase,
45 file_id: FileId,
46 source_file: &'a SourceFile,
47 range: TextRange,
48 ) -> AssistCtx<'a> {
49 AssistCtx {
50 source_file,
51 file_id,
52 db,
53 range,
54 should_compute_edit: false,
55 }
56 }
57
58 pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> {
59 self.should_compute_edit = true;
60 match assist(self) {
61 None => None,
62 Some(Assist::Edit(e)) => Some(e),
63 Some(Assist::Applicable) => unreachable!(),
64 }
65 }
66
67 #[allow(unused)]
68 pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool {
69 self.should_compute_edit = false;
70 match assist(self) {
71 None => false,
72 Some(Assist::Edit(_)) => unreachable!(),
73 Some(Assist::Applicable) => true,
74 }
75 }
76
77 fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> {
78 if !self.should_compute_edit {
79 return Some(Assist::Applicable);
80 }
81 let mut edit = AssistBuilder::default();
82 f(&mut edit);
83 Some(edit.build(label))
84 }
85
86 pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> {
87 find_node_at_offset(self.source_file.syntax(), self.range.start())
88 }
89}
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 6ae829d85..000000000
--- a/crates/ra_ide_api/src/assists/fill_match_arm.rs
+++ /dev/null
@@ -1,157 +0,0 @@
1use std::fmt::Write;
2use hir::{
3 AdtDef,
4 source_binder,
5 Ty,
6 FieldSource,
7};
8use ra_ide_api_light::{
9 assists::{
10 Assist,
11 AssistBuilder
12 }
13};
14use ra_syntax::{
15 ast::{
16 self,
17 AstNode,
18 }
19};
20
21use crate::assists::AssistCtx;
22
23pub fn fill_match_arm(ctx: AssistCtx) -> Option<Assist> {
24 let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?;
25
26 // We already have some match arms, so we don't provide any assists.
27 match match_expr.match_arm_list() {
28 Some(arm_list) if arm_list.arms().count() > 0 => {
29 return None;
30 }
31 _ => {}
32 }
33
34 let expr = match_expr.expr()?;
35 let function = source_binder::function_from_child_node(ctx.db, ctx.file_id, expr.syntax())?;
36 let infer_result = function.infer(ctx.db);
37 let syntax_mapping = function.body_syntax_mapping(ctx.db);
38 let node_expr = syntax_mapping.node_expr(expr)?;
39 let match_expr_ty = infer_result[node_expr].clone();
40 match match_expr_ty {
41 Ty::Adt { def_id, .. } => match def_id {
42 AdtDef::Enum(e) => {
43 let mut buf = format!("match {} {{\n", expr.syntax().text().to_string());
44 let variants = e.variants(ctx.db);
45 for variant in variants {
46 let name = variant.name(ctx.db)?;
47 write!(
48 &mut buf,
49 " {}::{}",
50 e.name(ctx.db)?.to_string(),
51 name.to_string()
52 )
53 .expect("write fmt");
54
55 let pat = variant
56 .fields(ctx.db)
57 .into_iter()
58 .map(|field| {
59 let name = field.name(ctx.db).to_string();
60 let (_, source) = field.source(ctx.db);
61 match source {
62 FieldSource::Named(_) => name,
63 FieldSource::Pos(_) => "_".to_string(),
64 }
65 })
66 .collect::<Vec<_>>();
67
68 match pat.first().map(|s| s.as_str()) {
69 Some("_") => write!(&mut buf, "({})", pat.join(", ")).expect("write fmt"),
70 Some(_) => write!(&mut buf, "{{{}}}", pat.join(", ")).expect("write fmt"),
71 None => (),
72 };
73
74 buf.push_str(" => (),\n");
75 }
76 buf.push_str("}");
77 ctx.build("fill match arms", |edit: &mut AssistBuilder| {
78 edit.replace_node_and_indent(match_expr.syntax(), buf);
79 })
80 }
81 _ => None,
82 },
83 _ => None,
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use insta::assert_debug_snapshot_matches;
90
91 use ra_syntax::{TextRange, TextUnit};
92
93 use crate::{
94 FileRange,
95 mock_analysis::{analysis_and_position, single_file_with_position}
96};
97 use ra_db::SourceDatabase;
98
99 fn test_assit(name: &str, code: &str) {
100 let (analysis, position) = if code.contains("//-") {
101 analysis_and_position(code)
102 } else {
103 single_file_with_position(code)
104 };
105 let frange = FileRange {
106 file_id: position.file_id,
107 range: TextRange::offset_len(position.offset, TextUnit::from(1)),
108 };
109 let source_file = analysis
110 .with_db(|db| db.parse(frange.file_id))
111 .expect("source file");
112 let ret = analysis
113 .with_db(|db| crate::assists::assists(db, frange.file_id, &source_file, frange.range))
114 .expect("assists");
115
116 assert_debug_snapshot_matches!(name, ret);
117 }
118
119 #[test]
120 fn test_fill_match_arm() {
121 test_assit(
122 "fill_match_arm1",
123 r#"
124 enum A {
125 As,
126 Bs,
127 Cs(String),
128 Ds(String, String),
129 Es{x: usize, y: usize}
130 }
131
132 fn main() {
133 let a = A::As;
134 match a<|>
135 }
136 "#,
137 );
138
139 test_assit(
140 "fill_match_arm2",
141 r#"
142 enum A {
143 As,
144 Bs,
145 Cs(String),
146 Ds(String, String),
147 Es{x: usize, y: usize}
148 }
149
150 fn main() {
151 let a = A::As;
152 match a<|> {}
153 }
154 "#,
155 );
156 }
157}
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 980726d92..000000000
--- a/crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm1.snap
+++ /dev/null
@@ -1,20 +0,0 @@
1---
2created: "2019-02-03T15:38:46.094184+00:00"
3creator: [email protected]
4expression: ret
5source: crates/ra_ide_api/src/assits/fill_match_arm.rs
6---
7[
8 LocalEdit {
9 label: "fill match arms",
10 edit: TextEdit {
11 atoms: [
12 AtomTextEdit {
13 delete: [211; 218),
14 insert: "match a {\n A::As => (),\n A::Bs => (),\n A::Cs(_) => (),\n A::Ds(_, _) => (),\n A::Es{x, y} => (),\n }"
15 }
16 ]
17 },
18 cursor_position: None
19 }
20]
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 cee0efe74..000000000
--- a/crates/ra_ide_api/src/assists/snapshots/tests__fill_match_arm2.snap
+++ /dev/null
@@ -1,20 +0,0 @@
1---
2created: "2019-02-03T15:41:34.640074+00:00"
3creator: [email protected]
4expression: ret
5source: crates/ra_ide_api/src/assits/fill_match_arm.rs
6---
7[
8 LocalEdit {
9 label: "fill match arms",
10 edit: TextEdit {
11 atoms: [
12 AtomTextEdit {
13 delete: [211; 221),
14 insert: "match a {\n A::As => (),\n A::Bs => (),\n A::Cs(_) => (),\n A::Ds(_, _) => (),\n A::Es{x, y} => (),\n }"
15 }
16 ]
17 },
18 cursor_position: None
19 }
20]
diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs
index 5d1851da6..8abab0221 100644
--- a/crates/ra_ide_api/src/completion/completion_context.rs
+++ b/crates/ra_ide_api/src/completion/completion_context.rs
@@ -130,12 +130,9 @@ impl<'a> CompletionContext<'a> {
130 .ancestors() 130 .ancestors()
131 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 131 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
132 .find_map(ast::FnDef::cast); 132 .find_map(ast::FnDef::cast);
133 match (self.module, self.function_syntax) { 133 if let (Some(module), Some(fn_def)) = (self.module, self.function_syntax) {
134 (Some(module), Some(fn_def)) => { 134 let function = source_binder::function_from_module(self.db, module, fn_def);
135 let function = source_binder::function_from_module(self.db, module, fn_def); 135 self.function = Some(function);
136 self.function = Some(function);
137 }
138 _ => (),
139 } 136 }
140 137
141 let parent = match name_ref.syntax().parent() { 138 let parent = match name_ref.syntax().parent() {
diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs
index bada6a33b..92e6e78bf 100644
--- a/crates/ra_ide_api/src/completion/completion_item.rs
+++ b/crates/ra_ide_api/src/completion/completion_item.rs
@@ -108,11 +108,11 @@ impl CompletionItem {
108 self.lookup 108 self.lookup
109 .as_ref() 109 .as_ref()
110 .map(|it| it.as_str()) 110 .map(|it| it.as_str())
111 .unwrap_or(self.label()) 111 .unwrap_or_else(|| self.label())
112 } 112 }
113 113
114 pub fn insert_text_format(&self) -> InsertTextFormat { 114 pub fn insert_text_format(&self) -> InsertTextFormat {
115 self.insert_text_format.clone() 115 self.insert_text_format
116 } 116 }
117 pub fn insert_text(&self) -> String { 117 pub fn insert_text(&self) -> String {
118 match &self.insert_text { 118 match &self.insert_text {
@@ -217,7 +217,7 @@ impl Builder {
217 let def = resolution 217 let def = resolution
218 .as_ref() 218 .as_ref()
219 .take_types() 219 .take_types()
220 .or(resolution.as_ref().take_values()); 220 .or_else(|| resolution.as_ref().take_values());
221 let def = match def { 221 let def = match def {
222 None => return self, 222 None => return self,
223 Some(it) => it, 223 Some(it) => it,
diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs
index 88efcea2a..681f36623 100644
--- a/crates/ra_ide_api/src/goto_definition.rs
+++ b/crates/ra_ide_api/src/goto_definition.rs
@@ -89,7 +89,11 @@ pub(crate) fn reference_definition(
89 .and_then(hir::Path::from_ast) 89 .and_then(hir::Path::from_ast)
90 { 90 {
91 let resolved = resolver.resolve_path(db, &path); 91 let resolved = resolver.resolve_path(db, &path);
92 match resolved.clone().take_types().or(resolved.take_values()) { 92 match resolved
93 .clone()
94 .take_types()
95 .or_else(|| resolved.take_values())
96 {
93 Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)), 97 Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)),
94 Some(Resolution::LocalBinding(pat)) => { 98 Some(Resolution::LocalBinding(pat)) => {
95 let body = resolver.body().expect("no body for local binding"); 99 let body = resolver.body().expect("no body for local binding");
diff --git a/crates/ra_ide_api/src/imp.rs b/crates/ra_ide_api/src/imp.rs
index fd8637ad2..b139efabf 100644
--- a/crates/ra_ide_api/src/imp.rs
+++ b/crates/ra_ide_api/src/imp.rs
@@ -19,7 +19,7 @@ use ra_syntax::{
19 19
20use crate::{ 20use crate::{
21 AnalysisChange, 21 AnalysisChange,
22 CrateId, db, Diagnostic, FileId, FilePosition, FileRange, FileSystemEdit, 22 CrateId, db, Diagnostic, FileId, FilePosition, FileSystemEdit,
23 Query, RootChange, SourceChange, SourceFileEdit, 23 Query, RootChange, SourceChange, SourceFileEdit,
24 symbol_index::{FileSymbol, SymbolsDatabase}, 24 symbol_index::{FileSymbol, SymbolsDatabase},
25 status::syntax_tree_stats 25 status::syntax_tree_stats
@@ -236,15 +236,6 @@ impl db::RootDatabase {
236 res 236 res
237 } 237 }
238 238
239 pub(crate) fn assists(&self, frange: FileRange) -> Vec<SourceChange> {
240 let file = self.parse(frange.file_id);
241 ra_ide_api_light::assists::assists(&file, frange.range)
242 .into_iter()
243 .chain(crate::assists::assists(self, frange.file_id, &file, frange.range).into_iter())
244 .map(|local_edit| SourceChange::from_local_edit(frange.file_id, local_edit))
245 .collect()
246 }
247
248 pub(crate) fn index_resolve(&self, name_ref: &ast::NameRef) -> Vec<FileSymbol> { 239 pub(crate) fn index_resolve(&self, name_ref: &ast::NameRef) -> Vec<FileSymbol> {
249 let name = name_ref.text(); 240 let name = name_ref.text();
250 let mut query = Query::new(name.to_string()); 241 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 3a187d7a5..68d59aae1 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -117,7 +117,7 @@ impl fmt::Debug for AnalysisChange {
117 if !self.libraries_added.is_empty() { 117 if !self.libraries_added.is_empty() {
118 d.field("libraries_added", &self.libraries_added.len()); 118 d.field("libraries_added", &self.libraries_added.len());
119 } 119 }
120 if !self.crate_graph.is_some() { 120 if self.crate_graph.is_none() {
121 d.field("crate_graph", &self.crate_graph); 121 d.field("crate_graph", &self.crate_graph);
122 } 122 }
123 d.finish() 123 d.finish()
@@ -477,7 +477,7 @@ impl Analysis {
477 /// Computes assists (aks code actons aka intentions) for the given 477 /// Computes assists (aks code actons aka intentions) for the given
478 /// position. 478 /// position.
479 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<SourceChange>> { 479 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<SourceChange>> {
480 self.with_db(|db| db.assists(frange)) 480 self.with_db(|db| assists::assists(db, frange))
481 } 481 }
482 482
483 /// Computes the set of diagnostics for the given file. 483 /// Computes the set of diagnostics for the given file.
diff --git a/crates/ra_ide_api/src/rename.rs b/crates/ra_ide_api/src/rename.rs
index db5ccf969..1c9491a0a 100644
--- a/crates/ra_ide_api/src/rename.rs
+++ b/crates/ra_ide_api/src/rename.rs
@@ -95,12 +95,12 @@ fn rename_mod(
95 }; 95 };
96 source_file_edits.push(edit); 96 source_file_edits.push(edit);
97 97
98 return Some(SourceChange { 98 Some(SourceChange {
99 label: "rename".to_string(), 99 label: "rename".to_string(),
100 source_file_edits, 100 source_file_edits,
101 file_system_edits, 101 file_system_edits,
102 cursor_position: None, 102 cursor_position: None,
103 }); 103 })
104} 104}
105 105
106fn rename_reference( 106fn rename_reference(
@@ -124,12 +124,12 @@ fn rename_reference(
124 return None; 124 return None;
125 } 125 }
126 126
127 return Some(SourceChange { 127 Some(SourceChange {
128 label: "rename".to_string(), 128 label: "rename".to_string(),
129 source_file_edits: edit, 129 source_file_edits: edit,
130 file_system_edits: Vec::new(), 130 file_system_edits: Vec::new(),
131 cursor_position: None, 131 cursor_position: None,
132 }); 132 })
133} 133}
134 134
135#[cfg(test)] 135#[cfg(test)]
diff --git a/crates/ra_ide_api/src/symbol_index.rs b/crates/ra_ide_api/src/symbol_index.rs
index 72c93f530..9f939c650 100644
--- a/crates/ra_ide_api/src/symbol_index.rs
+++ b/crates/ra_ide_api/src/symbol_index.rs
@@ -137,7 +137,7 @@ impl SymbolIndex {
137 symbols.par_sort_by(cmp); 137 symbols.par_sort_by(cmp);
138 symbols.dedup_by(|s1, s2| cmp(s1, s2) == Ordering::Equal); 138 symbols.dedup_by(|s1, s2| cmp(s1, s2) == Ordering::Equal);
139 let names = symbols.iter().map(|it| it.name.as_str().to_lowercase()); 139 let names = symbols.iter().map(|it| it.name.as_str().to_lowercase());
140 let map = fst::Map::from_iter(names.into_iter().zip(0u64..)).unwrap(); 140 let map = fst::Map::from_iter(names.zip(0u64..)).unwrap();
141 SymbolIndex { symbols, map } 141 SymbolIndex { symbols, map }
142 } 142 }
143 143
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 e578805f1..000000000
--- a/crates/ra_ide_api_light/src/assists.rs
+++ /dev/null
@@ -1,215 +0,0 @@
1//! This modules contains various "assists": suggestions for source code edits
2//! which are likely to occur at a given cursor position. For example, if the
3//! cursor is on the `,`, a possible assist is swapping the elements around the
4//! comma.
5
6mod flip_comma;
7mod add_derive;
8mod add_impl;
9mod introduce_variable;
10mod change_visibility;
11mod split_import;
12mod replace_if_let_with_match;
13
14use ra_text_edit::{TextEdit, TextEditBuilder};
15use ra_syntax::{
16 Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode,
17 algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset},
18};
19use itertools::Itertools;
20
21use crate::formatting::leading_indent;
22
23pub use self::{
24 flip_comma::flip_comma,
25 add_derive::add_derive,
26 add_impl::add_impl,
27 introduce_variable::introduce_variable,
28 change_visibility::change_visibility,
29 split_import::split_import,
30 replace_if_let_with_match::replace_if_let_with_match,
31};
32
33/// Return all the assists applicable at the given position.
34pub fn assists(file: &SourceFile, range: TextRange) -> Vec<LocalEdit> {
35 let ctx = AssistCtx::new(file, range);
36 [
37 flip_comma,
38 add_derive,
39 add_impl,
40 introduce_variable,
41 change_visibility,
42 split_import,
43 replace_if_let_with_match,
44 ]
45 .iter()
46 .filter_map(|&assist| ctx.clone().apply(assist))
47 .collect()
48}
49
50#[derive(Debug)]
51pub struct LocalEdit {
52 pub label: String,
53 pub edit: TextEdit,
54 pub cursor_position: Option<TextUnit>,
55}
56
57fn non_trivia_sibling(node: &SyntaxNode, direction: Direction) -> Option<&SyntaxNode> {
58 node.siblings(direction)
59 .skip(1)
60 .find(|node| !node.kind().is_trivia())
61}
62
63/// `AssistCtx` allows to apply an assist or check if it could be applied.
64///
65/// Assists use a somewhat overengineered approach, given the current needs. The
66/// assists workflow consists of two phases. In the first phase, a user asks for
67/// the list of available assists. In the second phase, the user picks a
68/// particular assist and it gets applied.
69///
70/// There are two peculiarities here:
71///
72/// * first, we ideally avoid computing more things then necessary to answer
73/// "is assist applicable" in the first phase.
74/// * second, when we are applying assist, we don't have a guarantee that there
75/// weren't any changes between the point when user asked for assists and when
76/// they applied a particular assist. So, when applying assist, we need to do
77/// all the checks from scratch.
78///
79/// To avoid repeating the same code twice for both "check" and "apply"
80/// functions, we use an approach reminiscent of that of Django's function based
81/// views dealing with forms. Each assist receives a runtime parameter,
82/// `should_compute_edit`. It first check if an edit is applicable (potentially
83/// computing info required to compute the actual edit). If it is applicable,
84/// and `should_compute_edit` is `true`, it then computes the actual edit.
85///
86/// So, to implement the original assists workflow, we can first apply each edit
87/// with `should_compute_edit = false`, and then applying the selected edit
88/// again, with `should_compute_edit = true` this time.
89///
90/// Note, however, that we don't actually use such two-phase logic at the
91/// moment, because the LSP API is pretty awkward in this place, and it's much
92/// easier to just compute the edit eagerly :-)
93#[derive(Debug, Clone)]
94pub struct AssistCtx<'a> {
95 source_file: &'a SourceFile,
96 range: TextRange,
97 should_compute_edit: bool,
98}
99
100#[derive(Debug)]
101pub enum Assist {
102 Applicable,
103 Edit(LocalEdit),
104}
105
106#[derive(Default)]
107pub struct AssistBuilder {
108 edit: TextEditBuilder,
109 cursor_position: Option<TextUnit>,
110}
111
112impl<'a> AssistCtx<'a> {
113 pub fn new(source_file: &'a SourceFile, range: TextRange) -> AssistCtx {
114 AssistCtx {
115 source_file,
116 range,
117 should_compute_edit: false,
118 }
119 }
120
121 pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> {
122 self.should_compute_edit = true;
123 match assist(self) {
124 None => None,
125 Some(Assist::Edit(e)) => Some(e),
126 Some(Assist::Applicable) => unreachable!(),
127 }
128 }
129
130 pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool {
131 self.should_compute_edit = false;
132 match assist(self) {
133 None => false,
134 Some(Assist::Edit(_)) => unreachable!(),
135 Some(Assist::Applicable) => true,
136 }
137 }
138
139 fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> {
140 if !self.should_compute_edit {
141 return Some(Assist::Applicable);
142 }
143 let mut edit = AssistBuilder::default();
144 f(&mut edit);
145 Some(edit.build(label))
146 }
147
148 pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> {
149 find_leaf_at_offset(self.source_file.syntax(), self.range.start())
150 }
151 pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> {
152 find_node_at_offset(self.source_file.syntax(), self.range.start())
153 }
154 pub(crate) fn covering_node(&self) -> &'a SyntaxNode {
155 find_covering_node(self.source_file.syntax(), self.range)
156 }
157}
158
159impl AssistBuilder {
160 fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
161 self.edit.replace(range, replace_with.into())
162 }
163 pub fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) {
164 let mut replace_with = replace_with.into();
165 if let Some(indent) = leading_indent(node) {
166 replace_with = reindent(&replace_with, indent)
167 }
168 self.replace(node.range(), replace_with)
169 }
170 #[allow(unused)]
171 fn delete(&mut self, range: TextRange) {
172 self.edit.delete(range)
173 }
174 fn insert(&mut self, offset: TextUnit, text: impl Into<String>) {
175 self.edit.insert(offset, text.into())
176 }
177 fn set_cursor(&mut self, offset: TextUnit) {
178 self.cursor_position = Some(offset)
179 }
180 pub fn build(self, label: impl Into<String>) -> Assist {
181 Assist::Edit(LocalEdit {
182 label: label.into(),
183 cursor_position: self.cursor_position,
184 edit: self.edit.finish(),
185 })
186 }
187}
188
189fn reindent(text: &str, indent: &str) -> String {
190 let indent = format!("\n{}", indent);
191 text.lines().intersperse(&indent).collect()
192}
193
194#[cfg(test)]
195fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) {
196 crate::test_utils::check_action(before, after, |file, off| {
197 let range = TextRange::offset_len(off, 0.into());
198 AssistCtx::new(file, range).apply(assist)
199 })
200}
201
202#[cfg(test)]
203fn check_assist_not_applicable(assist: fn(AssistCtx) -> Option<Assist>, text: &str) {
204 crate::test_utils::check_action_not_applicable(text, |file, off| {
205 let range = TextRange::offset_len(off, 0.into());
206 AssistCtx::new(file, range).apply(assist)
207 })
208}
209
210#[cfg(test)]
211fn check_assist_range(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) {
212 crate::test_utils::check_action_range(before, after, |file, range| {
213 AssistCtx::new(file, range).apply(assist)
214 })
215}
diff --git a/crates/ra_ide_api_light/src/formatting.rs b/crates/ra_ide_api_light/src/formatting.rs
index 1f34b85d6..46ffa7d96 100644
--- a/crates/ra_ide_api_light/src/formatting.rs
+++ b/crates/ra_ide_api_light/src/formatting.rs
@@ -1,3 +1,4 @@
1use itertools::Itertools;
1use ra_syntax::{ 2use ra_syntax::{
2 AstNode, 3 AstNode,
3 SyntaxNode, SyntaxKind::*, 4 SyntaxNode, SyntaxKind::*,
@@ -5,8 +6,13 @@ use ra_syntax::{
5 algo::generate, 6 algo::generate,
6}; 7};
7 8
9pub fn reindent(text: &str, indent: &str) -> String {
10 let indent = format!("\n{}", indent);
11 text.lines().intersperse(&indent).collect()
12}
13
8/// If the node is on the beginning of the line, calculate indent. 14/// If the node is on the beginning of the line, calculate indent.
9pub(crate) fn leading_indent(node: &SyntaxNode) -> Option<&str> { 15pub fn leading_indent(node: &SyntaxNode) -> Option<&str> {
10 for leaf in prev_leaves(node) { 16 for leaf in prev_leaves(node) {
11 if let Some(ws) = ast::Whitespace::cast(leaf) { 17 if let Some(ws) = ast::Whitespace::cast(leaf) {
12 let ws_text = ws.text(); 18 let ws_text = ws.text();
@@ -32,7 +38,7 @@ fn prev_leaf(node: &SyntaxNode) -> Option<&SyntaxNode> {
32 .last() 38 .last()
33} 39}
34 40
35pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> { 41pub fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> {
36 let expr = block.expr()?; 42 let expr = block.expr()?;
37 if expr.syntax().text().contains('\n') { 43 if expr.syntax().text().contains('\n') {
38 return None; 44 return None;
diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs
index 9dd72701d..17044270c 100644
--- a/crates/ra_ide_api_light/src/lib.rs
+++ b/crates/ra_ide_api_light/src/lib.rs
@@ -3,7 +3,7 @@
3//! This usually means functions which take syntax tree as an input and produce 3//! This usually means functions which take syntax tree as an input and produce
4//! an edit or some auxiliary info. 4//! an edit or some auxiliary info.
5 5
6pub mod assists; 6pub mod formatting;
7mod extend_selection; 7mod extend_selection;
8mod folding_ranges; 8mod folding_ranges;
9mod line_index; 9mod line_index;
@@ -14,10 +14,15 @@ mod test_utils;
14mod join_lines; 14mod join_lines;
15mod typing; 15mod typing;
16mod diagnostics; 16mod diagnostics;
17pub(crate) mod formatting; 17
18#[derive(Debug)]
19pub struct LocalEdit {
20 pub label: String,
21 pub edit: ra_text_edit::TextEdit,
22 pub cursor_position: Option<TextUnit>,
23}
18 24
19pub use self::{ 25pub use self::{
20 assists::LocalEdit,
21 extend_selection::extend_selection, 26 extend_selection::extend_selection,
22 folding_ranges::{folding_ranges, Fold, FoldKind}, 27 folding_ranges::{folding_ranges, Fold, FoldKind},
23 line_index::{LineCol, LineIndex}, 28 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 22ded2435..bfac0fce3 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 @@
1use ra_syntax::{SourceFile, TextRange, TextUnit}; 1use ra_syntax::{SourceFile, TextUnit};
2 2
3use crate::LocalEdit; 3use crate::LocalEdit;
4pub use test_utils::*; 4pub use test_utils::*;
@@ -22,32 +22,3 @@ pub fn check_action<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>(
22 let actual = add_cursor(&actual, actual_cursor_pos); 22 let actual = add_cursor(&actual, actual_cursor_pos);
23 assert_eq_text!(after, &actual); 23 assert_eq_text!(after, &actual);
24} 24}
25
26pub fn check_action_not_applicable<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>(
27 text: &str,
28 f: F,
29) {
30 let (text_cursor_pos, text) = extract_offset(text);
31 let file = SourceFile::parse(&text);
32 assert!(
33 f(&file, text_cursor_pos).is_none(),
34 "code action is applicable but it shouldn't"
35 );
36}
37
38pub fn check_action_range<F: Fn(&SourceFile, TextRange) -> Option<LocalEdit>>(
39 before: &str,
40 after: &str,
41 f: F,
42) {
43 let (range, before) = extract_range(before);
44 let file = SourceFile::parse(&before);
45 let result = f(&file, range).expect("code action is not applicable");
46 let actual = result.edit.apply(&before);
47 let actual_cursor_pos = match result.cursor_position {
48 None => result.edit.apply_to_offset(range.start()).unwrap(),
49 Some(off) => off,
50 };
51 let actual = add_cursor(&actual, actual_cursor_pos);
52 assert_eq_text!(after, &actual);
53}
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs
index 17fa07340..981385466 100644
--- a/crates/ra_lsp_server/src/conv.rs
+++ b/crates/ra_lsp_server/src/conv.rs
@@ -169,10 +169,7 @@ impl ConvWith for TextEdit {
169 type Output = Vec<lsp_types::TextEdit>; 169 type Output = Vec<lsp_types::TextEdit>;
170 170
171 fn conv_with(self, line_index: &LineIndex) -> Vec<lsp_types::TextEdit> { 171 fn conv_with(self, line_index: &LineIndex) -> Vec<lsp_types::TextEdit> {
172 self.as_atoms() 172 self.as_atoms().iter().map_conv_with(line_index).collect()
173 .into_iter()
174 .map_conv_with(line_index)
175 .collect()
176 } 173 }
177} 174}
178 175
@@ -394,7 +391,7 @@ pub fn to_location_link(
394 origin_selection_range: Some(target.range.conv_with(line_index)), 391 origin_selection_range: Some(target.range.conv_with(line_index)),
395 target_uri, 392 target_uri,
396 target_range, 393 target_range,
397 target_selection_range: target_selection_range, 394 target_selection_range,
398 }; 395 };
399 Ok(res) 396 Ok(res)
400} 397}
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index ab2b81bf0..aa55d1255 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -123,7 +123,7 @@ pub fn handle_on_type_formatting(
123 let edit = edit.source_file_edits.pop().unwrap(); 123 let edit = edit.source_file_edits.pop().unwrap();
124 124
125 let change: Vec<TextEdit> = edit.edit.conv_with(&line_index); 125 let change: Vec<TextEdit> = edit.edit.conv_with(&line_index);
126 return Ok(Some(change)); 126 Ok(Some(change))
127} 127}
128 128
129pub fn handle_document_symbol( 129pub fn handle_document_symbol(
@@ -319,7 +319,7 @@ pub fn handle_runnables(
319 args: check_args, 319 args: check_args,
320 env: FxHashMap::default(), 320 env: FxHashMap::default(),
321 }); 321 });
322 return Ok(res); 322 Ok(res)
323} 323}
324 324
325pub fn handle_decorations( 325pub fn handle_decorations(
@@ -622,10 +622,8 @@ pub fn handle_code_lens(
622 // Gather runnables 622 // Gather runnables
623 for runnable in world.analysis().runnables(file_id)? { 623 for runnable in world.analysis().runnables(file_id)? {
624 let title = match &runnable.kind { 624 let title = match &runnable.kind {
625 RunnableKind::Test { name: _ } | RunnableKind::TestMod { path: _ } => { 625 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => Some("▶️Run Test"),
626 Some("▶️Run Test") 626 RunnableKind::Bench { .. } => Some("Run Bench"),
627 }
628 RunnableKind::Bench { name: _ } => Some("Run Bench"),
629 _ => None, 627 _ => None,
630 }; 628 };
631 629
@@ -679,7 +677,7 @@ pub fn handle_code_lens(
679 }), 677 }),
680 ); 678 );
681 679
682 return Ok(Some(lenses)); 680 Ok(Some(lenses))
683} 681}
684 682
685#[derive(Debug, Serialize, Deserialize)] 683#[derive(Debug, Serialize, Deserialize)]
@@ -722,22 +720,20 @@ pub fn handle_code_lens_resolve(world: ServerWorld, code_lens: CodeLens) -> Resu
722 to_value(locations).unwrap(), 720 to_value(locations).unwrap(),
723 ]), 721 ]),
724 }; 722 };
725 return Ok(CodeLens { 723 Ok(CodeLens {
726 range: code_lens.range, 724 range: code_lens.range,
727 command: Some(cmd), 725 command: Some(cmd),
728 data: None, 726 data: None,
729 }); 727 })
730 }
731 None => {
732 return Ok(CodeLens {
733 range: code_lens.range,
734 command: Some(Command {
735 title: "Error".into(),
736 ..Default::default()
737 }),
738 data: None,
739 });
740 } 728 }
729 None => Ok(CodeLens {
730 range: code_lens.range,
731 command: Some(Command {
732 title: "Error".into(),
733 ..Default::default()
734 }),
735 data: None,
736 }),
741 } 737 }
742} 738}
743 739