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 = [COMMENT, STRING, BYTE_STRING, C_STRING];
39 let list_kinds = [
40 RECORD_PAT_FIELD_LIST,
41 MATCH_ARM_LIST,
42 RECORD_FIELD_LIST,
43 TUPLE_FIELD_LIST,
44 RECORD_EXPR_FIELD_LIST,
45 VARIANT_LIST,
46 USE_TREE_LIST,
47 GENERIC_PARAM_LIST,
48 GENERIC_ARG_LIST,
49 TYPE_BOUND_LIST,
50 PARAM_LIST,
51 ARG_LIST,
52 ARRAY_EXPR,
53 TUPLE_EXPR,
54 TUPLE_TYPE,
55 TUPLE_PAT,
56 WHERE_CLAUSE,
57 ];
58
59 if range.is_empty() {
60 let offset = range.start();
61 let mut leaves = root.token_at_offset(offset);
62 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
63 return Some(extend_ws(root, leaves.next()?, offset));
64 }
65 let leaf_range = match leaves {
66 TokenAtOffset::None => return None,
67 TokenAtOffset::Single(l) => {
68 if string_kinds.contains(&l.kind()) {
69 extend_single_word_in_comment_or_string(&l, offset)
70 .unwrap_or_else(|| l.text_range())
71 } else {
72 l.text_range()
73 }
74 }
75 TokenAtOffset::Between(l, r) => pick_best(l, r).text_range(),
76 };
77 return Some(leaf_range);
78 };
79 let node = match root.covering_element(range) {
80 NodeOrToken::Token(token) => {
81 if token.text_range() != range {
82 return Some(token.text_range());
83 }
84 if let Some(comment) = ast::Comment::cast(token.clone()) {
85 if let Some(range) = extend_comments(comment) {
86 return Some(range);
87 }
88 }
89 token.parent()?
90 }
91 NodeOrToken::Node(node) => node,
92 };
93
94 if node.kind() == TOKEN_TREE {
96 if let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast) {
97 if let Some(range) = extend_tokens_from_range(sema, macro_call, range) {
98 return Some(range);
99 }
100 }
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 if let Some(range) = extend_list_item(&node) {
111 return Some(range);
112 }
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') && !ws_suffix.contains('\n') {
225 if let Some(node) = ws.next_sibling_or_token() {
226 let start = match ws_prefix.rfind('\n') {
227 Some(idx) => ws.text_range().start() + TextSize::from((idx + 1) as u32),
228 None => node.text_range().start(),
229 };
230 let end = if root.text().char_at(node.text_range().end()) == Some('\n') {
231 node.text_range().end() + TextSize::of('\n')
232 } else {
233 node.text_range().end()
234 };
235 return TextRange::new(start, end);
236 }
237 }
238 ws.text_range()
239}
240
241fn pick_best(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
242 return if priority(&r) > priority(&l) { r } else { l };
243 fn priority(n: &SyntaxToken) -> usize {
244 match n.kind() {
245 WHITESPACE => 0,
246 IDENT | T![self] | T![super] | T![crate] | T![Self] | LIFETIME_IDENT => 2,
247 _ => 1,
248 }
249 }
250}
251
252fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
254 fn is_single_line_ws(node: &SyntaxToken) -> bool {
255 node.kind() == WHITESPACE && !node.text().contains('\n')
256 }
257
258 fn nearby_delimiter(
259 delimiter_kind: SyntaxKind,
260 node: &SyntaxNode,
261 dir: Direction,
262 ) -> Option<SyntaxToken> {
263 node.siblings_with_tokens(dir)
264 .skip(1)
265 .find(|node| match node {
266 NodeOrToken::Node(_) => true,
267 NodeOrToken::Token(it) => !is_single_line_ws(it),
268 })
269 .and_then(|it| it.into_token())
270 .filter(|node| node.kind() == delimiter_kind)
271 }
272
273 let delimiter = match node.kind() {
274 TYPE_BOUND => T![+],
275 _ => T![,],
276 };
277
278 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) {
279 let final_node = delimiter_node
281 .next_sibling_or_token()
282 .and_then(|it| it.into_token())
283 .filter(is_single_line_ws)
284 .unwrap_or(delimiter_node);
285
286 return Some(TextRange::new(node.text_range().start(), final_node.text_range().end()));
287 }
288 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) {
289 return Some(TextRange::new(delimiter_node.text_range().start(), node.text_range().end()));
290 }
291
292 None
293}
294
295fn extend_comments(comment: ast::Comment) -> Option<TextRange> {
296 let prev = adj_comments(&comment, Direction::Prev);
297 let next = adj_comments(&comment, Direction::Next);
298 if prev != next {
299 Some(TextRange::new(prev.syntax().text_range().start(), next.syntax().text_range().end()))
300 } else {
301 None
302 }
303}
304
305fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
306 let mut res = comment.clone();
307 for element in comment.syntax().siblings_with_tokens(dir) {
308 let token = match element.as_token() {
309 None => break,
310 Some(token) => token,
311 };
312 if let Some(c) = ast::Comment::cast(token.clone()) {
313 res = c
314 } else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
315 break;
316 }
317 }
318 res
319}
320
321#[cfg(test)]
322mod tests {
323 use crate::fixture;
324
325 use super::*;
326
327 fn do_check(before: &str, afters: &[&str]) {
328 let (analysis, position) = fixture::position(before);
329 let before = analysis.file_text(position.file_id).unwrap();
330 let range = TextRange::empty(position.offset);
331 let mut frange = FileRange { file_id: position.file_id, range };
332
333 for &after in afters {
334 frange.range = analysis.extend_selection(frange).unwrap();
335 let actual = &before[frange.range];
336 assert_eq!(after, actual);
337 }
338 }
339
340 #[test]
341 fn test_extend_selection_arith() {
342 do_check(r#"fn foo() { $01 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
343 }
344
345 #[test]
346 fn test_extend_selection_list() {
347 do_check(r#"fn foo($0x: i32) {}"#, &["x", "x: i32"]);
348 do_check(r#"fn foo($0x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
349 do_check(r#"fn foo($0x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,", "(x: i32,y: i32)"]);
350 do_check(r#"fn foo(x: i32, $0y: i32) {}"#, &["y", "y: 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
354 do_check(r#"const FOO: [usize; 2] = [ 22$0 , 33];"#, &["22", "22 , "]);
355 do_check(r#"const FOO: [usize; 2] = [ 22 , 33$0];"#, &["33", ", 33"]);
356 do_check(r#"const FOO: [usize; 2] = [ 22 , 33$0 ,];"#, &["33", "33 ,", "[ 22 , 33 ,]"]);
357
358 do_check(r#"fn main() { (1, 2$0) }"#, &["2", ", 2", "(1, 2)"]);
359
360 do_check(
361 r#"
362const FOO: [usize; 2] = [
363 22,
364 $033,
365]"#,
366 &["33", "33,"],
367 );
368
369 do_check(
370 r#"
371const FOO: [usize; 2] = [
372 22
373 , 33$0,
374]"#,
375 &["33", "33,"],
376 );
377 }
378
379 #[test]
380 fn test_extend_selection_start_of_the_line() {
381 do_check(
382 r#"
383impl S {
384$0 fn foo() {
385
386 }
387}"#,
388 &[" fn foo() {\n\n }\n"],
389 );
390 }
391
392 #[test]
393 fn test_extend_selection_doc_comments() {
394 do_check(
395 r#"
396struct A;
397
398/// bla
399/// bla
400struct B {
401 $0
402}
403 "#,
404 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
405 )
406 }
407
408 #[test]
409 fn test_extend_selection_comments() {
410 do_check(
411 r#"
412fn bar(){}
413
414// fn foo() {
415// 1 + $01
416// }
417
418// fn foo(){}
419 "#,
420 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
421 );
422
423 do_check(
424 r#"
425// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
426// pub enum Direction {
427// $0 Next,
428// Prev
429// }
430"#,
431 &[
432 "// Next,",
433 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
434 ],
435 );
436
437 do_check(
438 r#"
439/*
440foo
441_bar1$0*/
442"#,
443 &["_bar1", "/*\nfoo\n_bar1*/"],
444 );
445
446 do_check(r#"//!$0foo_2 bar"#, &["foo_2", "//!foo_2 bar"]);
447
448 do_check(r#"/$0/foo bar"#, &["//foo bar"]);
449 }
450
451 #[test]
452 fn test_extend_selection_prefer_idents() {
453 do_check(
454 r#"
455fn main() { foo$0+bar;}
456"#,
457 &["foo", "foo+bar"],
458 );
459 do_check(
460 r#"
461fn main() { foo+$0bar;}
462"#,
463 &["bar", "foo+bar"],
464 );
465 }
466
467 #[test]
468 fn test_extend_selection_prefer_lifetimes() {
469 do_check(r#"fn foo<$0'a>() {}"#, &["'a", "<'a>"]);
470 do_check(r#"fn foo<'a$0>() {}"#, &["'a", "<'a>"]);
471 }
472
473 #[test]
474 fn test_extend_selection_select_first_word() {
475 do_check(r#"// foo bar b$0az quxx"#, &["baz", "// foo bar baz quxx"]);
476 do_check(
477 r#"
478impl S {
479fn foo() {
480// hel$0lo world
481}
482}
483"#,
484 &["hello", "// hello world"],
485 );
486 }
487
488 #[test]
489 fn test_extend_selection_string() {
490 do_check(
491 r#"
492fn bar(){}
493
494" fn f$0oo() {"
495"#,
496 &["foo", "\" fn foo() {\""],
497 );
498 }
499
500 #[test]
501 fn test_extend_trait_bounds_list_in_where_clause() {
502 do_check(
503 r#"
504fn foo<R>()
505 where
506 R: req::Request + 'static,
507 R::Params: DeserializeOwned$0 + panic::UnwindSafe + 'static,
508 R::Result: Serialize + 'static,
509"#,
510 &[
511 "DeserializeOwned",
512 "DeserializeOwned + ",
513 "DeserializeOwned + panic::UnwindSafe + 'static",
514 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static",
515 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,",
516 ],
517 );
518 do_check(r#"fn foo<T>() where T: $0Copy"#, &["Copy"]);
519 do_check(r#"fn foo<T>() where T: $0Copy + Display"#, &["Copy", "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: Copy + $0Display"#, &["Display", "+ Display"]);
523 do_check(r#"fn foo<T>() where T: Copy + $0Display + Sync"#, &["Display", "Display + "]);
524 do_check(r#"fn foo<T>() where T: Copy +$0Display"#, &["Display", "+Display"]);
525 }
526
527 #[test]
528 fn test_extend_trait_bounds_list_inline() {
529 do_check(r#"fn foo<T: $0Copy>() {}"#, &["Copy"]);
530 do_check(r#"fn foo<T: $0Copy + Display>() {}"#, &["Copy", "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: Copy + $0Display>() {}"#, &["Display", "+ Display"]);
534 do_check(r#"fn foo<T: Copy + $0Display + Sync>() {}"#, &["Display", "Display + "]);
535 do_check(r#"fn foo<T: Copy +$0Display>() {}"#, &["Display", "+Display"]);
536 do_check(
537 r#"fn foo<T: Copy$0 + Display, U: Copy>() {}"#,
538 &[
539 "Copy",
540 "Copy + ",
541 "Copy + Display",
542 "T: Copy + Display",
543 "T: Copy + Display, ",
544 "<T: Copy + Display, U: Copy>",
545 ],
546 );
547 }
548
549 #[test]
550 fn test_extend_selection_on_tuple_in_type() {
551 do_check(
552 r#"fn main() { let _: (krate, $0_crate_def_map, module_id) = (); }"#,
553 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
554 );
555 do_check(
557 r#"fn main() { let _: (krate,$0_crate_def_map,module_id) = (); }"#,
558 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
559 );
560 do_check(
561 r#"
562fn main() { let _: (
563 krate,
564 _crate$0_def_map,
565 module_id
566) = (); }"#,
567 &[
568 "_crate_def_map",
569 "_crate_def_map,",
570 "(\n krate,\n _crate_def_map,\n module_id\n)",
571 ],
572 );
573 }
574
575 #[test]
576 fn test_extend_selection_on_tuple_in_rvalue() {
577 do_check(
578 r#"fn main() { let var = (krate, _crate_def_map$0, module_id); }"#,
579 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
580 );
581 do_check(
583 r#"fn main() { let var = (krate,_crate$0_def_map,module_id); }"#,
584 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
585 );
586 do_check(
587 r#"
588fn main() { let var = (
589 krate,
590 _crate_def_map$0,
591 module_id
592); }"#,
593 &[
594 "_crate_def_map",
595 "_crate_def_map,",
596 "(\n krate,\n _crate_def_map,\n module_id\n)",
597 ],
598 );
599 }
600
601 #[test]
602 fn test_extend_selection_on_tuple_pat() {
603 do_check(
604 r#"fn main() { let (krate, _crate_def_map$0, module_id) = var; }"#,
605 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
606 );
607 do_check(
609 r#"fn main() { let (krate,_crate$0_def_map,module_id) = var; }"#,
610 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
611 );
612 do_check(
613 r#"
614fn main() { let (
615 krate,
616 _crate_def_map$0,
617 module_id
618) = var; }"#,
619 &[
620 "_crate_def_map",
621 "_crate_def_map,",
622 "(\n krate,\n _crate_def_map,\n module_id\n)",
623 ],
624 );
625 }
626
627 #[test]
628 fn extend_selection_inside_macros() {
629 do_check(
630 r#"macro_rules! foo { ($item:item) => {$item} }
631 foo!{fn hello(na$0me:usize){}}"#,
632 &[
633 "name",
634 "name:usize",
635 "(name:usize)",
636 "fn hello(name:usize){}",
637 "{fn hello(name:usize){}}",
638 "foo!{fn hello(name:usize){}}",
639 ],
640 );
641 }
642
643 #[test]
644 fn extend_selection_inside_recur_macros() {
645 do_check(
646 r#" macro_rules! foo2 { ($item:item) => {$item} }
647 macro_rules! foo { ($item:item) => {foo2!($item);} }
648 foo!{fn hello(na$0me:usize){}}"#,
649 &[
650 "name",
651 "name:usize",
652 "(name:usize)",
653 "fn hello(name:usize){}",
654 "{fn hello(name:usize){}}",
655 "foo!{fn hello(name:usize){}}",
656 ],
657 );
658 }
659
660 #[test]
661 fn extend_selection_inside_str_with_wide_char() {
662 do_check(
664 r#"fn main() { let x = "═$0═══════"; }"#,
665 &[
666 r#""════════""#,
667 r#"let x = "════════";"#,
668 r#"{ let x = "════════"; }"#,
669 r#"fn main() { let x = "════════"; }"#,
670 ],
671 );
672 }
673}