diff options
Diffstat (limited to 'crates')
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] | ||
2 | edition = "2018" | ||
3 | name = "ra_assists" | ||
4 | version = "0.1.0" | ||
5 | authors = ["Aleksey Kladov <[email protected]>"] | ||
6 | |||
7 | [dependencies] | ||
8 | join_to_string = "0.1.3" | ||
9 | |||
10 | ra_ide_api_light = { path = "../ra_ide_api_light" } | ||
11 | ra_syntax = { path = "../ra_syntax" } | ||
12 | ra_text_edit = { path = "../ra_text_edit" } | ||
13 | ra_db = { path = "../ra_db" } | ||
14 | hir = { path = "../ra_hir", package = "ra_hir" } | ||
15 | |||
16 | [dev-dependencies] | ||
17 | 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 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 @@ | |||
1 | use hir::db::HirDatabase; | ||
1 | use ra_syntax::{ | 2 | use 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 | ||
7 | use crate::assists::{AssistCtx, Assist}; | 8 | use crate::{AssistCtx, Assist}; |
8 | 9 | ||
9 | pub fn add_derive(ctx: AssistCtx) -> Option<Assist> { | 10 | pub(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)] |
40 | mod tests { | 41 | mod 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 @@ | |||
1 | use join_to_string::join; | 1 | use join_to_string::join; |
2 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | 3 | use ra_syntax::{ |
3 | ast::{self, AstNode, AstToken, NameOwner, TypeParamsOwner}, | 4 | ast::{self, AstNode, AstToken, NameOwner, TypeParamsOwner}, |
4 | TextUnit, | 5 | TextUnit, |
5 | }; | 6 | }; |
6 | 7 | ||
7 | use crate::assists::{AssistCtx, Assist}; | 8 | use crate::{AssistCtx, Assist}; |
8 | 9 | ||
9 | pub fn add_impl(ctx: AssistCtx) -> Option<Assist> { | 10 | pub(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)] |
43 | mod tests { | 44 | mod 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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_text_edit::TextEditBuilder; | ||
3 | use ra_db::FileRange; | ||
4 | use ra_syntax::{ | ||
5 | SourceFile, TextRange, AstNode, TextUnit, SyntaxNode, | ||
6 | algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset}, | ||
7 | }; | ||
8 | use ra_ide_api_light::formatting::{leading_indent, reindent}; | ||
9 | |||
10 | use crate::{AssistLabel, AssistAction}; | ||
11 | |||
12 | pub(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)] | ||
48 | pub(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 | |||
55 | impl<'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 | |||
66 | impl<'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)] | ||
113 | pub(crate) struct AssistBuilder { | ||
114 | edit: TextEditBuilder, | ||
115 | cursor_position: Option<TextUnit>, | ||
116 | } | ||
117 | |||
118 | impl 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 @@ | |||
1 | use hir::db::HirDatabase; | ||
1 | use ra_syntax::{ | 2 | use 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 | ||
7 | use crate::assists::{AssistCtx, Assist}; | 8 | use crate::{AssistCtx, Assist}; |
8 | 9 | ||
9 | pub fn change_visibility(ctx: AssistCtx) -> Option<Assist> { | 10 | pub(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 | ||
16 | fn add_vis(ctx: AssistCtx) -> Option<Assist> { | 17 | fn 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 | ||
60 | fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option<Assist> { | 61 | fn 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)] |
77 | mod tests { | 78 | mod 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 @@ | |||
1 | use std::fmt::Write; | ||
2 | |||
3 | use hir::{ | ||
4 | AdtDef, Ty, FieldSource, source_binder, | ||
5 | db::HirDatabase, | ||
6 | }; | ||
7 | use ra_syntax::ast::{self, AstNode}; | ||
8 | |||
9 | use crate::{AssistCtx, Assist}; | ||
10 | |||
11 | pub(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)] | ||
77 | mod 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 @@ | |||
1 | use hir::db::HirDatabase; | ||
1 | use ra_syntax::{ | 2 | use ra_syntax::{ |
2 | Direction, | 3 | Direction, |
3 | SyntaxKind::COMMA, | 4 | SyntaxKind::COMMA, |
4 | }; | 5 | }; |
5 | 6 | ||
6 | use crate::assists::{non_trivia_sibling, AssistCtx, Assist}; | 7 | use crate::{AssistCtx, Assist, non_trivia_sibling}; |
7 | 8 | ||
8 | pub fn flip_comma(ctx: AssistCtx) -> Option<Assist> { | 9 | pub(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)] |
19 | mod tests { | 20 | mod 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 @@ | |||
1 | use hir::db::HirDatabase; | ||
1 | use ra_syntax::{ | 2 | use 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 | ||
8 | use crate::assists::{AssistCtx, Assist}; | 9 | use crate::{AssistCtx, Assist}; |
9 | 10 | ||
10 | pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> { | 11 | pub(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. |
62 | fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> { | 63 | fn 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)] |
104 | mod tests { | 105 | mod 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 | |||
8 | mod assist_ctx; | ||
9 | |||
10 | use ra_text_edit::TextEdit; | ||
11 | use ra_syntax::{TextUnit, SyntaxNode, Direction}; | ||
12 | use ra_db::FileRange; | ||
13 | use hir::db::HirDatabase; | ||
14 | |||
15 | pub(crate) use crate::assist_ctx::{AssistCtx, Assist}; | ||
16 | |||
17 | #[derive(Debug)] | ||
18 | pub struct AssistLabel { | ||
19 | /// Short description of the assist, as shown in the UI. | ||
20 | pub label: String, | ||
21 | } | ||
22 | |||
23 | pub 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. | ||
32 | pub fn applicable_assists<H>(db: &H, range: FileRange) -> Vec<AssistLabel> | ||
33 | where | ||
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. | ||
52 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)> | ||
53 | where | ||
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 | |||
68 | mod add_derive; | ||
69 | mod add_impl; | ||
70 | mod flip_comma; | ||
71 | mod change_visibility; | ||
72 | mod fill_match_arms; | ||
73 | mod introduce_variable; | ||
74 | mod replace_if_let_with_match; | ||
75 | mod split_import; | ||
76 | fn 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 | |||
89 | fn 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)] | ||
96 | mod 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 @@ | |||
1 | use ra_syntax::{AstNode, ast}; | 1 | use ra_syntax::{AstNode, ast}; |
2 | use ra_ide_api_light::formatting::extract_trivial_expression; | ||
3 | use hir::db::HirDatabase; | ||
2 | 4 | ||
3 | use crate::{ | 5 | use crate::{AssistCtx, Assist}; |
4 | assists::{AssistCtx, Assist}, | ||
5 | formatting::extract_trivial_expression, | ||
6 | }; | ||
7 | 6 | ||
8 | pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { | 7 | pub(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)] |
52 | mod tests { | 51 | mod 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 @@ | |||
1 | use hir::db::HirDatabase; | ||
1 | use ra_syntax::{ | 2 | use 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 | ||
7 | use crate::assists::{AssistCtx, Assist}; | 8 | use crate::{AssistCtx, Assist}; |
8 | 9 | ||
9 | pub fn split_import(ctx: AssistCtx) -> Option<Assist> { | 10 | pub(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)] |
35 | mod tests { | 36 | mod 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)] |
73 | pub trait SourceDatabase: CheckCanceled { | 73 | pub 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 | ||
20 | pub mod db; | 20 | pub mod db; |
21 | #[cfg(test)] | 21 | pub mod mock; |
22 | mod mock; | ||
23 | mod query_definitions; | 22 | mod query_definitions; |
24 | mod path; | 23 | mod path; |
25 | pub mod source_binder; | 24 | pub 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)] |
20 | pub(crate) struct MockDatabase { | 20 | pub 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 { | |||
27 | impl panic::RefUnwindSafe for MockDatabase {} | 27 | impl panic::RefUnwindSafe for MockDatabase {} |
28 | 28 | ||
29 | impl MockDatabase { | 29 | impl 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 | ||
168 | impl MockDatabase { | 168 | impl 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" } | |||
24 | ra_db = { path = "../ra_db" } | 24 | ra_db = { path = "../ra_db" } |
25 | hir = { path = "../ra_hir", package = "ra_hir" } | 25 | hir = { path = "../ra_hir", package = "ra_hir" } |
26 | test_utils = { path = "../test_utils" } | 26 | test_utils = { path = "../test_utils" } |
27 | ra_assists = { path = "../ra_assists" } | ||
27 | 28 | ||
28 | [dev-dependencies] | 29 | [dev-dependencies] |
29 | insta = "0.6.1" | 30 | insta = "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 @@ | |||
1 | mod fill_match_arm; | 1 | use ra_db::{FileRange, FilePosition}; |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use crate::{SourceFileEdit, SourceChange, db::RootDatabase}; |
4 | TextRange, SourceFile, AstNode, | 4 | |
5 | algo::find_node_at_offset, | 5 | pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<SourceChange> { |
6 | }; | 6 | ra_assists::assists(db, frange) |
7 | use 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 | }; |
14 | use 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 |
20 | pub(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)] | ||
34 | pub 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 | |||
42 | impl<'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 @@ | |||
1 | use std::fmt::Write; | ||
2 | use hir::{ | ||
3 | AdtDef, | ||
4 | source_binder, | ||
5 | Ty, | ||
6 | FieldSource, | ||
7 | }; | ||
8 | use ra_ide_api_light::{ | ||
9 | assists::{ | ||
10 | Assist, | ||
11 | AssistBuilder | ||
12 | } | ||
13 | }; | ||
14 | use ra_syntax::{ | ||
15 | ast::{ | ||
16 | self, | ||
17 | AstNode, | ||
18 | } | ||
19 | }; | ||
20 | |||
21 | use crate::assists::AssistCtx; | ||
22 | |||
23 | pub 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)] | ||
88 | mod 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 | --- | ||
2 | created: "2019-02-03T15:38:46.094184+00:00" | ||
3 | creator: [email protected] | ||
4 | expression: ret | ||
5 | source: 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 | --- | ||
2 | created: "2019-02-03T15:41:34.640074+00:00" | ||
3 | creator: [email protected] | ||
4 | expression: ret | ||
5 | source: 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 | ||
20 | use crate::{ | 20 | use 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 | ||
106 | fn rename_reference( | 106 | fn 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 | |||
6 | mod flip_comma; | ||
7 | mod add_derive; | ||
8 | mod add_impl; | ||
9 | mod introduce_variable; | ||
10 | mod change_visibility; | ||
11 | mod split_import; | ||
12 | mod replace_if_let_with_match; | ||
13 | |||
14 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
15 | use ra_syntax::{ | ||
16 | Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode, | ||
17 | algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset}, | ||
18 | }; | ||
19 | use itertools::Itertools; | ||
20 | |||
21 | use crate::formatting::leading_indent; | ||
22 | |||
23 | pub 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. | ||
34 | pub 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)] | ||
51 | pub struct LocalEdit { | ||
52 | pub label: String, | ||
53 | pub edit: TextEdit, | ||
54 | pub cursor_position: Option<TextUnit>, | ||
55 | } | ||
56 | |||
57 | fn 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)] | ||
94 | pub struct AssistCtx<'a> { | ||
95 | source_file: &'a SourceFile, | ||
96 | range: TextRange, | ||
97 | should_compute_edit: bool, | ||
98 | } | ||
99 | |||
100 | #[derive(Debug)] | ||
101 | pub enum Assist { | ||
102 | Applicable, | ||
103 | Edit(LocalEdit), | ||
104 | } | ||
105 | |||
106 | #[derive(Default)] | ||
107 | pub struct AssistBuilder { | ||
108 | edit: TextEditBuilder, | ||
109 | cursor_position: Option<TextUnit>, | ||
110 | } | ||
111 | |||
112 | impl<'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 | |||
159 | impl 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 | |||
189 | fn reindent(text: &str, indent: &str) -> String { | ||
190 | let indent = format!("\n{}", indent); | ||
191 | text.lines().intersperse(&indent).collect() | ||
192 | } | ||
193 | |||
194 | #[cfg(test)] | ||
195 | fn 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)] | ||
203 | fn 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)] | ||
211 | fn 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 @@ | |||
1 | use itertools::Itertools; | ||
1 | use ra_syntax::{ | 2 | use 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 | ||
9 | pub 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. |
9 | pub(crate) fn leading_indent(node: &SyntaxNode) -> Option<&str> { | 15 | pub 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 | ||
35 | pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> { | 41 | pub 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 | ||
6 | pub mod assists; | 6 | pub mod formatting; |
7 | mod extend_selection; | 7 | mod extend_selection; |
8 | mod folding_ranges; | 8 | mod folding_ranges; |
9 | mod line_index; | 9 | mod line_index; |
@@ -14,10 +14,15 @@ mod test_utils; | |||
14 | mod join_lines; | 14 | mod join_lines; |
15 | mod typing; | 15 | mod typing; |
16 | mod diagnostics; | 16 | mod diagnostics; |
17 | pub(crate) mod formatting; | 17 | |
18 | #[derive(Debug)] | ||
19 | pub struct LocalEdit { | ||
20 | pub label: String, | ||
21 | pub edit: ra_text_edit::TextEdit, | ||
22 | pub cursor_position: Option<TextUnit>, | ||
23 | } | ||
18 | 24 | ||
19 | pub use self::{ | 25 | pub 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 @@ | |||
1 | use ra_syntax::{SourceFile, TextRange, TextUnit}; | 1 | use ra_syntax::{SourceFile, TextUnit}; |
2 | 2 | ||
3 | use crate::LocalEdit; | 3 | use crate::LocalEdit; |
4 | pub use test_utils::*; | 4 | pub 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 | |||
26 | pub 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 | |||
38 | pub 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 | ||
129 | pub fn handle_document_symbol( | 129 | pub 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 | ||
325 | pub fn handle_decorations( | 325 | pub 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 | ||