1 //! FIXME: write short doc here
3 use ra_db::SourceDatabase;
5 algo::find_covering_element,
6 ast::{self, AstNode, AstToken},
7 Direction, NodeOrToken,
9 SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T,
12 use crate::{db::RootDatabase, FileRange};
14 // FIXME: restore macro support
15 pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
16 let parse = db.parse(frange.file_id);
17 try_extend_selection(parse.tree().syntax(), frange.range).unwrap_or(frange.range)
20 fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> {
21 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
23 RECORD_FIELD_PAT_LIST,
25 RECORD_FIELD_DEF_LIST,
42 let offset = range.start();
43 let mut leaves = root.token_at_offset(offset);
44 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
45 return Some(extend_ws(root, leaves.next()?, offset));
47 let leaf_range = match leaves {
48 TokenAtOffset::None => return None,
49 TokenAtOffset::Single(l) => {
50 if string_kinds.contains(&l.kind()) {
51 extend_single_word_in_comment_or_string(&l, offset)
52 .unwrap_or_else(|| l.text_range())
57 TokenAtOffset::Between(l, r) => pick_best(l, r).text_range(),
59 return Some(leaf_range);
61 let node = match find_covering_element(root, range) {
62 NodeOrToken::Token(token) => {
63 if token.text_range() != range {
64 return Some(token.text_range());
66 if let Some(comment) = ast::Comment::cast(token.clone()) {
67 if let Some(range) = extend_comments(comment) {
73 NodeOrToken::Node(node) => node,
75 if node.text_range() != range {
76 return Some(node.text_range());
79 // Using shallowest node with same range allows us to traverse siblings.
80 let node = node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap();
82 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
83 if let Some(range) = extend_list_item(&node) {
88 node.parent().map(|it| it.text_range())
91 fn extend_single_word_in_comment_or_string(
94 ) -> Option<TextRange> {
95 let text: &str = leaf.text();
96 let cursor_position: u32 = (offset - leaf.text_range().start()).into();
98 let (before, after) = text.split_at(cursor_position as usize);
100 fn non_word_char(c: char) -> bool {
101 !(c.is_alphanumeric() || c == '_')
104 let start_idx = before.rfind(non_word_char)? as u32;
105 let end_idx = after.find(non_word_char).unwrap_or_else(|| after.len()) as u32;
107 let from: TextUnit = (start_idx + 1).into();
108 let to: TextUnit = (cursor_position + end_idx).into();
110 let range = TextRange::from_to(from, to);
111 if range.is_empty() {
114 Some(range + leaf.text_range().start())
118 fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextUnit) -> TextRange {
119 let ws_text = ws.text();
120 let suffix = TextRange::from_to(offset, ws.text_range().end()) - ws.text_range().start();
121 let prefix = TextRange::from_to(ws.text_range().start(), offset) - ws.text_range().start();
122 let ws_suffix = &ws_text.as_str()[suffix];
123 let ws_prefix = &ws_text.as_str()[prefix];
124 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
125 if let Some(node) = ws.next_sibling_or_token() {
126 let start = match ws_prefix.rfind('\n') {
127 Some(idx) => ws.text_range().start() + TextUnit::from((idx + 1) as u32),
128 None => node.text_range().start(),
130 let end = if root.text().char_at(node.text_range().end()) == Some('\n') {
131 node.text_range().end() + TextUnit::of_char('\n')
133 node.text_range().end()
135 return TextRange::from_to(start, end);
141 fn pick_best<'a>(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
142 return if priority(&r) > priority(&l) { r } else { l };
143 fn priority(n: &SyntaxToken) -> usize {
146 IDENT | T![self] | T![super] | T![crate] | LIFETIME => 2,
152 /// Extend list item selection to include nearby delimiter and whitespace.
153 fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
154 fn is_single_line_ws(node: &SyntaxToken) -> bool {
155 node.kind() == WHITESPACE && !node.text().contains('\n')
159 delimiter_kind: SyntaxKind,
162 ) -> Option<SyntaxToken> {
163 node.siblings_with_tokens(dir)
165 .skip_while(|node| match node {
166 NodeOrToken::Node(_) => false,
167 NodeOrToken::Token(it) => is_single_line_ws(it),
170 .and_then(|it| it.into_token())
171 .filter(|node| node.kind() == delimiter_kind)
174 let delimiter = match node.kind() {
179 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) {
180 // Include any following whitespace when delimiter is after list item.
181 let final_node = delimiter_node
182 .next_sibling_or_token()
183 .and_then(|it| it.into_token())
184 .filter(|node| is_single_line_ws(node))
185 .unwrap_or(delimiter_node);
187 return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end()));
189 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) {
190 return Some(TextRange::from_to(
191 delimiter_node.text_range().start(),
192 node.text_range().end(),
199 fn extend_comments(comment: ast::Comment) -> Option<TextRange> {
200 let prev = adj_comments(&comment, Direction::Prev);
201 let next = adj_comments(&comment, Direction::Next);
203 Some(TextRange::from_to(
204 prev.syntax().text_range().start(),
205 next.syntax().text_range().end(),
212 fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
213 let mut res = comment.clone();
214 for element in comment.syntax().siblings_with_tokens(dir) {
215 let token = match element.as_token() {
217 Some(token) => token,
219 if let Some(c) = ast::Comment::cast(token.clone()) {
221 } else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
230 use ra_syntax::{AstNode, SourceFile};
231 use test_utils::extract_offset;
235 fn do_check(before: &str, afters: &[&str]) {
236 let (cursor, before) = extract_offset(before);
237 let parse = SourceFile::parse(&before);
238 let mut range = TextRange::offset_len(cursor, 0.into());
239 for &after in afters {
240 range = try_extend_selection(parse.tree().syntax(), range).unwrap();
241 let actual = &before[range];
242 assert_eq!(after, actual);
247 fn test_extend_selection_arith() {
248 do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
252 fn test_extend_selection_list() {
253 do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
254 do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
255 do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,", "(x: i32,y: i32)"]);
256 do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
257 do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", "y: i32, "]);
258 do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
260 do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
261 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
262 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", "33 ,", "[ 22 , 33 ,]"]);
264 do_check(r#"fn main() { (1, 2<|>) }"#, &["2", ", 2", "(1, 2)"]);
268 const FOO: [usize; 2] = [
277 const FOO: [usize; 2] = [
286 fn test_extend_selection_start_of_the_line() {
294 &[" fn foo() {\n\n }\n"],
299 fn test_extend_selection_doc_comments() {
310 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
315 fn test_extend_selection_comments() {
326 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
331 // #[derive(Debug, Clone, Copy, PartialEq, Eq)]
332 // pub enum Direction {
339 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
349 &["_bar1", "/*\nfoo\n_bar1*/"],
352 do_check(r#"//!<|>foo_2 bar"#, &["foo_2", "//!foo_2 bar"]);
354 do_check(r#"/<|>/foo bar"#, &["//foo bar"]);
358 fn test_extend_selection_prefer_idents() {
361 fn main() { foo<|>+bar;}
367 fn main() { foo+<|>bar;}
374 fn test_extend_selection_prefer_lifetimes() {
375 do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
376 do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
380 fn test_extend_selection_select_first_word() {
381 do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
390 &["hello", "// hello world"],
395 fn test_extend_selection_string() {
402 &["foo", "\" fn foo() {\""],
407 fn test_extend_trait_bounds_list_in_where_clause() {
412 R: req::Request + 'static,
413 R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static,
414 R::Result: Serialize + 'static,
418 "DeserializeOwned + ",
419 "DeserializeOwned + panic::UnwindSafe + 'static",
420 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static",
421 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,",
424 do_check(r#"fn foo<T>() where T: <|>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"#, &["Copy", "Copy +"]);
427 do_check(r#"fn foo<T>() where T: <|>Copy+Display"#, &["Copy", "Copy+"]);
428 do_check(r#"fn foo<T>() where T: Copy + <|>Display"#, &["Display", "+ Display"]);
429 do_check(r#"fn foo<T>() where T: Copy + <|>Display + Sync"#, &["Display", "Display + "]);
430 do_check(r#"fn foo<T>() where T: Copy +<|>Display"#, &["Display", "+Display"]);
434 fn test_extend_trait_bounds_list_inline() {
435 do_check(r#"fn foo<T: <|>Copy>() {}"#, &["Copy"]);
436 do_check(r#"fn foo<T: <|>Copy + Display>() {}"#, &["Copy", "Copy + "]);
437 do_check(r#"fn foo<T: <|>Copy +Display>() {}"#, &["Copy", "Copy +"]);
438 do_check(r#"fn foo<T: <|>Copy+Display>() {}"#, &["Copy", "Copy+"]);
439 do_check(r#"fn foo<T: Copy + <|>Display>() {}"#, &["Display", "+ Display"]);
440 do_check(r#"fn foo<T: Copy + <|>Display + Sync>() {}"#, &["Display", "Display + "]);
441 do_check(r#"fn foo<T: Copy +<|>Display>() {}"#, &["Display", "+Display"]);
443 r#"fn foo<T: Copy<|> + Display, U: Copy>() {}"#,
449 "T: Copy + Display, ",
450 "<T: Copy + Display, U: Copy>",
456 fn test_extend_selection_on_tuple_in_type() {
458 r#"fn main() { let _: (krate, <|>_crate_def_map, module_id) = (); }"#,
459 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
461 // white space variations
463 r#"fn main() { let _: (krate,<|>_crate_def_map,module_id) = (); }"#,
464 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
476 "(\n krate,\n _crate_def_map,\n module_id\n)",
482 fn test_extend_selection_on_tuple_in_rvalue() {
484 r#"fn main() { let var = (krate, _crate_def_map<|>, module_id); }"#,
485 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
487 // white space variations
489 r#"fn main() { let var = (krate,_crate<|>_def_map,module_id); }"#,
490 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
494 fn main() { let var = (
502 "(\n krate,\n _crate_def_map,\n module_id\n)",