aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/assists/flip_trait_bound.rs119
-rw-r--r--crates/ra_assists/src/doc_tests.rs12
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs13
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_ide_api/src/extend_selection.rs82
5 files changed, 217 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 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 algo::non_trivia_sibling,
4 ast::{self, AstNode},
5 Direction, T,
6};
7
8use 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// ```
21pub(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)]
46mod 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]
259fn doctest_flip_trait_bound() {
260 check(
261 "flip_trait_bound",
262 r#####"
263fn foo<T: Clone +<|> Copy>() { }
264"#####,
265 r#####"
266fn foo<T: Copy + Clone>() { }
267"#####,
268 )
269}
270
271#[test]
259fn doctest_inline_local_variable() { 272fn 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.
150fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { 152fn 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#"
408fn 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}