]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/highlight_related.rs
Merge #9413
[rust.git] / crates / ide / src / highlight_related.rs
1 use hir::Semantics;
2 use ide_db::{
3     base_db::FilePosition,
4     defs::Definition,
5     helpers::{for_each_break_expr, for_each_tail_expr, pick_best_token},
6     search::{FileReference, ReferenceAccess, SearchScope},
7     RootDatabase,
8 };
9 use syntax::{
10     ast::{self, LoopBodyOwner},
11     match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, T,
12 };
13
14 use crate::{display::TryToNav, references, NavigationTarget};
15
16 pub struct HighlightedRange {
17     pub range: TextRange,
18     pub access: Option<ReferenceAccess>,
19 }
20
21 // Feature: Highlight Related
22 //
23 // Highlights constructs related to the thing under the cursor:
24 // - if on an identifier, highlights all references to that identifier in the current file
25 // - if on an `async` or `await token, highlights all yield points for that async context
26 // - if on a `return` token, `?` character or `->` return type arrow, highlights all exit points for that context
27 // - if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context
28 pub(crate) fn highlight_related(
29     sema: &Semantics<RootDatabase>,
30     position: FilePosition,
31 ) -> Option<Vec<HighlightedRange>> {
32     let _p = profile::span("highlight_related");
33     let syntax = sema.parse(position.file_id).syntax().clone();
34
35     let token = pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind {
36         T![?] => 2, // prefer `?` when the cursor is sandwiched like `await$0?`
37         T![await]
38         | T![async]
39         | T![return]
40         | T![break]
41         | T![loop]
42         | T![for]
43         | T![while]
44         | T![->] => 1,
45         _ => 0,
46     })?;
47
48     match token.kind() {
49         T![return] | T![?] | T![->] => highlight_exit_points(sema, token),
50         T![await] | T![async] => highlight_yield_points(token),
51         T![break] | T![loop] | T![for] | T![while] => highlight_break_points(token),
52         _ => highlight_references(sema, &syntax, position),
53     }
54 }
55
56 fn highlight_references(
57     sema: &Semantics<RootDatabase>,
58     syntax: &SyntaxNode,
59     FilePosition { offset, file_id }: FilePosition,
60 ) -> Option<Vec<HighlightedRange>> {
61     let def = references::find_def(sema, syntax, offset)?;
62     let usages = def.usages(sema).set_scope(Some(SearchScope::single_file(file_id))).all();
63
64     let declaration = match def {
65         Definition::ModuleDef(hir::ModuleDef::Module(module)) => {
66             Some(NavigationTarget::from_module_to_decl(sema.db, module))
67         }
68         def => def.try_to_nav(sema.db),
69     }
70     .filter(|decl| decl.file_id == file_id)
71     .and_then(|decl| {
72         let range = decl.focus_range?;
73         let access = references::decl_access(&def, syntax, range);
74         Some(HighlightedRange { range, access })
75     });
76
77     let file_refs = usages.references.get(&file_id).map_or(&[][..], Vec::as_slice);
78     let mut res = Vec::with_capacity(file_refs.len() + 1);
79     res.extend(declaration);
80     res.extend(
81         file_refs
82             .iter()
83             .map(|&FileReference { access, range, .. }| HighlightedRange { range, access }),
84     );
85     Some(res)
86 }
87
88 fn highlight_exit_points(
89     sema: &Semantics<RootDatabase>,
90     token: SyntaxToken,
91 ) -> Option<Vec<HighlightedRange>> {
92     fn hl(
93         sema: &Semantics<RootDatabase>,
94         body: Option<ast::Expr>,
95     ) -> Option<Vec<HighlightedRange>> {
96         let mut highlights = Vec::new();
97         let body = body?;
98         body.walk(&mut |expr| match expr {
99             ast::Expr::ReturnExpr(expr) => {
100                 if let Some(token) = expr.return_token() {
101                     highlights.push(HighlightedRange { access: None, range: token.text_range() });
102                 }
103             }
104             ast::Expr::TryExpr(try_) => {
105                 if let Some(token) = try_.question_mark_token() {
106                     highlights.push(HighlightedRange { access: None, range: token.text_range() });
107                 }
108             }
109             ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => {
110                 if sema.type_of_expr(&expr).map_or(false, |ty| ty.is_never()) {
111                     highlights
112                         .push(HighlightedRange { access: None, range: expr.syntax().text_range() });
113                 }
114             }
115             _ => (),
116         });
117         let tail = match body {
118             ast::Expr::BlockExpr(b) => b.tail_expr(),
119             e => Some(e),
120         };
121
122         if let Some(tail) = tail {
123             for_each_tail_expr(&tail, &mut |tail| {
124                 let range = match tail {
125                     ast::Expr::BreakExpr(b) => b
126                         .break_token()
127                         .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
128                     _ => tail.syntax().text_range(),
129                 };
130                 highlights.push(HighlightedRange { access: None, range })
131             });
132         }
133         Some(highlights)
134     }
135     for anc in token.ancestors() {
136         return match_ast! {
137             match anc {
138                 ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)),
139                 ast::ClosureExpr(closure) => hl(sema, closure.body()),
140                 ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) {
141                     hl(sema, effect.block_expr().map(ast::Expr::BlockExpr))
142                 } else {
143                     continue;
144                 },
145                 _ => continue,
146             }
147         };
148     }
149     None
150 }
151
152 fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
153     fn hl(
154         token: Option<SyntaxToken>,
155         label: Option<ast::Label>,
156         body: Option<ast::BlockExpr>,
157     ) -> Option<Vec<HighlightedRange>> {
158         let mut highlights = Vec::new();
159         let range = cover_range(
160             token.map(|tok| tok.text_range()),
161             label.as_ref().map(|it| it.syntax().text_range()),
162         );
163         highlights.extend(range.map(|range| HighlightedRange { access: None, range }));
164         for_each_break_expr(label, body, &mut |break_| {
165             let range = cover_range(
166                 break_.break_token().map(|it| it.text_range()),
167                 break_.lifetime().map(|it| it.syntax().text_range()),
168             );
169             highlights.extend(range.map(|range| HighlightedRange { access: None, range }));
170         });
171         Some(highlights)
172     }
173     let parent = token.parent()?;
174     let lbl = match_ast! {
175         match parent {
176             ast::BreakExpr(b) => b.lifetime(),
177             ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()),
178             ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()),
179             ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()),
180             ast::EffectExpr(b) => Some(b.label().and_then(|it| it.lifetime())?),
181             _ => return None,
182         }
183     };
184     let lbl = lbl.as_ref();
185     let label_matches = |def_lbl: Option<ast::Label>| match lbl {
186         Some(lbl) => {
187             Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text())
188         }
189         None => true,
190     };
191     for anc in token.ancestors().flat_map(ast::Expr::cast) {
192         return match anc {
193             ast::Expr::LoopExpr(l) if label_matches(l.label()) => {
194                 hl(l.loop_token(), l.label(), l.loop_body())
195             }
196             ast::Expr::ForExpr(f) if label_matches(f.label()) => {
197                 hl(f.for_token(), f.label(), f.loop_body())
198             }
199             ast::Expr::WhileExpr(w) if label_matches(w.label()) => {
200                 hl(w.while_token(), w.label(), w.loop_body())
201             }
202             ast::Expr::EffectExpr(e) if e.label().is_some() && label_matches(e.label()) => {
203                 hl(None, e.label(), e.block_expr())
204             }
205             _ => continue,
206         };
207     }
208     None
209 }
210
211 fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
212     fn hl(
213         async_token: Option<SyntaxToken>,
214         body: Option<ast::Expr>,
215     ) -> Option<Vec<HighlightedRange>> {
216         let mut highlights = Vec::new();
217         highlights.push(HighlightedRange { access: None, range: async_token?.text_range() });
218         if let Some(body) = body {
219             body.walk(&mut |expr| {
220                 if let ast::Expr::AwaitExpr(expr) = expr {
221                     if let Some(token) = expr.await_token() {
222                         highlights
223                             .push(HighlightedRange { access: None, range: token.text_range() });
224                     }
225                 }
226             });
227         }
228         Some(highlights)
229     }
230     for anc in token.ancestors() {
231         return match_ast! {
232             match anc {
233                 ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
234                 ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr().map(ast::Expr::BlockExpr)),
235                 ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()),
236                 _ => continue,
237             }
238         };
239     }
240     None
241 }
242
243 fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
244     match (r0, r1) {
245         (Some(r0), Some(r1)) => Some(r0.cover(r1)),
246         (Some(range), None) => Some(range),
247         (None, Some(range)) => Some(range),
248         (None, None) => None,
249     }
250 }
251
252 #[cfg(test)]
253 mod tests {
254     use crate::fixture;
255
256     use super::*;
257
258     fn check(ra_fixture: &str) {
259         let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
260         let hls = analysis.highlight_related(pos).unwrap().unwrap();
261
262         let mut expected = annotations
263             .into_iter()
264             .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
265             .collect::<Vec<_>>();
266
267         let mut actual = hls
268             .into_iter()
269             .map(|hl| {
270                 (
271                     hl.range,
272                     hl.access.map(|it| {
273                         match it {
274                             ReferenceAccess::Read => "read",
275                             ReferenceAccess::Write => "write",
276                         }
277                         .to_string()
278                     }),
279                 )
280             })
281             .collect::<Vec<_>>();
282         actual.sort_by_key(|(range, _)| range.start());
283         expected.sort_by_key(|(range, _)| range.start());
284
285         assert_eq!(expected, actual);
286     }
287
288     #[test]
289     fn test_hl_module() {
290         check(
291             r#"
292 //- /lib.rs
293 mod foo$0;
294  // ^^^
295 //- /foo.rs
296 struct Foo;
297 "#,
298         );
299     }
300
301     #[test]
302     fn test_hl_self_in_crate_root() {
303         check(
304             r#"
305 use self$0;
306 "#,
307         );
308     }
309
310     #[test]
311     fn test_hl_self_in_module() {
312         check(
313             r#"
314 //- /lib.rs
315 mod foo;
316 //- /foo.rs
317 use self$0;
318 "#,
319         );
320     }
321
322     #[test]
323     fn test_hl_local() {
324         check(
325             r#"
326 fn foo() {
327     let mut bar = 3;
328          // ^^^ write
329     bar$0;
330  // ^^^ read
331 }
332 "#,
333         );
334     }
335
336     #[test]
337     fn test_hl_yield_points() {
338         check(
339             r#"
340 pub async fn foo() {
341  // ^^^^^
342     let x = foo()
343         .await$0
344       // ^^^^^
345         .await;
346       // ^^^^^
347     || { 0.await };
348     (async { 0.await }).await
349                      // ^^^^^
350 }
351 "#,
352         );
353     }
354
355     #[test]
356     fn test_hl_yield_points2() {
357         check(
358             r#"
359 pub async$0 fn foo() {
360  // ^^^^^
361     let x = foo()
362         .await
363       // ^^^^^
364         .await;
365       // ^^^^^
366     || { 0.await };
367     (async { 0.await }).await
368                      // ^^^^^
369 }
370 "#,
371         );
372     }
373
374     #[test]
375     fn test_hl_yield_nested_fn() {
376         check(
377             r#"
378 async fn foo() {
379     async fn foo2() {
380  // ^^^^^
381         async fn foo3() {
382             0.await
383         }
384         0.await$0
385        // ^^^^^
386     }
387     0.await
388 }
389 "#,
390         );
391     }
392
393     #[test]
394     fn test_hl_yield_nested_async_blocks() {
395         check(
396             r#"
397 async fn foo() {
398     (async {
399   // ^^^^^
400         (async {
401            0.await
402         }).await$0 }
403         // ^^^^^
404     ).await;
405 }
406 "#,
407         );
408     }
409
410     #[test]
411     fn test_hl_exit_points() {
412         check(
413             r#"
414 fn foo() -> u32 {
415     if true {
416         return$0 0;
417      // ^^^^^^
418     }
419
420     0?;
421   // ^
422     0xDEAD_BEEF
423  // ^^^^^^^^^^^
424 }
425 "#,
426         );
427     }
428
429     #[test]
430     fn test_hl_exit_points2() {
431         check(
432             r#"
433 fn foo() ->$0 u32 {
434     if true {
435         return 0;
436      // ^^^^^^
437     }
438
439     0?;
440   // ^
441     0xDEAD_BEEF
442  // ^^^^^^^^^^^
443 }
444 "#,
445         );
446     }
447
448     #[test]
449     fn test_hl_prefer_ref_over_tail_exit() {
450         check(
451             r#"
452 fn foo() -> u32 {
453 // ^^^
454     if true {
455         return 0;
456     }
457
458     0?;
459
460     foo$0()
461  // ^^^
462 }
463 "#,
464         );
465     }
466
467     #[test]
468     fn test_hl_never_call_is_exit_point() {
469         check(
470             r#"
471 struct Never;
472 impl Never {
473     fn never(self) -> ! { loop {} }
474 }
475 macro_rules! never {
476     () => { never() }
477 }
478 fn never() -> ! { loop {} }
479 fn foo() ->$0 u32 {
480     never();
481  // ^^^^^^^
482     never!();
483  // FIXME sema doesn't give us types for macrocalls
484
485     Never.never();
486  // ^^^^^^^^^^^^^
487
488     0
489  // ^
490 }
491 "#,
492         );
493     }
494
495     #[test]
496     fn test_hl_inner_tail_exit_points() {
497         check(
498             r#"
499 fn foo() ->$0 u32 {
500     if true {
501         unsafe {
502             return 5;
503          // ^^^^^^
504             5
505          // ^
506         }
507     } else {
508         match 5 {
509             6 => 100,
510               // ^^^
511             7 => loop {
512                 break 5;
513              // ^^^^^
514             }
515             8 => 'a: loop {
516                 'b: loop {
517                     break 'a 5;
518                  // ^^^^^
519                     break 'b 5;
520                     break 5;
521                 };
522             }
523             //
524             _ => 500,
525               // ^^^
526         }
527     }
528 }
529 "#,
530         );
531     }
532
533     #[test]
534     fn test_hl_inner_tail_exit_points_labeled_block() {
535         check(
536             r#"
537 fn foo() ->$0 u32 {
538     'foo: {
539         break 'foo 0;
540      // ^^^^^
541         loop {
542             break;
543             break 'foo 0;
544          // ^^^^^
545         }
546         0
547      // ^
548     }
549 }
550 "#,
551         );
552     }
553
554     #[test]
555     fn test_hl_break_loop() {
556         check(
557             r#"
558 fn foo() {
559     'outer: loop {
560  // ^^^^^^^^^^^^
561          break;
562       // ^^^^^
563          'inner: loop {
564             break;
565             'innermost: loop {
566                 break 'outer;
567              // ^^^^^^^^^^^^
568                 break 'inner;
569             }
570             break$0 'outer;
571          // ^^^^^^^^^^^^
572             break;
573         }
574         break;
575      // ^^^^^
576     }
577 }
578 "#,
579         );
580     }
581
582     #[test]
583     fn test_hl_break_loop2() {
584         check(
585             r#"
586 fn foo() {
587     'outer: loop {
588         break;
589         'inner: loop {
590      // ^^^^^^^^^^^^
591             break;
592          // ^^^^^
593             'innermost: loop {
594                 break 'outer;
595                 break 'inner;
596              // ^^^^^^^^^^^^
597             }
598             break 'outer;
599             break$0;
600          // ^^^^^
601         }
602         break;
603     }
604 }
605 "#,
606         );
607     }
608
609     #[test]
610     fn test_hl_break_for() {
611         check(
612             r#"
613 fn foo() {
614     'outer: for _ in () {
615  // ^^^^^^^^^^^
616          break;
617       // ^^^^^
618          'inner: for _ in () {
619             break;
620             'innermost: for _ in () {
621                 break 'outer;
622              // ^^^^^^^^^^^^
623                 break 'inner;
624             }
625             break$0 'outer;
626          // ^^^^^^^^^^^^
627             break;
628         }
629         break;
630      // ^^^^^
631     }
632 }
633 "#,
634         );
635     }
636
637     #[test]
638     fn test_hl_break_while() {
639         check(
640             r#"
641 fn foo() {
642     'outer: while true {
643  // ^^^^^^^^^^^^^
644          break;
645       // ^^^^^
646          'inner: while true {
647             break;
648             'innermost: while true {
649                 break 'outer;
650              // ^^^^^^^^^^^^
651                 break 'inner;
652             }
653             break$0 'outer;
654          // ^^^^^^^^^^^^
655             break;
656         }
657         break;
658      // ^^^^^
659     }
660 }
661 "#,
662         );
663     }
664
665     #[test]
666     fn test_hl_break_labeled_block() {
667         check(
668             r#"
669 fn foo() {
670     'outer: {
671  // ^^^^^^^
672          break;
673       // ^^^^^
674          'inner: {
675             break;
676             'innermost: {
677                 break 'outer;
678              // ^^^^^^^^^^^^
679                 break 'inner;
680             }
681             break$0 'outer;
682          // ^^^^^^^^^^^^
683             break;
684         }
685         break;
686      // ^^^^^
687     }
688 }
689 "#,
690         );
691     }
692
693     #[test]
694     fn test_hl_break_unlabeled_loop() {
695         check(
696             r#"
697 fn foo() {
698     loop {
699  // ^^^^
700         break$0;
701      // ^^^^^
702     }
703 }
704 "#,
705         );
706     }
707
708     #[test]
709     fn test_hl_break_unlabeled_block_in_loop() {
710         check(
711             r#"
712 fn foo() {
713     loop {
714  // ^^^^
715         {
716             break$0;
717          // ^^^^^
718         }
719     }
720 }
721 "#,
722         );
723     }
724 }