diff options
-rw-r--r-- | crates/ra_assists/src/assists/flip_trait_bound.rs | 119 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests.rs | 12 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests/generated.rs | 13 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide_api/src/extend_selection.rs | 82 | ||||
-rw-r--r-- | docs/user/assists.md | 12 |
6 files changed, 229 insertions, 11 deletions
diff --git a/crates/ra_assists/src/assists/flip_trait_bound.rs b/crates/ra_assists/src/assists/flip_trait_bound.rs new file mode 100644 index 000000000..1625b241f --- /dev/null +++ b/crates/ra_assists/src/assists/flip_trait_bound.rs | |||
@@ -0,0 +1,119 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | algo::non_trivia_sibling, | ||
4 | ast::{self, AstNode}, | ||
5 | Direction, T, | ||
6 | }; | ||
7 | |||
8 | use crate::{Assist, AssistCtx, AssistId}; | ||
9 | |||
10 | // Assist: flip_trait_bound | ||
11 | // | ||
12 | // Flips two trait bounds. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn foo<T: Clone +<|> Copy>() { } | ||
16 | // ``` | ||
17 | // -> | ||
18 | // ``` | ||
19 | // fn foo<T: Copy + Clone>() { } | ||
20 | // ``` | ||
21 | pub(crate) fn flip_trait_bound(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
22 | // We want to replicate the behavior of `flip_binexpr` by only suggesting | ||
23 | // the assist when the cursor is on a `+` | ||
24 | let plus = ctx.find_token_at_offset(T![+])?; | ||
25 | |||
26 | // Make sure we're in a `TypeBoundList` | ||
27 | if ast::TypeBoundList::cast(plus.parent()).is_none() { | ||
28 | return None; | ||
29 | } | ||
30 | |||
31 | let (before, after) = ( | ||
32 | non_trivia_sibling(plus.clone().into(), Direction::Prev)?, | ||
33 | non_trivia_sibling(plus.clone().into(), Direction::Next)?, | ||
34 | ); | ||
35 | |||
36 | ctx.add_action(AssistId("flip_trait_bound"), "flip trait bound", |edit| { | ||
37 | edit.target(plus.text_range()); | ||
38 | edit.replace(before.text_range(), after.to_string()); | ||
39 | edit.replace(after.text_range(), before.to_string()); | ||
40 | }); | ||
41 | |||
42 | ctx.build() | ||
43 | } | ||
44 | |||
45 | #[cfg(test)] | ||
46 | mod tests { | ||
47 | use super::*; | ||
48 | |||
49 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
50 | |||
51 | #[test] | ||
52 | fn flip_trait_bound_assist_available() { | ||
53 | check_assist_target(flip_trait_bound, "struct S<T> where T: A <|>+ B + C { }", "+") | ||
54 | } | ||
55 | |||
56 | #[test] | ||
57 | fn flip_trait_bound_not_applicable_for_single_trait_bound() { | ||
58 | check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: <|>A { }") | ||
59 | } | ||
60 | |||
61 | #[test] | ||
62 | fn flip_trait_bound_works_for_struct() { | ||
63 | check_assist( | ||
64 | flip_trait_bound, | ||
65 | "struct S<T> where T: A <|>+ B { }", | ||
66 | "struct S<T> where T: B <|>+ A { }", | ||
67 | ) | ||
68 | } | ||
69 | |||
70 | #[test] | ||
71 | fn flip_trait_bound_works_for_trait_impl() { | ||
72 | check_assist( | ||
73 | flip_trait_bound, | ||
74 | "impl X for S<T> where T: A +<|> B { }", | ||
75 | "impl X for S<T> where T: B +<|> A { }", | ||
76 | ) | ||
77 | } | ||
78 | |||
79 | #[test] | ||
80 | fn flip_trait_bound_works_for_fn() { | ||
81 | check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B <|>+ A>(t: T) { }") | ||
82 | } | ||
83 | |||
84 | #[test] | ||
85 | fn flip_trait_bound_works_for_fn_where_clause() { | ||
86 | check_assist( | ||
87 | flip_trait_bound, | ||
88 | "fn f<T>(t: T) where T: A +<|> B { }", | ||
89 | "fn f<T>(t: T) where T: B +<|> A { }", | ||
90 | ) | ||
91 | } | ||
92 | |||
93 | #[test] | ||
94 | fn flip_trait_bound_works_for_lifetime() { | ||
95 | check_assist( | ||
96 | flip_trait_bound, | ||
97 | "fn f<T>(t: T) where T: A <|>+ 'static { }", | ||
98 | "fn f<T>(t: T) where T: 'static <|>+ A { }", | ||
99 | ) | ||
100 | } | ||
101 | |||
102 | #[test] | ||
103 | fn flip_trait_bound_works_for_complex_bounds() { | ||
104 | check_assist( | ||
105 | flip_trait_bound, | ||
106 | "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }", | ||
107 | "struct S<T> where T: b_mod::B<T> <|>+ A<T> + C<T> { }", | ||
108 | ) | ||
109 | } | ||
110 | |||
111 | #[test] | ||
112 | fn flip_trait_bound_works_for_long_bounds() { | ||
113 | check_assist( | ||
114 | flip_trait_bound, | ||
115 | "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }", | ||
116 | "struct S<T> where T: A + B + C + D + E + G +<|> F + H + I + J { }", | ||
117 | ) | ||
118 | } | ||
119 | } | ||
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs index 0ccf9d730..6e1e3de84 100644 --- a/crates/ra_assists/src/doc_tests.rs +++ b/crates/ra_assists/src/doc_tests.rs | |||
@@ -17,7 +17,17 @@ fn check(assist_id: &str, before: &str, after: &str) { | |||
17 | let (_assist_id, action) = crate::assists(&db, frange) | 17 | let (_assist_id, action) = crate::assists(&db, frange) |
18 | .into_iter() | 18 | .into_iter() |
19 | .find(|(id, _)| id.id.0 == assist_id) | 19 | .find(|(id, _)| id.id.0 == assist_id) |
20 | .unwrap_or_else(|| panic!("Assist {:?} is not applicable", assist_id)); | 20 | .unwrap_or_else(|| { |
21 | panic!( | ||
22 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | ||
23 | assist_id, | ||
24 | crate::assists(&db, frange) | ||
25 | .into_iter() | ||
26 | .map(|(id, _)| id.id.0) | ||
27 | .collect::<Vec<_>>() | ||
28 | .join(", ") | ||
29 | ) | ||
30 | }); | ||
21 | 31 | ||
22 | let actual = action.edit.apply(&before); | 32 | let actual = action.edit.apply(&before); |
23 | assert_eq_text!(after, &actual); | 33 | assert_eq_text!(after, &actual); |
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index b8d335911..ebe49aecf 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs | |||
@@ -256,6 +256,19 @@ fn main() { | |||
256 | } | 256 | } |
257 | 257 | ||
258 | #[test] | 258 | #[test] |
259 | fn doctest_flip_trait_bound() { | ||
260 | check( | ||
261 | "flip_trait_bound", | ||
262 | r#####" | ||
263 | fn foo<T: Clone +<|> Copy>() { } | ||
264 | "#####, | ||
265 | r#####" | ||
266 | fn foo<T: Copy + Clone>() { } | ||
267 | "#####, | ||
268 | ) | ||
269 | } | ||
270 | |||
271 | #[test] | ||
259 | fn doctest_inline_local_variable() { | 272 | fn doctest_inline_local_variable() { |
260 | check( | 273 | check( |
261 | "inline_local_variable", | 274 | "inline_local_variable", |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 7c226572a..7a1657d87 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -97,6 +97,7 @@ mod assists { | |||
97 | mod apply_demorgan; | 97 | mod apply_demorgan; |
98 | mod flip_comma; | 98 | mod flip_comma; |
99 | mod flip_binexpr; | 99 | mod flip_binexpr; |
100 | mod flip_trait_bound; | ||
100 | mod change_visibility; | 101 | mod change_visibility; |
101 | mod fill_match_arms; | 102 | mod fill_match_arms; |
102 | mod merge_match_arms; | 103 | mod merge_match_arms; |
@@ -123,6 +124,7 @@ mod assists { | |||
123 | merge_match_arms::merge_match_arms, | 124 | merge_match_arms::merge_match_arms, |
124 | flip_comma::flip_comma, | 125 | flip_comma::flip_comma, |
125 | flip_binexpr::flip_binexpr, | 126 | flip_binexpr::flip_binexpr, |
127 | flip_trait_bound::flip_trait_bound, | ||
126 | introduce_variable::introduce_variable, | 128 | introduce_variable::introduce_variable, |
127 | replace_if_let_with_match::replace_if_let_with_match, | 129 | replace_if_let_with_match::replace_if_let_with_match, |
128 | split_import::split_import, | 130 | split_import::split_import, |
diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs index 602757e92..4b7bfc0b1 100644 --- a/crates/ra_ide_api/src/extend_selection.rs +++ b/crates/ra_ide_api/src/extend_selection.rs | |||
@@ -5,7 +5,7 @@ use ra_syntax::{ | |||
5 | algo::find_covering_element, | 5 | algo::find_covering_element, |
6 | ast::{self, AstNode, AstToken}, | 6 | ast::{self, AstNode, AstToken}, |
7 | Direction, NodeOrToken, | 7 | Direction, NodeOrToken, |
8 | SyntaxKind::*, | 8 | SyntaxKind::{self, *}, |
9 | SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, | 9 | SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, |
10 | }; | 10 | }; |
11 | 11 | ||
@@ -29,10 +29,12 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange | |||
29 | USE_TREE_LIST, | 29 | USE_TREE_LIST, |
30 | TYPE_PARAM_LIST, | 30 | TYPE_PARAM_LIST, |
31 | TYPE_ARG_LIST, | 31 | TYPE_ARG_LIST, |
32 | TYPE_BOUND_LIST, | ||
32 | PARAM_LIST, | 33 | PARAM_LIST, |
33 | ARG_LIST, | 34 | ARG_LIST, |
34 | ARRAY_EXPR, | 35 | ARRAY_EXPR, |
35 | TUPLE_EXPR, | 36 | TUPLE_EXPR, |
37 | WHERE_CLAUSE, | ||
36 | ]; | 38 | ]; |
37 | 39 | ||
38 | if range.is_empty() { | 40 | if range.is_empty() { |
@@ -146,13 +148,17 @@ fn pick_best<'a>(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken { | |||
146 | } | 148 | } |
147 | } | 149 | } |
148 | 150 | ||
149 | /// Extend list item selection to include nearby comma and whitespace. | 151 | /// Extend list item selection to include nearby delimiter and whitespace. |
150 | fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { | 152 | fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { |
151 | fn is_single_line_ws(node: &SyntaxToken) -> bool { | 153 | fn is_single_line_ws(node: &SyntaxToken) -> bool { |
152 | node.kind() == WHITESPACE && !node.text().contains('\n') | 154 | node.kind() == WHITESPACE && !node.text().contains('\n') |
153 | } | 155 | } |
154 | 156 | ||
155 | fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<SyntaxToken> { | 157 | fn nearby_delimiter( |
158 | delimiter_kind: SyntaxKind, | ||
159 | node: &SyntaxNode, | ||
160 | dir: Direction, | ||
161 | ) -> Option<SyntaxToken> { | ||
156 | node.siblings_with_tokens(dir) | 162 | node.siblings_with_tokens(dir) |
157 | .skip(1) | 163 | .skip(1) |
158 | .skip_while(|node| match node { | 164 | .skip_while(|node| match node { |
@@ -161,19 +167,26 @@ fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { | |||
161 | }) | 167 | }) |
162 | .next() | 168 | .next() |
163 | .and_then(|it| it.into_token()) | 169 | .and_then(|it| it.into_token()) |
164 | .filter(|node| node.kind() == T![,]) | 170 | .filter(|node| node.kind() == delimiter_kind) |
165 | } | 171 | } |
166 | 172 | ||
167 | if let Some(comma_node) = nearby_comma(node, Direction::Prev) { | 173 | let delimiter = match node.kind() { |
168 | return Some(TextRange::from_to(comma_node.text_range().start(), node.text_range().end())); | 174 | TYPE_BOUND => T![+], |
175 | _ => T![,], | ||
176 | }; | ||
177 | if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) { | ||
178 | return Some(TextRange::from_to( | ||
179 | delimiter_node.text_range().start(), | ||
180 | node.text_range().end(), | ||
181 | )); | ||
169 | } | 182 | } |
170 | if let Some(comma_node) = nearby_comma(node, Direction::Next) { | 183 | if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) { |
171 | // Include any following whitespace when comma if after list item. | 184 | // Include any following whitespace when delimiter is after list item. |
172 | let final_node = comma_node | 185 | let final_node = delimiter_node |
173 | .next_sibling_or_token() | 186 | .next_sibling_or_token() |
174 | .and_then(|it| it.into_token()) | 187 | .and_then(|it| it.into_token()) |
175 | .filter(|node| is_single_line_ws(node)) | 188 | .filter(|node| is_single_line_ws(node)) |
176 | .unwrap_or(comma_node); | 189 | .unwrap_or(delimiter_node); |
177 | 190 | ||
178 | return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end())); | 191 | return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end())); |
179 | } | 192 | } |
@@ -387,4 +400,53 @@ fn bar(){} | |||
387 | &["foo", "\" fn foo() {\""], | 400 | &["foo", "\" fn foo() {\""], |
388 | ); | 401 | ); |
389 | } | 402 | } |
403 | |||
404 | #[test] | ||
405 | fn test_extend_trait_bounds_list_in_where_clause() { | ||
406 | do_check( | ||
407 | r#" | ||
408 | fn foo<R>() | ||
409 | where | ||
410 | R: req::Request + 'static, | ||
411 | R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static, | ||
412 | R::Result: Serialize + 'static, | ||
413 | "#, | ||
414 | &[ | ||
415 | "DeserializeOwned", | ||
416 | "DeserializeOwned + ", | ||
417 | "DeserializeOwned + panic::UnwindSafe + 'static", | ||
418 | "R::Params: DeserializeOwned + panic::UnwindSafe + 'static", | ||
419 | "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,", | ||
420 | ], | ||
421 | ); | ||
422 | do_check(r#"fn foo<T>() where T: <|>Copy"#, &["Copy"]); | ||
423 | do_check(r#"fn foo<T>() where T: <|>Copy + Display"#, &["Copy", "Copy + "]); | ||
424 | do_check(r#"fn foo<T>() where T: <|>Copy +Display"#, &["Copy", "Copy +"]); | ||
425 | do_check(r#"fn foo<T>() where T: <|>Copy+Display"#, &["Copy", "Copy+"]); | ||
426 | do_check(r#"fn foo<T>() where T: Copy + <|>Display"#, &["Display", "+ Display"]); | ||
427 | do_check(r#"fn foo<T>() where T: Copy + <|>Display + Sync"#, &["Display", "+ Display"]); | ||
428 | do_check(r#"fn foo<T>() where T: Copy +<|>Display"#, &["Display", "+Display"]); | ||
429 | } | ||
430 | |||
431 | #[test] | ||
432 | fn test_extend_trait_bounds_list_inline() { | ||
433 | do_check(r#"fn foo<T: <|>Copy>() {}"#, &["Copy"]); | ||
434 | do_check(r#"fn foo<T: <|>Copy + Display>() {}"#, &["Copy", "Copy + "]); | ||
435 | do_check(r#"fn foo<T: <|>Copy +Display>() {}"#, &["Copy", "Copy +"]); | ||
436 | do_check(r#"fn foo<T: <|>Copy+Display>() {}"#, &["Copy", "Copy+"]); | ||
437 | do_check(r#"fn foo<T: Copy + <|>Display>() {}"#, &["Display", "+ Display"]); | ||
438 | do_check(r#"fn foo<T: Copy + <|>Display + Sync>() {}"#, &["Display", "+ Display"]); | ||
439 | do_check(r#"fn foo<T: Copy +<|>Display>() {}"#, &["Display", "+Display"]); | ||
440 | do_check( | ||
441 | r#"fn foo<T: Copy<|> + Display, U: Copy>() {}"#, | ||
442 | &[ | ||
443 | "Copy", | ||
444 | "Copy + ", | ||
445 | "Copy + Display", | ||
446 | "T: Copy + Display", | ||
447 | "T: Copy + Display, ", | ||
448 | "<T: Copy + Display, U: Copy>", | ||
449 | ], | ||
450 | ); | ||
451 | } | ||
390 | } | 452 | } |
diff --git a/docs/user/assists.md b/docs/user/assists.md index e4d08a7dc..b1fe44d84 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md | |||
@@ -248,6 +248,18 @@ fn main() { | |||
248 | } | 248 | } |
249 | ``` | 249 | ``` |
250 | 250 | ||
251 | ## `flip_trait_bound` | ||
252 | |||
253 | Flips two trait bounds. | ||
254 | |||
255 | ```rust | ||
256 | // BEFORE | ||
257 | fn foo<T: Clone +┃ Copy>() { } | ||
258 | |||
259 | // AFTER | ||
260 | fn foo<T: Copy + Clone>() { } | ||
261 | ``` | ||
262 | |||
251 | ## `inline_local_variable` | 263 | ## `inline_local_variable` |
252 | 264 | ||
253 | Inlines local variable. | 265 | Inlines local variable. |