diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-09 08:52:09 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-09 08:52:09 +0000 |
commit | 3e8351fb0607f8711749b00d80f68bf25de01a76 (patch) | |
tree | 97388dafe71ececcbaf97249021b9c8d49786ccf /crates | |
parent | 12e3b4c70b5ef23b2fdfc197296d483680e125f9 (diff) | |
parent | 4fdeb54bb5c7ba0704839a65996766d223c51fc1 (diff) |
Merge #768
768: Sort assists by the range of the affected element r=matklad a=robojumper
Closes #763.
https://github.com/rust-analyzer/rust-analyzer/blob/3be98f2ac93b278828e76eb813bdd8033f647b12/crates/ra_assists/src/lib.rs#L233-L236
This could be made more robust by a) adding a way to identify actions by things other than their label and b) allowing arbitrary actions to appear in the list as long as the tested actions are there in the correct order. Let me know if I should do any of that.
Co-authored-by: robojumper <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_assists/src/add_derive.rs | 20 | ||||
-rw-r--r-- | crates/ra_assists/src/add_impl.rs | 17 | ||||
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 13 | ||||
-rw-r--r-- | crates/ra_assists/src/change_visibility.rs | 22 | ||||
-rw-r--r-- | crates/ra_assists/src/fill_match_arms.rs | 18 | ||||
-rw-r--r-- | crates/ra_assists/src/flip_comma.rs | 8 | ||||
-rw-r--r-- | crates/ra_assists/src/introduce_variable.rs | 35 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 100 | ||||
-rw-r--r-- | crates/ra_assists/src/remove_dbg.rs | 18 | ||||
-rw-r--r-- | crates/ra_assists/src/replace_if_let_with_match.rs | 25 | ||||
-rw-r--r-- | crates/ra_assists/src/split_import.rs | 8 |
11 files changed, 262 insertions, 22 deletions
diff --git a/crates/ra_assists/src/add_derive.rs b/crates/ra_assists/src/add_derive.rs index caf21e079..ea9707631 100644 --- a/crates/ra_assists/src/add_derive.rs +++ b/crates/ra_assists/src/add_derive.rs | |||
@@ -24,6 +24,7 @@ pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
24 | } | 24 | } |
25 | Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), | 25 | Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), |
26 | }; | 26 | }; |
27 | edit.target(nominal.syntax().range()); | ||
27 | edit.set_cursor(offset) | 28 | edit.set_cursor(offset) |
28 | }) | 29 | }) |
29 | } | 30 | } |
@@ -38,7 +39,7 @@ fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextUnit> { | |||
38 | #[cfg(test)] | 39 | #[cfg(test)] |
39 | mod tests { | 40 | mod tests { |
40 | use super::*; | 41 | use super::*; |
41 | use crate::helpers::check_assist; | 42 | use crate::helpers::{check_assist, check_assist_target}; |
42 | 43 | ||
43 | #[test] | 44 | #[test] |
44 | fn add_derive_new() { | 45 | fn add_derive_new() { |
@@ -80,4 +81,21 @@ struct Foo { a: i32, } | |||
80 | ", | 81 | ", |
81 | ); | 82 | ); |
82 | } | 83 | } |
84 | |||
85 | #[test] | ||
86 | fn add_derive_target() { | ||
87 | check_assist_target( | ||
88 | add_derive, | ||
89 | " | ||
90 | struct SomeThingIrrelevant; | ||
91 | /// `Foo` is a pretty important struct. | ||
92 | /// It does stuff. | ||
93 | struct Foo { a: i32<|>, } | ||
94 | struct EvenMoreIrrelevant; | ||
95 | ", | ||
96 | "/// `Foo` is a pretty important struct. | ||
97 | /// It does stuff. | ||
98 | struct Foo { a: i32, }", | ||
99 | ); | ||
100 | } | ||
83 | } | 101 | } |
diff --git a/crates/ra_assists/src/add_impl.rs b/crates/ra_assists/src/add_impl.rs index f2360bc89..32fc074a6 100644 --- a/crates/ra_assists/src/add_impl.rs +++ b/crates/ra_assists/src/add_impl.rs | |||
@@ -11,6 +11,7 @@ pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
11 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | 11 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; |
12 | let name = nominal.name()?; | 12 | let name = nominal.name()?; |
13 | ctx.build("add impl", |edit| { | 13 | ctx.build("add impl", |edit| { |
14 | edit.target(nominal.syntax().range()); | ||
14 | let type_params = nominal.type_param_list(); | 15 | let type_params = nominal.type_param_list(); |
15 | let start_offset = nominal.syntax().range().end(); | 16 | let start_offset = nominal.syntax().range().end(); |
16 | let mut buf = String::new(); | 17 | let mut buf = String::new(); |
@@ -37,7 +38,7 @@ pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
37 | #[cfg(test)] | 38 | #[cfg(test)] |
38 | mod tests { | 39 | mod tests { |
39 | use super::*; | 40 | use super::*; |
40 | use crate::helpers::check_assist; | 41 | use crate::helpers::{check_assist, check_assist_target}; |
41 | 42 | ||
42 | #[test] | 43 | #[test] |
43 | fn test_add_impl() { | 44 | fn test_add_impl() { |
@@ -54,4 +55,18 @@ mod tests { | |||
54 | ); | 55 | ); |
55 | } | 56 | } |
56 | 57 | ||
58 | #[test] | ||
59 | fn add_impl_target() { | ||
60 | check_assist_target( | ||
61 | add_impl, | ||
62 | " | ||
63 | struct SomeThingIrrelevant; | ||
64 | /// Has a lifetime parameter | ||
65 | struct Foo<'a, T: Foo<'a>> {<|>} | ||
66 | struct EvenMoreIrrelevant; | ||
67 | ", | ||
68 | "/// Has a lifetime parameter | ||
69 | struct Foo<'a, T: Foo<'a>> {}", | ||
70 | ); | ||
71 | } | ||
57 | } | 72 | } |
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index 0bf640241..41c8ac2f6 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -16,7 +16,7 @@ pub(crate) enum Assist { | |||
16 | 16 | ||
17 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | 17 | /// `AssistCtx` allows to apply an assist or check if it could be applied. |
18 | /// | 18 | /// |
19 | /// Assists use a somewhat overengineered approach, given the current needs. The | 19 | /// Assists use a somewhat over-engineered approach, given the current needs. The |
20 | /// assists workflow consists of two phases. In the first phase, a user asks for | 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 | 21 | /// the list of available assists. In the second phase, the user picks a |
22 | /// particular assist and it gets applied. | 22 | /// particular assist and it gets applied. |
@@ -106,6 +106,7 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | |||
106 | pub(crate) struct AssistBuilder { | 106 | pub(crate) struct AssistBuilder { |
107 | edit: TextEditBuilder, | 107 | edit: TextEditBuilder, |
108 | cursor_position: Option<TextUnit>, | 108 | cursor_position: Option<TextUnit>, |
109 | target: Option<TextRange>, | ||
109 | } | 110 | } |
110 | 111 | ||
111 | impl AssistBuilder { | 112 | impl AssistBuilder { |
@@ -138,7 +139,15 @@ impl AssistBuilder { | |||
138 | self.cursor_position = Some(offset) | 139 | self.cursor_position = Some(offset) |
139 | } | 140 | } |
140 | 141 | ||
142 | pub(crate) fn target(&mut self, target: TextRange) { | ||
143 | self.target = Some(target) | ||
144 | } | ||
145 | |||
141 | fn build(self) -> AssistAction { | 146 | fn build(self) -> AssistAction { |
142 | AssistAction { edit: self.edit.finish(), cursor_position: self.cursor_position } | 147 | AssistAction { |
148 | edit: self.edit.finish(), | ||
149 | cursor_position: self.cursor_position, | ||
150 | target: self.target, | ||
151 | } | ||
143 | } | 152 | } |
144 | } | 153 | } |
diff --git a/crates/ra_assists/src/change_visibility.rs b/crates/ra_assists/src/change_visibility.rs index fa5f231c8..6d9a4eec2 100644 --- a/crates/ra_assists/src/change_visibility.rs +++ b/crates/ra_assists/src/change_visibility.rs | |||
@@ -20,7 +20,7 @@ fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
20 | _ => false, | 20 | _ => false, |
21 | }); | 21 | }); |
22 | 22 | ||
23 | let offset = if let Some(keyword) = item_keyword { | 23 | let (offset, target) = if let Some(keyword) = item_keyword { |
24 | let parent = keyword.parent()?; | 24 | let parent = keyword.parent()?; |
25 | let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; | 25 | let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; |
26 | // Parent is not a definition, can't add visibility | 26 | // Parent is not a definition, can't add visibility |
@@ -31,17 +31,18 @@ fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
31 | if parent.children().any(|child| child.kind() == VISIBILITY) { | 31 | if parent.children().any(|child| child.kind() == VISIBILITY) { |
32 | return None; | 32 | return None; |
33 | } | 33 | } |
34 | vis_offset(parent) | 34 | (vis_offset(parent), keyword.range()) |
35 | } else { | 35 | } else { |
36 | let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?; | 36 | let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?; |
37 | let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?; | 37 | let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?; |
38 | if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() { | 38 | if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() { |
39 | return None; | 39 | return None; |
40 | } | 40 | } |
41 | vis_offset(field.syntax()) | 41 | (vis_offset(field.syntax()), ident.range()) |
42 | }; | 42 | }; |
43 | 43 | ||
44 | ctx.build("make pub(crate)", |edit| { | 44 | ctx.build("make pub(crate)", |edit| { |
45 | edit.target(target); | ||
45 | edit.insert(offset, "pub(crate) "); | 46 | edit.insert(offset, "pub(crate) "); |
46 | edit.set_cursor(offset); | 47 | edit.set_cursor(offset); |
47 | }) | 48 | }) |
@@ -60,13 +61,15 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit { | |||
60 | 61 | ||
61 | fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> { | 62 | fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> { |
62 | if vis.syntax().text() == "pub" { | 63 | if vis.syntax().text() == "pub" { |
63 | return ctx.build("chage to pub(crate)", |edit| { | 64 | return ctx.build("change to pub(crate)", |edit| { |
65 | edit.target(vis.syntax().range()); | ||
64 | edit.replace(vis.syntax().range(), "pub(crate)"); | 66 | edit.replace(vis.syntax().range(), "pub(crate)"); |
65 | edit.set_cursor(vis.syntax().range().start()); | 67 | edit.set_cursor(vis.syntax().range().start()); |
66 | }); | 68 | }); |
67 | } | 69 | } |
68 | if vis.syntax().text() == "pub(crate)" { | 70 | if vis.syntax().text() == "pub(crate)" { |
69 | return ctx.build("chage to pub", |edit| { | 71 | return ctx.build("change to pub", |edit| { |
72 | edit.target(vis.syntax().range()); | ||
70 | edit.replace(vis.syntax().range(), "pub"); | 73 | edit.replace(vis.syntax().range(), "pub"); |
71 | edit.set_cursor(vis.syntax().range().start()); | 74 | edit.set_cursor(vis.syntax().range().start()); |
72 | }); | 75 | }); |
@@ -77,7 +80,7 @@ fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option | |||
77 | #[cfg(test)] | 80 | #[cfg(test)] |
78 | mod tests { | 81 | mod tests { |
79 | use super::*; | 82 | use super::*; |
80 | use crate::helpers::check_assist; | 83 | use crate::helpers::{check_assist, check_assist_target}; |
81 | 84 | ||
82 | #[test] | 85 | #[test] |
83 | fn change_visibility_adds_pub_crate_to_items() { | 86 | fn change_visibility_adds_pub_crate_to_items() { |
@@ -135,4 +138,11 @@ mod tests { | |||
135 | ", | 138 | ", |
136 | ) | 139 | ) |
137 | } | 140 | } |
141 | |||
142 | #[test] | ||
143 | fn change_visibility_target() { | ||
144 | check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); | ||
145 | check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); | ||
146 | check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field"); | ||
147 | } | ||
138 | } | 148 | } |
diff --git a/crates/ra_assists/src/fill_match_arms.rs b/crates/ra_assists/src/fill_match_arms.rs index 741f75e2a..69b535a27 100644 --- a/crates/ra_assists/src/fill_match_arms.rs +++ b/crates/ra_assists/src/fill_match_arms.rs | |||
@@ -65,6 +65,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist | |||
65 | buf.push_str(" => (),\n"); | 65 | buf.push_str(" => (),\n"); |
66 | } | 66 | } |
67 | buf.push_str("}"); | 67 | buf.push_str("}"); |
68 | edit.target(match_expr.syntax().range()); | ||
68 | edit.set_cursor(expr.syntax().range().start()); | 69 | edit.set_cursor(expr.syntax().range().start()); |
69 | edit.replace_node_and_indent(match_expr.syntax(), buf); | 70 | edit.replace_node_and_indent(match_expr.syntax(), buf); |
70 | }) | 71 | }) |
@@ -72,7 +73,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist | |||
72 | 73 | ||
73 | #[cfg(test)] | 74 | #[cfg(test)] |
74 | mod tests { | 75 | mod tests { |
75 | use crate::helpers::check_assist; | 76 | use crate::helpers::{check_assist, check_assist_target}; |
76 | 77 | ||
77 | use super::fill_match_arms; | 78 | use super::fill_match_arms; |
78 | 79 | ||
@@ -139,4 +140,19 @@ mod tests { | |||
139 | "#, | 140 | "#, |
140 | ); | 141 | ); |
141 | } | 142 | } |
143 | |||
144 | #[test] | ||
145 | fn fill_match_arms_target() { | ||
146 | check_assist_target( | ||
147 | fill_match_arms, | ||
148 | r#" | ||
149 | enum E { X, Y} | ||
150 | |||
151 | fn main() { | ||
152 | match E::X<|> {} | ||
153 | } | ||
154 | "#, | ||
155 | "match E::X {}", | ||
156 | ); | ||
157 | } | ||
142 | } | 158 | } |
diff --git a/crates/ra_assists/src/flip_comma.rs b/crates/ra_assists/src/flip_comma.rs index a49820c29..33da58f17 100644 --- a/crates/ra_assists/src/flip_comma.rs +++ b/crates/ra_assists/src/flip_comma.rs | |||
@@ -11,6 +11,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
11 | let prev = non_trivia_sibling(comma, Direction::Prev)?; | 11 | let prev = non_trivia_sibling(comma, Direction::Prev)?; |
12 | let next = non_trivia_sibling(comma, Direction::Next)?; | 12 | let next = non_trivia_sibling(comma, Direction::Next)?; |
13 | ctx.build("flip comma", |edit| { | 13 | ctx.build("flip comma", |edit| { |
14 | edit.target(comma.range()); | ||
14 | edit.replace(prev.range(), next.text()); | 15 | edit.replace(prev.range(), next.text()); |
15 | edit.replace(next.range(), prev.text()); | 16 | edit.replace(next.range(), prev.text()); |
16 | }) | 17 | }) |
@@ -20,7 +21,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
20 | mod tests { | 21 | mod tests { |
21 | use super::*; | 22 | use super::*; |
22 | 23 | ||
23 | use crate::helpers::check_assist; | 24 | use crate::helpers::{check_assist, check_assist_target}; |
24 | 25 | ||
25 | #[test] | 26 | #[test] |
26 | fn flip_comma_works_for_function_parameters() { | 27 | fn flip_comma_works_for_function_parameters() { |
@@ -30,4 +31,9 @@ mod tests { | |||
30 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", | 31 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", |
31 | ) | 32 | ) |
32 | } | 33 | } |
34 | |||
35 | #[test] | ||
36 | fn flip_comma_target() { | ||
37 | check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",") | ||
38 | } | ||
33 | } | 39 | } |
diff --git a/crates/ra_assists/src/introduce_variable.rs b/crates/ra_assists/src/introduce_variable.rs index 4f7c9f3c2..934d1d6b3 100644 --- a/crates/ra_assists/src/introduce_variable.rs +++ b/crates/ra_assists/src/introduce_variable.rs | |||
@@ -45,6 +45,7 @@ pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Ass | |||
45 | } else { | 45 | } else { |
46 | buf.push_str(";"); | 46 | buf.push_str(";"); |
47 | indent.text().push_to(&mut buf); | 47 | indent.text().push_to(&mut buf); |
48 | edit.target(expr.syntax().range()); | ||
48 | edit.replace(expr.syntax().range(), "var_name".to_string()); | 49 | edit.replace(expr.syntax().range(), "var_name".to_string()); |
49 | edit.insert(anchor_stmt.range().start(), buf); | 50 | edit.insert(anchor_stmt.range().start(), buf); |
50 | if wrap_in_block { | 51 | if wrap_in_block { |
@@ -58,7 +59,7 @@ pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Ass | |||
58 | fn valid_covering_node(node: &SyntaxNode) -> bool { | 59 | fn valid_covering_node(node: &SyntaxNode) -> bool { |
59 | node.kind() != COMMENT | 60 | node.kind() != COMMENT |
60 | } | 61 | } |
61 | /// Check wether the node is a valid expression which can be extracted to a variable. | 62 | /// Check whether the node is a valid expression which can be extracted to a variable. |
62 | /// In general that's true for any expression, but in some cases that would produce invalid code. | 63 | /// In general that's true for any expression, but in some cases that would produce invalid code. |
63 | fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> { | 64 | fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> { |
64 | match node.kind() { | 65 | match node.kind() { |
@@ -74,7 +75,7 @@ fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> { | |||
74 | /// and a boolean indicating whether we have to wrap it within a { } block | 75 | /// and a boolean indicating whether we have to wrap it within a { } block |
75 | /// to produce correct code. | 76 | /// to produce correct code. |
76 | /// It can be a statement, the last in a block expression or a wanna be block | 77 | /// It can be a statement, the last in a block expression or a wanna be block |
77 | /// expression like a lamba or match arm. | 78 | /// expression like a lambda or match arm. |
78 | fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> { | 79 | fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> { |
79 | expr.syntax().ancestors().find_map(|node| { | 80 | expr.syntax().ancestors().find_map(|node| { |
80 | if ast::Stmt::cast(node).is_some() { | 81 | if ast::Stmt::cast(node).is_some() { |
@@ -100,7 +101,7 @@ fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> { | |||
100 | #[cfg(test)] | 101 | #[cfg(test)] |
101 | mod tests { | 102 | mod tests { |
102 | use super::*; | 103 | use super::*; |
103 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range}; | 104 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range, check_assist_target, check_assist_range_target}; |
104 | 105 | ||
105 | #[test] | 106 | #[test] |
106 | fn test_introduce_var_simple() { | 107 | fn test_introduce_var_simple() { |
@@ -425,4 +426,32 @@ fn main() { | |||
425 | ", | 426 | ", |
426 | ); | 427 | ); |
427 | } | 428 | } |
429 | |||
430 | // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic | ||
431 | #[test] | ||
432 | fn introduce_var_target() { | ||
433 | check_assist_target( | ||
434 | introduce_variable, | ||
435 | " | ||
436 | fn foo() -> u32 { | ||
437 | r<|>eturn 2 + 2; | ||
438 | } | ||
439 | ", | ||
440 | "2 + 2", | ||
441 | ); | ||
442 | |||
443 | check_assist_range_target( | ||
444 | introduce_variable, | ||
445 | " | ||
446 | fn main() { | ||
447 | let x = true; | ||
448 | let tuple = match x { | ||
449 | true => (<|>2 + 2<|>, true) | ||
450 | _ => (0, false) | ||
451 | }; | ||
452 | } | ||
453 | ", | ||
454 | "2 + 2", | ||
455 | ); | ||
456 | } | ||
428 | } | 457 | } |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 881db6347..7928b4983 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -8,7 +8,7 @@ | |||
8 | mod assist_ctx; | 8 | mod assist_ctx; |
9 | 9 | ||
10 | use ra_text_edit::TextEdit; | 10 | use ra_text_edit::TextEdit; |
11 | use ra_syntax::{TextUnit, SyntaxNode, Direction}; | 11 | use ra_syntax::{TextRange, TextUnit, SyntaxNode, Direction}; |
12 | use ra_db::FileRange; | 12 | use ra_db::FileRange; |
13 | use hir::db::HirDatabase; | 13 | use hir::db::HirDatabase; |
14 | 14 | ||
@@ -23,6 +23,7 @@ pub struct AssistLabel { | |||
23 | pub struct AssistAction { | 23 | pub struct AssistAction { |
24 | pub edit: TextEdit, | 24 | pub edit: TextEdit, |
25 | pub cursor_position: Option<TextUnit>, | 25 | pub cursor_position: Option<TextUnit>, |
26 | pub target: Option<TextRange>, | ||
26 | } | 27 | } |
27 | 28 | ||
28 | /// Return all the assists applicable at the given position. | 29 | /// Return all the assists applicable at the given position. |
@@ -53,15 +54,24 @@ pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)> | |||
53 | where | 54 | where |
54 | H: HirDatabase + 'static, | 55 | H: HirDatabase + 'static, |
55 | { | 56 | { |
57 | use std::cmp::Ordering; | ||
58 | |||
56 | AssistCtx::with_ctx(db, range, true, |ctx| { | 59 | AssistCtx::with_ctx(db, range, true, |ctx| { |
57 | all_assists() | 60 | let mut a = all_assists() |
58 | .iter() | 61 | .iter() |
59 | .filter_map(|f| f(ctx.clone())) | 62 | .filter_map(|f| f(ctx.clone())) |
60 | .map(|a| match a { | 63 | .map(|a| match a { |
61 | Assist::Resolved(label, action) => (label, action), | 64 | Assist::Resolved(label, action) => (label, action), |
62 | Assist::Unresolved(..) => unreachable!(), | 65 | Assist::Unresolved(..) => unreachable!(), |
63 | }) | 66 | }) |
64 | .collect() | 67 | .collect::<Vec<(AssistLabel, AssistAction)>>(); |
68 | a.sort_by(|a, b| match (a.1.target, b.1.target) { | ||
69 | (Some(a), Some(b)) => a.len().cmp(&b.len()), | ||
70 | (Some(_), None) => Ordering::Less, | ||
71 | (None, Some(_)) => Ordering::Greater, | ||
72 | (None, None) => Ordering::Equal, | ||
73 | }); | ||
74 | a | ||
65 | }) | 75 | }) |
66 | } | 76 | } |
67 | 77 | ||
@@ -97,7 +107,7 @@ mod helpers { | |||
97 | use hir::mock::MockDatabase; | 107 | use hir::mock::MockDatabase; |
98 | use ra_syntax::TextRange; | 108 | use ra_syntax::TextRange; |
99 | use ra_db::FileRange; | 109 | use ra_db::FileRange; |
100 | use test_utils::{extract_offset, assert_eq_text, add_cursor, extract_range}; | 110 | use test_utils::{extract_offset, extract_range, assert_eq_text, add_cursor}; |
101 | 111 | ||
102 | use crate::{AssistCtx, Assist}; | 112 | use crate::{AssistCtx, Assist}; |
103 | 113 | ||
@@ -151,6 +161,45 @@ mod helpers { | |||
151 | assert_eq_text!(after, &actual); | 161 | assert_eq_text!(after, &actual); |
152 | } | 162 | } |
153 | 163 | ||
164 | pub(crate) fn check_assist_target( | ||
165 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
166 | before: &str, | ||
167 | target: &str, | ||
168 | ) { | ||
169 | let (before_cursor_pos, before) = extract_offset(before); | ||
170 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | ||
171 | let frange = | ||
172 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | ||
173 | let assist = | ||
174 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | ||
175 | let action = match assist { | ||
176 | Assist::Unresolved(_) => unreachable!(), | ||
177 | Assist::Resolved(_, it) => it, | ||
178 | }; | ||
179 | |||
180 | let range = action.target.expect("expected target on action"); | ||
181 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); | ||
182 | } | ||
183 | |||
184 | pub(crate) fn check_assist_range_target( | ||
185 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
186 | before: &str, | ||
187 | target: &str, | ||
188 | ) { | ||
189 | let (range, before) = extract_range(before); | ||
190 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | ||
191 | let frange = FileRange { file_id, range }; | ||
192 | let assist = | ||
193 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | ||
194 | let action = match assist { | ||
195 | Assist::Unresolved(_) => unreachable!(), | ||
196 | Assist::Resolved(_, it) => it, | ||
197 | }; | ||
198 | |||
199 | let range = action.target.expect("expected target on action"); | ||
200 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); | ||
201 | } | ||
202 | |||
154 | pub(crate) fn check_assist_not_applicable( | 203 | pub(crate) fn check_assist_not_applicable( |
155 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | 204 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, |
156 | before: &str, | 205 | before: &str, |
@@ -162,5 +211,48 @@ mod helpers { | |||
162 | let assist = AssistCtx::with_ctx(&db, frange, true, assist); | 211 | let assist = AssistCtx::with_ctx(&db, frange, true, assist); |
163 | assert!(assist.is_none()); | 212 | assert!(assist.is_none()); |
164 | } | 213 | } |
214 | } | ||
215 | |||
216 | #[cfg(test)] | ||
217 | mod tests { | ||
218 | use hir::mock::MockDatabase; | ||
219 | use ra_syntax::TextRange; | ||
220 | use ra_db::FileRange; | ||
221 | use test_utils::{extract_offset}; | ||
222 | |||
223 | #[test] | ||
224 | fn assist_order_field_struct() { | ||
225 | let before = "struct Foo { <|>bar: u32 }"; | ||
226 | let (before_cursor_pos, before) = extract_offset(before); | ||
227 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | ||
228 | let frange = | ||
229 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | ||
230 | let assists = super::assists(&db, frange); | ||
231 | let mut assists = assists.iter(); | ||
232 | |||
233 | assert_eq!(assists.next().expect("expected assist").0.label, "make pub(crate)"); | ||
234 | assert_eq!(assists.next().expect("expected assist").0.label, "add `#[derive]`"); | ||
235 | } | ||
236 | |||
237 | #[test] | ||
238 | fn assist_order_if_expr() { | ||
239 | let before = " | ||
240 | pub fn test_some_range(a: int) -> bool { | ||
241 | if let 2..6 = 5<|> { | ||
242 | true | ||
243 | } else { | ||
244 | false | ||
245 | } | ||
246 | }"; | ||
247 | let (before_cursor_pos, before) = extract_offset(before); | ||
248 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | ||
249 | let frange = | ||
250 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | ||
251 | let assists = super::assists(&db, frange); | ||
252 | let mut assists = assists.iter(); | ||
253 | |||
254 | assert_eq!(assists.next().expect("expected assist").0.label, "introduce variable"); | ||
255 | assert_eq!(assists.next().expect("expected assist").0.label, "replace with match"); | ||
256 | } | ||
165 | 257 | ||
166 | } | 258 | } |
diff --git a/crates/ra_assists/src/remove_dbg.rs b/crates/ra_assists/src/remove_dbg.rs index 40f97a849..e9d0a635b 100644 --- a/crates/ra_assists/src/remove_dbg.rs +++ b/crates/ra_assists/src/remove_dbg.rs | |||
@@ -47,6 +47,7 @@ pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
47 | }; | 47 | }; |
48 | 48 | ||
49 | ctx.build("remove dbg!()", |edit| { | 49 | ctx.build("remove dbg!()", |edit| { |
50 | edit.target(macro_call.syntax().range()); | ||
50 | edit.replace(macro_range, macro_content); | 51 | edit.replace(macro_range, macro_content); |
51 | edit.set_cursor(cursor_pos); | 52 | edit.set_cursor(cursor_pos); |
52 | }) | 53 | }) |
@@ -78,7 +79,7 @@ fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<b | |||
78 | #[cfg(test)] | 79 | #[cfg(test)] |
79 | mod tests { | 80 | mod tests { |
80 | use super::*; | 81 | use super::*; |
81 | use crate::helpers::{check_assist, check_assist_not_applicable}; | 82 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; |
82 | 83 | ||
83 | #[test] | 84 | #[test] |
84 | fn test_remove_dbg() { | 85 | fn test_remove_dbg() { |
@@ -120,4 +121,19 @@ fn foo(n: usize) { | |||
120 | check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)"); | 121 | check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)"); |
121 | check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7"); | 122 | check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7"); |
122 | } | 123 | } |
124 | |||
125 | #[test] | ||
126 | fn remove_dbg_target() { | ||
127 | check_assist_target( | ||
128 | remove_dbg, | ||
129 | " | ||
130 | fn foo(n: usize) { | ||
131 | if let Some(_) = dbg!(n.<|>checked_sub(4)) { | ||
132 | // ... | ||
133 | } | ||
134 | } | ||
135 | ", | ||
136 | "dbg!(n.checked_sub(4))", | ||
137 | ); | ||
138 | } | ||
123 | } | 139 | } |
diff --git a/crates/ra_assists/src/replace_if_let_with_match.rs b/crates/ra_assists/src/replace_if_let_with_match.rs index 683f0d119..a22ec5584 100644 --- a/crates/ra_assists/src/replace_if_let_with_match.rs +++ b/crates/ra_assists/src/replace_if_let_with_match.rs | |||
@@ -17,6 +17,7 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Opt | |||
17 | 17 | ||
18 | ctx.build("replace with match", |edit| { | 18 | ctx.build("replace with match", |edit| { |
19 | let match_expr = build_match_expr(expr, pat, then_block, else_block); | 19 | let match_expr = build_match_expr(expr, pat, then_block, else_block); |
20 | edit.target(if_expr.syntax().range()); | ||
20 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); | 21 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); |
21 | edit.set_cursor(if_expr.syntax().range().start()) | 22 | edit.set_cursor(if_expr.syntax().range().start()) |
22 | }) | 23 | }) |
@@ -46,7 +47,7 @@ fn format_arm(block: &ast::Block) -> String { | |||
46 | #[cfg(test)] | 47 | #[cfg(test)] |
47 | mod tests { | 48 | mod tests { |
48 | use super::*; | 49 | use super::*; |
49 | use crate::helpers::check_assist; | 50 | use crate::helpers::{check_assist, check_assist_target}; |
50 | 51 | ||
51 | #[test] | 52 | #[test] |
52 | fn test_replace_if_let_with_match_unwraps_simple_expressions() { | 53 | fn test_replace_if_let_with_match_unwraps_simple_expressions() { |
@@ -73,4 +74,26 @@ impl VariantData { | |||
73 | } ", | 74 | } ", |
74 | ) | 75 | ) |
75 | } | 76 | } |
77 | |||
78 | #[test] | ||
79 | fn replace_if_let_with_match_target() { | ||
80 | check_assist_target( | ||
81 | replace_if_let_with_match, | ||
82 | " | ||
83 | impl VariantData { | ||
84 | pub fn is_struct(&self) -> bool { | ||
85 | if <|>let VariantData::Struct(..) = *self { | ||
86 | true | ||
87 | } else { | ||
88 | false | ||
89 | } | ||
90 | } | ||
91 | } ", | ||
92 | "if let VariantData::Struct(..) = *self { | ||
93 | true | ||
94 | } else { | ||
95 | false | ||
96 | }", | ||
97 | ); | ||
98 | } | ||
76 | } | 99 | } |
diff --git a/crates/ra_assists/src/split_import.rs b/crates/ra_assists/src/split_import.rs index fb69cef9c..051bc6fec 100644 --- a/crates/ra_assists/src/split_import.rs +++ b/crates/ra_assists/src/split_import.rs | |||
@@ -24,6 +24,7 @@ pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
24 | }; | 24 | }; |
25 | 25 | ||
26 | ctx.build("split import", |edit| { | 26 | ctx.build("split import", |edit| { |
27 | edit.target(colon_colon.range()); | ||
27 | edit.insert(l_curly, "{"); | 28 | edit.insert(l_curly, "{"); |
28 | edit.insert(r_curly, "}"); | 29 | edit.insert(r_curly, "}"); |
29 | edit.set_cursor(l_curly + TextUnit::of_str("{")); | 30 | edit.set_cursor(l_curly + TextUnit::of_str("{")); |
@@ -33,7 +34,7 @@ pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
33 | #[cfg(test)] | 34 | #[cfg(test)] |
34 | mod tests { | 35 | mod tests { |
35 | use super::*; | 36 | use super::*; |
36 | use crate::helpers::check_assist; | 37 | use crate::helpers::{check_assist, check_assist_target}; |
37 | 38 | ||
38 | #[test] | 39 | #[test] |
39 | fn test_split_import() { | 40 | fn test_split_import() { |
@@ -52,4 +53,9 @@ mod tests { | |||
52 | "use algo::{<|>visitor::{Visitor, visit}}", | 53 | "use algo::{<|>visitor::{Visitor, visit}}", |
53 | ) | 54 | ) |
54 | } | 55 | } |
56 | |||
57 | #[test] | ||
58 | fn split_import_target() { | ||
59 | check_assist_target(split_import, "use algo::<|>visitor::{Visitor, visit}", "::"); | ||
60 | } | ||
55 | } | 61 | } |