aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ide_api/src/extend_selection.rs82
1 files changed, 72 insertions, 10 deletions
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}