1use std::iter::successors;
2
3use hir::Semantics;
4use ide_db::RootDatabase;
5use syntax::{
6 Direction, NodeOrToken,
7 SyntaxKind::{self, *},
8 SyntaxNode, SyntaxToken, T, TextRange, TextSize, TokenAtOffset,
9 algo::{self, skip_trivia_token},
10 ast::{self, AstNode, AstToken},
11};
12
13use crate::FileRange;
14
15pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
26 let sema = Semantics::new(db);
27 let src = sema.parse_guess_edition(frange.file_id);
28 try_extend_selection(&sema, src.syntax(), frange).unwrap_or(frange.range)
29}
30
31fn try_extend_selection(
32 sema: &Semantics<'_, RootDatabase>,
33 root: &SyntaxNode,
34 frange: FileRange,
35) -> Option<TextRange> {
36 let range = frange.range;
37
38 let string_kinds =
39 [COMMENT, INNER_DOC_COMMENT, OUTER_DOC_COMMENT, STRING, BYTE_STRING, C_STRING];
40 let list_kinds = [
41 RECORD_PAT_FIELD_LIST,
42 MATCH_ARM_LIST,
43 RECORD_FIELD_LIST,
44 TUPLE_FIELD_LIST,
45 RECORD_EXPR_FIELD_LIST,
46 VARIANT_LIST,
47 USE_TREE_LIST,
48 GENERIC_PARAM_LIST,
49 GENERIC_ARG_LIST,
50 TYPE_BOUND_LIST,
51 PARAM_LIST,
52 ARG_LIST,
53 ARRAY_EXPR,
54 TUPLE_EXPR,
55 TUPLE_TYPE,
56 TUPLE_PAT,
57 WHERE_CLAUSE,
58 ];
59
60 if range.is_empty() {
61 let offset = range.start();
62 let mut leaves = root.token_at_offset(offset);
63 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
64 return Some(extend_ws(root, leaves.next()?, offset));
65 }
66 let leaf_range = match leaves {
67 TokenAtOffset::None => return None,
68 TokenAtOffset::Single(l) => {
69 if string_kinds.contains(&l.kind()) {
70 extend_single_word_in_comment_or_string(&l, offset)
71 .unwrap_or_else(|| l.text_range())
72 } else {
73 l.text_range()
74 }
75 }
76 TokenAtOffset::Between(l, r) => pick_best(l, r).text_range(),
77 };
78 return Some(leaf_range);
79 };
80 let node = match root.covering_element(range) {
81 NodeOrToken::Token(token) => {
82 if token.text_range() != range {
83 return Some(token.text_range());
84 }
85 if let Some(comment) = ast::AnyComment::cast(token.clone())
86 && let Some(range) = extend_comments(comment)
87 {
88 return Some(range);
89 }
90 token.parent()?
91 }
92 NodeOrToken::Node(node) => node,
93 };
94
95 if node.kind() == TOKEN_TREE
97 && let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast)
98 && let Some(range) = extend_tokens_from_range(sema, macro_call, range)
99 {
100 return Some(range);
101 }
102
103 if node.text_range() != range {
104 return Some(node.text_range());
105 }
106
107 let node = shallowest_node(&node);
108
109 if node.parent().is_some_and(|n| list_kinds.contains(&n.kind()))
110 && let Some(range) = extend_list_item(&node)
111 {
112 return Some(range);
113 }
114
115 node.parent().map(|it| it.text_range())
116}
117
118fn extend_tokens_from_range(
119 sema: &Semantics<'_, RootDatabase>,
120 macro_call: ast::MacroCall,
121 original_range: TextRange,
122) -> Option<TextRange> {
123 let src = macro_call.syntax().covering_element(original_range);
124 let (first_token, last_token) = match src {
125 NodeOrToken::Node(it) => (it.first_token()?, it.last_token()?),
126 NodeOrToken::Token(it) => (it.clone(), it),
127 };
128
129 let mut first_token = skip_trivia_token(first_token, Direction::Next)?;
130 let mut last_token = skip_trivia_token(last_token, Direction::Prev)?;
131
132 while !original_range.contains_range(first_token.text_range()) {
133 first_token = skip_trivia_token(first_token.next_token()?, Direction::Next)?;
134 }
135 while !original_range.contains_range(last_token.text_range()) {
136 last_token = skip_trivia_token(last_token.prev_token()?, Direction::Prev)?;
137 }
138
139 let extended = {
141 let fst_expanded = sema.descend_into_macros_single_exact(first_token.clone());
142 let lst_expanded = sema.descend_into_macros_single_exact(last_token.clone());
143 let mut lca =
144 algo::least_common_ancestor(&fst_expanded.parent()?, &lst_expanded.parent()?)?;
145 lca = shallowest_node(&lca);
146 if lca.first_token() == Some(fst_expanded) && lca.last_token() == Some(lst_expanded) {
147 lca = lca.parent()?;
148 }
149 lca
150 };
151
152 let validate = || {
154 let extended = &extended;
155 move |token: &SyntaxToken| -> bool {
156 let expanded = sema.descend_into_macros_single_exact(token.clone());
157 let parent = match expanded.parent() {
158 Some(it) => it,
159 None => return false,
160 };
161 algo::least_common_ancestor(extended, &parent).as_ref() == Some(extended)
162 }
163 };
164
165 let first = successors(Some(first_token), |token| {
167 let token = token.prev_token()?;
168 skip_trivia_token(token, Direction::Prev)
169 })
170 .take_while(validate())
171 .last()?;
172
173 let last = successors(Some(last_token), |token| {
174 let token = token.next_token()?;
175 skip_trivia_token(token, Direction::Next)
176 })
177 .take_while(validate())
178 .last()?;
179
180 let range = first.text_range().cover(last.text_range());
181 if range.contains_range(original_range) && original_range != range { Some(range) } else { None }
182}
183
184fn shallowest_node(node: &SyntaxNode) -> SyntaxNode {
186 node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap()
187}
188
189fn extend_single_word_in_comment_or_string(
190 leaf: &SyntaxToken,
191 offset: TextSize,
192) -> Option<TextRange> {
193 let text: &str = leaf.text();
194 let cursor_position: u32 = (offset - leaf.text_range().start()).into();
195
196 let (before, after) = text.split_at(cursor_position as usize);
197
198 fn non_word_char(c: char) -> bool {
199 !(c.is_alphanumeric() || c == '_')
200 }
201
202 let start_idx = before.rfind(non_word_char)? as u32;
203 let end_idx = after.find(non_word_char).unwrap_or(after.len()) as u32;
204
205 fn ceil_char_boundary(text: &str, index: u32) -> u32 {
208 (index..).find(|&index| text.is_char_boundary(index as usize)).unwrap_or(text.len() as u32)
209 }
210
211 let from: TextSize = ceil_char_boundary(text, start_idx + 1).into();
212 let to: TextSize = (cursor_position + end_idx).into();
213
214 let range = TextRange::new(from, to);
215 if range.is_empty() { None } else { Some(range + leaf.text_range().start()) }
216}
217
218fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextSize) -> TextRange {
219 let ws_text = ws.text();
220 let suffix = TextRange::new(offset, ws.text_range().end()) - ws.text_range().start();
221 let prefix = TextRange::new(ws.text_range().start(), offset) - ws.text_range().start();
222 let ws_suffix = &ws_text[suffix];
223 let ws_prefix = &ws_text[prefix];
224 if ws_text.contains('\n')
225 && !ws_suffix.contains('\n')
226 && let Some(node) = ws.next_sibling_or_token()
227 {
228 let start = match ws_prefix.rfind('\n') {
229 Some(idx) => ws.text_range().start() + TextSize::from((idx + 1) as u32),
230 None => node.text_range().start(),
231 };
232 let end = if root.text().char_at(node.text_range().end()) == Some('\n') {
233 node.text_range().end() + TextSize::of('\n')
234 } else {
235 node.text_range().end()
236 };
237 return TextRange::new(start, end);
238 }
239 ws.text_range()
240}
241
242fn pick_best(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
243 return if priority(&r) > priority(&l) { r } else { l };
244 fn priority(n: &SyntaxToken) -> usize {
245 match n.kind() {
246 WHITESPACE => 0,
247 IDENT | T![self] | T![super] | T![crate] | T![Self] | LIFETIME_IDENT => 2,
248 _ => 1,
249 }
250 }
251}
252
253fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
255 fn is_single_line_ws(node: &SyntaxToken) -> bool {
256 node.kind() == WHITESPACE && !node.text().contains('\n')
257 }
258
259 fn nearby_delimiter(
260 delimiter_kind: SyntaxKind,
261 node: &SyntaxNode,
262 dir: Direction,
263 ) -> Option<SyntaxToken> {
264 node.siblings_with_tokens(dir)
265 .skip(1)
266 .find(|node| match node {
267 NodeOrToken::Node(_) => true,
268 NodeOrToken::Token(it) => !is_single_line_ws(it),
269 })
270 .and_then(|it| it.into_token())
271 .filter(|node| node.kind() == delimiter_kind)
272 }
273
274 let delimiter = match node.kind() {
275 TYPE_BOUND => T![+],
276 _ => T![,],
277 };
278
279 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) {
280 let final_node = delimiter_node
282 .next_sibling_or_token()
283 .and_then(|it| it.into_token())
284 .filter(is_single_line_ws)
285 .unwrap_or(delimiter_node);
286
287 return Some(TextRange::new(node.text_range().start(), final_node.text_range().end()));
288 }
289 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) {
290 return Some(TextRange::new(delimiter_node.text_range().start(), node.text_range().end()));
291 }
292
293 None
294}
295
296fn extend_comments(comment: ast::AnyComment) -> Option<TextRange> {
297 let prev = adj_comments(&comment, Direction::Prev);
298 let next = adj_comments(&comment, Direction::Next);
299 if prev != next {
300 Some(TextRange::new(prev.syntax().text_range().start(), next.syntax().text_range().end()))
301 } else {
302 None
303 }
304}
305
306fn adj_comments(comment: &ast::AnyComment, dir: Direction) -> ast::AnyComment {
307 let mut res = comment.clone();
308 for element in comment.syntax().siblings_with_tokens(dir) {
309 let token = match element.as_token() {
310 None => break,
311 Some(token) => token,
312 };
313 if let Some(c) = ast::AnyComment::cast(token.clone()) {
314 res = c
315 } else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
316 break;
317 }
318 }
319 res
320}
321
322#[cfg(test)]
323mod tests {
324 use crate::fixture;
325
326 use super::*;
327
328 fn do_check(before: &str, afters: &[&str]) {
329 let (analysis, position) = fixture::position(before);
330 let before = analysis.file_text(position.file_id).unwrap();
331 let range = TextRange::empty(position.offset);
332 let mut frange = FileRange { file_id: position.file_id, range };
333
334 for &after in afters {
335 frange.range = analysis.extend_selection(frange).unwrap();
336 let actual = &before[frange.range];
337 assert_eq!(after, actual);
338 }
339 }
340
341 #[test]
342 fn test_extend_selection_arith() {
343 do_check(r#"fn foo() { $01 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
344 }
345
346 #[test]
347 fn test_extend_selection_list() {
348 do_check(r#"fn foo($0x: i32) {}"#, &["x", "x: i32"]);
349 do_check(r#"fn foo($0x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
350 do_check(r#"fn foo($0x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,", "(x: i32,y: i32)"]);
351 do_check(r#"fn foo(x: i32, $0y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
352 do_check(r#"fn foo(x: i32, $0y: i32, ) {}"#, &["y", "y: i32", "y: i32, "]);
353 do_check(r#"fn foo(x: i32,$0y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
354
355 do_check(r#"const FOO: [usize; 2] = [ 22$0 , 33];"#, &["22", "22 , "]);
356 do_check(r#"const FOO: [usize; 2] = [ 22 , 33$0];"#, &["33", ", 33"]);
357 do_check(r#"const FOO: [usize; 2] = [ 22 , 33$0 ,];"#, &["33", "33 ,", "[ 22 , 33 ,]"]);
358
359 do_check(r#"fn main() { (1, 2$0) }"#, &["2", ", 2", "(1, 2)"]);
360
361 do_check(
362 r#"
363const FOO: [usize; 2] = [
364 22,
365 $033,
366]"#,
367 &["33", "33,"],
368 );
369
370 do_check(
371 r#"
372const FOO: [usize; 2] = [
373 22
374 , 33$0,
375]"#,
376 &["33", "33,"],
377 );
378 }
379
380 #[test]
381 fn test_extend_selection_start_of_the_line() {
382 do_check(
383 r#"
384impl S {
385$0 fn foo() {
386
387 }
388}"#,
389 &[" fn foo() {\n\n }\n"],
390 );
391 }
392
393 #[test]
394 fn test_extend_selection_doc_comments() {
395 do_check(
396 r#"
397struct A;
398
399/// bla
400/// bla
401struct B {
402 $0
403}
404 "#,
405 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
406 )
407 }
408
409 #[test]
410 fn test_extend_selection_comments() {
411 do_check(
412 r#"
413fn bar(){}
414
415// fn foo() {
416// 1 + $01
417// }
418
419// fn foo(){}
420 "#,
421 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
422 );
423
424 do_check(
425 r#"
426// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
427// pub enum Direction {
428// $0 Next,
429// Prev
430// }
431"#,
432 &[
433 "// Next,",
434 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
435 ],
436 );
437
438 do_check(
439 r#"
440/*
441foo
442_bar1$0*/
443"#,
444 &["_bar1", "/*\nfoo\n_bar1*/"],
445 );
446
447 do_check(r#"//!$0foo_2 bar"#, &["foo_2", "//!foo_2 bar"]);
448
449 do_check(r#"/$0/foo bar"#, &["//foo bar"]);
450 }
451
452 #[test]
453 fn test_extend_selection_prefer_idents() {
454 do_check(
455 r#"
456fn main() { foo$0+bar;}
457"#,
458 &["foo", "foo+bar"],
459 );
460 do_check(
461 r#"
462fn main() { foo+$0bar;}
463"#,
464 &["bar", "foo+bar"],
465 );
466 }
467
468 #[test]
469 fn test_extend_selection_prefer_lifetimes() {
470 do_check(r#"fn foo<$0'a>() {}"#, &["'a", "<'a>"]);
471 do_check(r#"fn foo<'a$0>() {}"#, &["'a", "<'a>"]);
472 }
473
474 #[test]
475 fn test_extend_selection_select_first_word() {
476 do_check(r#"// foo bar b$0az quxx"#, &["baz", "// foo bar baz quxx"]);
477 do_check(
478 r#"
479impl S {
480fn foo() {
481// hel$0lo world
482}
483}
484"#,
485 &["hello", "// hello world"],
486 );
487 }
488
489 #[test]
490 fn test_extend_selection_string() {
491 do_check(
492 r#"
493fn bar(){}
494
495" fn f$0oo() {"
496"#,
497 &["foo", "\" fn foo() {\""],
498 );
499 }
500
501 #[test]
502 fn test_extend_trait_bounds_list_in_where_clause() {
503 do_check(
504 r#"
505fn foo<R>()
506 where
507 R: req::Request + 'static,
508 R::Params: DeserializeOwned$0 + panic::UnwindSafe + 'static,
509 R::Result: Serialize + 'static,
510"#,
511 &[
512 "DeserializeOwned",
513 "DeserializeOwned + ",
514 "DeserializeOwned + panic::UnwindSafe + 'static",
515 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static",
516 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,",
517 ],
518 );
519 do_check(r#"fn foo<T>() where T: $0Copy"#, &["Copy"]);
520 do_check(r#"fn foo<T>() where T: $0Copy + Display"#, &["Copy", "Copy + "]);
521 do_check(r#"fn foo<T>() where T: $0Copy +Display"#, &["Copy", "Copy +"]);
522 do_check(r#"fn foo<T>() where T: $0Copy+Display"#, &["Copy", "Copy+"]);
523 do_check(r#"fn foo<T>() where T: Copy + $0Display"#, &["Display", "+ Display"]);
524 do_check(r#"fn foo<T>() where T: Copy + $0Display + Sync"#, &["Display", "Display + "]);
525 do_check(r#"fn foo<T>() where T: Copy +$0Display"#, &["Display", "+Display"]);
526 }
527
528 #[test]
529 fn test_extend_trait_bounds_list_inline() {
530 do_check(r#"fn foo<T: $0Copy>() {}"#, &["Copy"]);
531 do_check(r#"fn foo<T: $0Copy + Display>() {}"#, &["Copy", "Copy + "]);
532 do_check(r#"fn foo<T: $0Copy +Display>() {}"#, &["Copy", "Copy +"]);
533 do_check(r#"fn foo<T: $0Copy+Display>() {}"#, &["Copy", "Copy+"]);
534 do_check(r#"fn foo<T: Copy + $0Display>() {}"#, &["Display", "+ Display"]);
535 do_check(r#"fn foo<T: Copy + $0Display + Sync>() {}"#, &["Display", "Display + "]);
536 do_check(r#"fn foo<T: Copy +$0Display>() {}"#, &["Display", "+Display"]);
537 do_check(
538 r#"fn foo<T: Copy$0 + Display, U: Copy>() {}"#,
539 &[
540 "Copy",
541 "Copy + ",
542 "Copy + Display",
543 "T: Copy + Display",
544 "T: Copy + Display, ",
545 "<T: Copy + Display, U: Copy>",
546 ],
547 );
548 }
549
550 #[test]
551 fn test_extend_selection_on_tuple_in_type() {
552 do_check(
553 r#"fn main() { let _: (krate, $0_crate_def_map, module_id) = (); }"#,
554 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
555 );
556 do_check(
558 r#"fn main() { let _: (krate,$0_crate_def_map,module_id) = (); }"#,
559 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
560 );
561 do_check(
562 r#"
563fn main() { let _: (
564 krate,
565 _crate$0_def_map,
566 module_id
567) = (); }"#,
568 &[
569 "_crate_def_map",
570 "_crate_def_map,",
571 "(\n krate,\n _crate_def_map,\n module_id\n)",
572 ],
573 );
574 }
575
576 #[test]
577 fn test_extend_selection_on_tuple_in_rvalue() {
578 do_check(
579 r#"fn main() { let var = (krate, _crate_def_map$0, module_id); }"#,
580 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
581 );
582 do_check(
584 r#"fn main() { let var = (krate,_crate$0_def_map,module_id); }"#,
585 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
586 );
587 do_check(
588 r#"
589fn main() { let var = (
590 krate,
591 _crate_def_map$0,
592 module_id
593); }"#,
594 &[
595 "_crate_def_map",
596 "_crate_def_map,",
597 "(\n krate,\n _crate_def_map,\n module_id\n)",
598 ],
599 );
600 }
601
602 #[test]
603 fn test_extend_selection_on_tuple_pat() {
604 do_check(
605 r#"fn main() { let (krate, _crate_def_map$0, module_id) = var; }"#,
606 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
607 );
608 do_check(
610 r#"fn main() { let (krate,_crate$0_def_map,module_id) = var; }"#,
611 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
612 );
613 do_check(
614 r#"
615fn main() { let (
616 krate,
617 _crate_def_map$0,
618 module_id
619) = var; }"#,
620 &[
621 "_crate_def_map",
622 "_crate_def_map,",
623 "(\n krate,\n _crate_def_map,\n module_id\n)",
624 ],
625 );
626 }
627
628 #[test]
629 fn extend_selection_inside_macros() {
630 do_check(
631 r#"macro_rules! foo { ($item:item) => {$item} }
632 foo!{fn hello(na$0me:usize){}}"#,
633 &[
634 "name",
635 "name:usize",
636 "(name:usize)",
637 "fn hello(name:usize){}",
638 "{fn hello(name:usize){}}",
639 "foo!{fn hello(name:usize){}}",
640 ],
641 );
642 }
643
644 #[test]
645 fn extend_selection_inside_recur_macros() {
646 do_check(
647 r#" macro_rules! foo2 { ($item:item) => {$item} }
648 macro_rules! foo { ($item:item) => {foo2!($item);} }
649 foo!{fn hello(na$0me:usize){}}"#,
650 &[
651 "name",
652 "name:usize",
653 "(name:usize)",
654 "fn hello(name:usize){}",
655 "{fn hello(name:usize){}}",
656 "foo!{fn hello(name:usize){}}",
657 ],
658 );
659 }
660
661 #[test]
662 fn extend_selection_inside_str_with_wide_char() {
663 do_check(
665 r#"fn main() { let x = "═$0═══════"; }"#,
666 &[
667 r#""════════""#,
668 r#"let x = "════════";"#,
669 r#"{ let x = "════════"; }"#,
670 r#"fn main() { let x = "════════"; }"#,
671 ],
672 );
673 }
674}