]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/highlight_related.rs
Merge #11579
[rust.git] / crates / ide / src / highlight_related.rs
1 use hir::Semantics;
2 use ide_db::{
3     base_db::{FileId, FilePosition},
4     defs::{Definition, IdentClass},
5     helpers::{
6         for_each_break_and_continue_expr, for_each_tail_expr, node_ext::walk_expr, pick_best_token,
7     },
8     search::{FileReference, ReferenceCategory, SearchScope},
9     RootDatabase,
10 };
11 use rustc_hash::FxHashSet;
12 use syntax::{
13     ast::{self, HasLoopBody},
14     match_ast, AstNode,
15     SyntaxKind::{self, IDENT, INT_NUMBER},
16     SyntaxNode, SyntaxToken, TextRange, T,
17 };
18
19 use crate::{references, NavigationTarget, TryToNav};
20
21 #[derive(PartialEq, Eq, Hash)]
22 pub struct HighlightedRange {
23     pub range: TextRange,
24     // FIXME: This needs to be more precise. Reference category makes sense only
25     // for references, but we also have defs. And things like exit points are
26     // neither.
27     pub category: Option<ReferenceCategory>,
28 }
29
30 #[derive(Default, Clone)]
31 pub struct HighlightRelatedConfig {
32     pub references: bool,
33     pub exit_points: bool,
34     pub break_points: bool,
35     pub yield_points: bool,
36 }
37
38 // Feature: Highlight Related
39 //
40 // Highlights constructs related to the thing under the cursor:
41 // - if on an identifier, highlights all references to that identifier in the current file
42 // - if on an `async` or `await token, highlights all yield points for that async context
43 // - if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context
44 // - if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context
45 //
46 // Note: `?` and `->` do not currently trigger this behavior in the VSCode editor.
47 pub(crate) fn highlight_related(
48     sema: &Semantics<RootDatabase>,
49     config: HighlightRelatedConfig,
50     FilePosition { offset, file_id }: FilePosition,
51 ) -> Option<Vec<HighlightedRange>> {
52     let _p = profile::span("highlight_related");
53     let syntax = sema.parse(file_id).syntax().clone();
54
55     let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
56         T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
57         T![->] => 3,
58         kind if kind.is_keyword() => 2,
59         IDENT | INT_NUMBER => 1,
60         _ => 0,
61     })?;
62     match token.kind() {
63         T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
64             highlight_exit_points(sema, token)
65         }
66         T![fn] | T![return] | T![->] if config.exit_points => highlight_exit_points(sema, token),
67         T![await] | T![async] if config.yield_points => highlight_yield_points(token),
68         T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => {
69             highlight_break_points(token)
70         }
71         T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
72             highlight_break_points(token)
73         }
74         _ if config.references => highlight_references(sema, &syntax, token, file_id),
75         _ => None,
76     }
77 }
78
79 fn highlight_references(
80     sema: &Semantics<RootDatabase>,
81     node: &SyntaxNode,
82     token: SyntaxToken,
83     file_id: FileId,
84 ) -> Option<Vec<HighlightedRange>> {
85     let defs = find_defs(sema, token);
86     let usages = defs
87         .iter()
88         .filter_map(|&d| {
89             d.usages(sema)
90                 .set_scope(Some(SearchScope::single_file(file_id)))
91                 .include_self_refs()
92                 .all()
93                 .references
94                 .remove(&file_id)
95         })
96         .flatten()
97         .map(|FileReference { category: access, range, .. }| HighlightedRange {
98             range,
99             category: access,
100         });
101
102     let declarations = defs.iter().flat_map(|def| {
103         match def {
104             &Definition::Module(module) => {
105                 Some(NavigationTarget::from_module_to_decl(sema.db, module))
106             }
107             def => def.try_to_nav(sema.db),
108         }
109         .filter(|decl| decl.file_id == file_id)
110         .and_then(|decl| {
111             let range = decl.focus_range?;
112             let category =
113                 references::decl_mutability(&def, node, range).then(|| ReferenceCategory::Write);
114             Some(HighlightedRange { range, category })
115         })
116     });
117
118     let res: FxHashSet<_> = declarations.chain(usages).collect();
119     if res.is_empty() {
120         None
121     } else {
122         Some(res.into_iter().collect())
123     }
124 }
125
126 fn highlight_exit_points(
127     sema: &Semantics<RootDatabase>,
128     token: SyntaxToken,
129 ) -> Option<Vec<HighlightedRange>> {
130     fn hl(
131         sema: &Semantics<RootDatabase>,
132         body: Option<ast::Expr>,
133     ) -> Option<Vec<HighlightedRange>> {
134         let mut highlights = Vec::new();
135         let body = body?;
136         walk_expr(&body, &mut |expr| match expr {
137             ast::Expr::ReturnExpr(expr) => {
138                 if let Some(token) = expr.return_token() {
139                     highlights.push(HighlightedRange { category: None, range: token.text_range() });
140                 }
141             }
142             ast::Expr::TryExpr(try_) => {
143                 if let Some(token) = try_.question_mark_token() {
144                     highlights.push(HighlightedRange { category: None, range: token.text_range() });
145                 }
146             }
147             ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => {
148                 if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) {
149                     highlights.push(HighlightedRange {
150                         category: None,
151                         range: expr.syntax().text_range(),
152                     });
153                 }
154             }
155             _ => (),
156         });
157         let tail = match body {
158             ast::Expr::BlockExpr(b) => b.tail_expr(),
159             e => Some(e),
160         };
161
162         if let Some(tail) = tail {
163             for_each_tail_expr(&tail, &mut |tail| {
164                 let range = match tail {
165                     ast::Expr::BreakExpr(b) => b
166                         .break_token()
167                         .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
168                     _ => tail.syntax().text_range(),
169                 };
170                 highlights.push(HighlightedRange { category: None, range })
171             });
172         }
173         Some(highlights)
174     }
175     for anc in token.ancestors() {
176         return match_ast! {
177             match anc {
178                 ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)),
179                 ast::ClosureExpr(closure) => hl(sema, closure.body()),
180                 ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
181                     hl(sema, Some(block_expr.into()))
182                 } else {
183                     continue;
184                 },
185                 _ => continue,
186             }
187         };
188     }
189     None
190 }
191
192 fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
193     fn hl(
194         cursor_token_kind: SyntaxKind,
195         token: Option<SyntaxToken>,
196         label: Option<ast::Label>,
197         body: Option<ast::StmtList>,
198     ) -> Option<Vec<HighlightedRange>> {
199         let mut highlights = Vec::new();
200         let range = cover_range(
201             token.map(|tok| tok.text_range()),
202             label.as_ref().map(|it| it.syntax().text_range()),
203         );
204         highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
205         for_each_break_and_continue_expr(label, body, &mut |expr| {
206             let range: Option<TextRange> = match (cursor_token_kind, expr) {
207                 (T![for] | T![while] | T![loop] | T![break], ast::Expr::BreakExpr(break_)) => {
208                     cover_range(
209                         break_.break_token().map(|it| it.text_range()),
210                         break_.lifetime().map(|it| it.syntax().text_range()),
211                     )
212                 }
213                 (
214                     T![for] | T![while] | T![loop] | T![continue],
215                     ast::Expr::ContinueExpr(continue_),
216                 ) => cover_range(
217                     continue_.continue_token().map(|it| it.text_range()),
218                     continue_.lifetime().map(|it| it.syntax().text_range()),
219                 ),
220                 _ => None,
221             };
222             highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
223         });
224         Some(highlights)
225     }
226     let parent = token.parent()?;
227     let lbl = match_ast! {
228         match parent {
229             ast::BreakExpr(b) => b.lifetime(),
230             ast::ContinueExpr(c) => c.lifetime(),
231             ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()),
232             ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()),
233             ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()),
234             ast::BlockExpr(b) => Some(b.label().and_then(|it| it.lifetime())?),
235             _ => return None,
236         }
237     };
238     let lbl = lbl.as_ref();
239     let label_matches = |def_lbl: Option<ast::Label>| match lbl {
240         Some(lbl) => {
241             Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text())
242         }
243         None => true,
244     };
245     let token_kind = token.kind();
246     for anc in token.ancestors().flat_map(ast::Expr::cast) {
247         return match anc {
248             ast::Expr::LoopExpr(l) if label_matches(l.label()) => hl(
249                 token_kind,
250                 l.loop_token(),
251                 l.label(),
252                 l.loop_body().and_then(|it| it.stmt_list()),
253             ),
254             ast::Expr::ForExpr(f) if label_matches(f.label()) => hl(
255                 token_kind,
256                 f.for_token(),
257                 f.label(),
258                 f.loop_body().and_then(|it| it.stmt_list()),
259             ),
260             ast::Expr::WhileExpr(w) if label_matches(w.label()) => hl(
261                 token_kind,
262                 w.while_token(),
263                 w.label(),
264                 w.loop_body().and_then(|it| it.stmt_list()),
265             ),
266             ast::Expr::BlockExpr(e) if e.label().is_some() && label_matches(e.label()) => {
267                 hl(token_kind, None, e.label(), e.stmt_list())
268             }
269             _ => continue,
270         };
271     }
272     None
273 }
274
275 fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
276     fn hl(
277         async_token: Option<SyntaxToken>,
278         body: Option<ast::Expr>,
279     ) -> Option<Vec<HighlightedRange>> {
280         let mut highlights =
281             vec![HighlightedRange { category: None, range: async_token?.text_range() }];
282         if let Some(body) = body {
283             walk_expr(&body, &mut |expr| {
284                 if let ast::Expr::AwaitExpr(expr) = expr {
285                     if let Some(token) = expr.await_token() {
286                         highlights
287                             .push(HighlightedRange { category: None, range: token.text_range() });
288                     }
289                 }
290             });
291         }
292         Some(highlights)
293     }
294     for anc in token.ancestors() {
295         return match_ast! {
296             match anc {
297                 ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
298                 ast::BlockExpr(block_expr) => {
299                     if block_expr.async_token().is_none() {
300                         continue;
301                     }
302                     hl(block_expr.async_token(), Some(block_expr.into()))
303                 },
304                 ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()),
305                 _ => continue,
306             }
307         };
308     }
309     None
310 }
311
312 fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
313     match (r0, r1) {
314         (Some(r0), Some(r1)) => Some(r0.cover(r1)),
315         (Some(range), None) => Some(range),
316         (None, Some(range)) => Some(range),
317         (None, None) => None,
318     }
319 }
320
321 fn find_defs(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> {
322     sema.descend_into_macros(token)
323         .into_iter()
324         .filter_map(|token| IdentClass::classify_token(sema, &token).map(IdentClass::definitions))
325         .flatten()
326         .collect()
327 }
328
329 #[cfg(test)]
330 mod tests {
331     use crate::fixture;
332
333     use super::*;
334
335     fn check(ra_fixture: &str) {
336         let config = HighlightRelatedConfig {
337             break_points: true,
338             exit_points: true,
339             references: true,
340             yield_points: true,
341         };
342
343         check_with_config(ra_fixture, config);
344     }
345
346     fn check_with_config(ra_fixture: &str, config: HighlightRelatedConfig) {
347         let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
348
349         let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();
350
351         let mut expected = annotations
352             .into_iter()
353             .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
354             .collect::<Vec<_>>();
355
356         let mut actual = hls
357             .into_iter()
358             .map(|hl| {
359                 (
360                     hl.range,
361                     hl.category.map(|it| {
362                         match it {
363                             ReferenceCategory::Read => "read",
364                             ReferenceCategory::Write => "write",
365                         }
366                         .to_string()
367                     }),
368                 )
369             })
370             .collect::<Vec<_>>();
371         actual.sort_by_key(|(range, _)| range.start());
372         expected.sort_by_key(|(range, _)| range.start());
373
374         assert_eq!(expected, actual);
375     }
376
377     #[test]
378     fn test_hl_tuple_fields() {
379         check(
380             r#"
381 struct Tuple(u32, u32);
382
383 fn foo(t: Tuple) {
384     t.0$0;
385    // ^ read
386     t.0;
387    // ^ read
388 }
389 "#,
390         );
391     }
392
393     #[test]
394     fn test_hl_module() {
395         check(
396             r#"
397 //- /lib.rs
398 mod foo$0;
399  // ^^^
400 //- /foo.rs
401 struct Foo;
402 "#,
403         );
404     }
405
406     #[test]
407     fn test_hl_self_in_crate_root() {
408         check(
409             r#"
410 use crate$0;
411   //^^^^^
412 use self;
413   //^^^^
414 mod __ {
415     use super;
416       //^^^^^
417 }
418 "#,
419         );
420         check(
421             r#"
422 //- /main.rs crate:main deps:lib
423 use lib$0;
424   //^^^
425 //- /lib.rs crate:lib
426 "#,
427         );
428     }
429
430     #[test]
431     fn test_hl_self_in_module() {
432         check(
433             r#"
434 //- /lib.rs
435 mod foo;
436 //- /foo.rs
437 use self$0;
438  // ^^^^
439 "#,
440         );
441     }
442
443     #[test]
444     fn test_hl_local() {
445         check(
446             r#"
447 fn foo() {
448     let mut bar = 3;
449          // ^^^ write
450     bar$0;
451  // ^^^ read
452 }
453 "#,
454         );
455     }
456
457     #[test]
458     fn test_hl_local_in_attr() {
459         check(
460             r#"
461 //- proc_macros: identity
462 #[proc_macros::identity]
463 fn foo() {
464     let mut bar = 3;
465          // ^^^ write
466     bar$0;
467  // ^^^ read
468 }
469 "#,
470         );
471     }
472
473     #[test]
474     fn test_multi_macro_usage() {
475         check(
476             r#"
477 macro_rules! foo {
478     ($ident:ident) => {
479         fn $ident() -> $ident { loop {} }
480         struct $ident;
481     }
482 }
483
484 foo!(bar$0);
485   // ^^^
486 fn foo() {
487     let bar: bar = bar();
488           // ^^^
489                 // ^^^
490 }
491 "#,
492         );
493         check(
494             r#"
495 macro_rules! foo {
496     ($ident:ident) => {
497         fn $ident() -> $ident { loop {} }
498         struct $ident;
499     }
500 }
501
502 foo!(bar);
503   // ^^^
504 fn foo() {
505     let bar: bar$0 = bar();
506           // ^^^
507 }
508 "#,
509         );
510     }
511
512     #[test]
513     fn test_hl_yield_points() {
514         check(
515             r#"
516 pub async fn foo() {
517  // ^^^^^
518     let x = foo()
519         .await$0
520       // ^^^^^
521         .await;
522       // ^^^^^
523     || { 0.await };
524     (async { 0.await }).await
525                      // ^^^^^
526 }
527 "#,
528         );
529     }
530
531     #[test]
532     fn test_hl_yield_points2() {
533         check(
534             r#"
535 pub async$0 fn foo() {
536  // ^^^^^
537     let x = foo()
538         .await
539       // ^^^^^
540         .await;
541       // ^^^^^
542     || { 0.await };
543     (async { 0.await }).await
544                      // ^^^^^
545 }
546 "#,
547         );
548     }
549
550     #[test]
551     fn test_hl_yield_nested_fn() {
552         check(
553             r#"
554 async fn foo() {
555     async fn foo2() {
556  // ^^^^^
557         async fn foo3() {
558             0.await
559         }
560         0.await$0
561        // ^^^^^
562     }
563     0.await
564 }
565 "#,
566         );
567     }
568
569     #[test]
570     fn test_hl_yield_nested_async_blocks() {
571         check(
572             r#"
573 async fn foo() {
574     (async {
575   // ^^^^^
576         (async {
577            0.await
578         }).await$0 }
579         // ^^^^^
580     ).await;
581 }
582 "#,
583         );
584     }
585
586     #[test]
587     fn test_hl_exit_points() {
588         check(
589             r#"
590 fn foo() -> u32 {
591     if true {
592         return$0 0;
593      // ^^^^^^
594     }
595
596     0?;
597   // ^
598     0xDEAD_BEEF
599  // ^^^^^^^^^^^
600 }
601 "#,
602         );
603     }
604
605     #[test]
606     fn test_hl_exit_points2() {
607         check(
608             r#"
609 fn foo() ->$0 u32 {
610     if true {
611         return 0;
612      // ^^^^^^
613     }
614
615     0?;
616   // ^
617     0xDEAD_BEEF
618  // ^^^^^^^^^^^
619 }
620 "#,
621         );
622     }
623
624     #[test]
625     fn test_hl_exit_points3() {
626         check(
627             r#"
628 fn$0 foo() -> u32 {
629     if true {
630         return 0;
631      // ^^^^^^
632     }
633
634     0?;
635   // ^
636     0xDEAD_BEEF
637  // ^^^^^^^^^^^
638 }
639 "#,
640         );
641     }
642
643     #[test]
644     fn test_hl_prefer_ref_over_tail_exit() {
645         check(
646             r#"
647 fn foo() -> u32 {
648 // ^^^
649     if true {
650         return 0;
651     }
652
653     0?;
654
655     foo$0()
656  // ^^^
657 }
658 "#,
659         );
660     }
661
662     #[test]
663     fn test_hl_never_call_is_exit_point() {
664         check(
665             r#"
666 struct Never;
667 impl Never {
668     fn never(self) -> ! { loop {} }
669 }
670 macro_rules! never {
671     () => { never() }
672 }
673 fn never() -> ! { loop {} }
674 fn foo() ->$0 u32 {
675     never();
676  // ^^^^^^^
677     never!();
678  // FIXME sema doesn't give us types for macrocalls
679
680     Never.never();
681  // ^^^^^^^^^^^^^
682
683     0
684  // ^
685 }
686 "#,
687         );
688     }
689
690     #[test]
691     fn test_hl_inner_tail_exit_points() {
692         check(
693             r#"
694 fn foo() ->$0 u32 {
695     if true {
696         unsafe {
697             return 5;
698          // ^^^^^^
699             5
700          // ^
701         }
702     } else if false {
703         0
704      // ^
705     } else {
706         match 5 {
707             6 => 100,
708               // ^^^
709             7 => loop {
710                 break 5;
711              // ^^^^^
712             }
713             8 => 'a: loop {
714                 'b: loop {
715                     break 'a 5;
716                  // ^^^^^
717                     break 'b 5;
718                     break 5;
719                 };
720             }
721             //
722             _ => 500,
723               // ^^^
724         }
725     }
726 }
727 "#,
728         );
729     }
730
731     #[test]
732     fn test_hl_inner_tail_exit_points_labeled_block() {
733         check(
734             r#"
735 fn foo() ->$0 u32 {
736     'foo: {
737         break 'foo 0;
738      // ^^^^^
739         loop {
740             break;
741             break 'foo 0;
742          // ^^^^^
743         }
744         0
745      // ^
746     }
747 }
748 "#,
749         );
750     }
751
752     #[test]
753     fn test_hl_break_loop() {
754         check(
755             r#"
756 fn foo() {
757     'outer: loop {
758  // ^^^^^^^^^^^^
759          break;
760       // ^^^^^
761          'inner: loop {
762             break;
763             'innermost: loop {
764                 break 'outer;
765              // ^^^^^^^^^^^^
766                 break 'inner;
767             }
768             break$0 'outer;
769          // ^^^^^^^^^^^^
770             break;
771         }
772         break;
773      // ^^^^^
774     }
775 }
776 "#,
777         );
778     }
779
780     #[test]
781     fn test_hl_break_loop2() {
782         check(
783             r#"
784 fn foo() {
785     'outer: loop {
786         break;
787         'inner: loop {
788      // ^^^^^^^^^^^^
789             break;
790          // ^^^^^
791             'innermost: loop {
792                 break 'outer;
793                 break 'inner;
794              // ^^^^^^^^^^^^
795             }
796             break 'outer;
797             break$0;
798          // ^^^^^
799         }
800         break;
801     }
802 }
803 "#,
804         );
805     }
806
807     #[test]
808     fn test_hl_break_for() {
809         check(
810             r#"
811 fn foo() {
812     'outer: for _ in () {
813  // ^^^^^^^^^^^
814          break;
815       // ^^^^^
816          'inner: for _ in () {
817             break;
818             'innermost: for _ in () {
819                 break 'outer;
820              // ^^^^^^^^^^^^
821                 break 'inner;
822             }
823             break$0 'outer;
824          // ^^^^^^^^^^^^
825             break;
826         }
827         break;
828      // ^^^^^
829     }
830 }
831 "#,
832         );
833     }
834
835     #[test]
836     fn test_hl_break_for_but_not_continue() {
837         check(
838             r#"
839 fn foo() {
840     'outer: for _ in () {
841  // ^^^^^^^^^^^
842         break;
843      // ^^^^^
844         continue;
845         'inner: for _ in () {
846             break;
847             continue;
848             'innermost: for _ in () {
849                 continue 'outer;
850                 break 'outer;
851              // ^^^^^^^^^^^^
852                 continue 'inner;
853                 break 'inner;
854             }
855             break$0 'outer;
856          // ^^^^^^^^^^^^
857             continue 'outer;
858             break;
859             continue;
860         }
861         break;
862      // ^^^^^
863         continue;
864     }
865 }
866 "#,
867         );
868     }
869
870     #[test]
871     fn test_hl_continue_for_but_not_break() {
872         check(
873             r#"
874 fn foo() {
875     'outer: for _ in () {
876  // ^^^^^^^^^^^
877         break;
878         continue;
879      // ^^^^^^^^
880         'inner: for _ in () {
881             break;
882             continue;
883             'innermost: for _ in () {
884                 continue 'outer;
885              // ^^^^^^^^^^^^^^^
886                 break 'outer;
887                 continue 'inner;
888                 break 'inner;
889             }
890             break 'outer;
891             continue$0 'outer;
892          // ^^^^^^^^^^^^^^^
893             break;
894             continue;
895         }
896         break;
897         continue;
898      // ^^^^^^^^
899     }
900 }
901 "#,
902         );
903     }
904
905     #[test]
906     fn test_hl_break_and_continue() {
907         check(
908             r#"
909 fn foo() {
910     'outer: fo$0r _ in () {
911  // ^^^^^^^^^^^
912         break;
913      // ^^^^^
914         continue;
915      // ^^^^^^^^
916         'inner: for _ in () {
917             break;
918             continue;
919             'innermost: for _ in () {
920                 continue 'outer;
921              // ^^^^^^^^^^^^^^^
922                 break 'outer;
923              // ^^^^^^^^^^^^
924                 continue 'inner;
925                 break 'inner;
926             }
927             break 'outer;
928          // ^^^^^^^^^^^^
929             continue 'outer;
930          // ^^^^^^^^^^^^^^^
931             break;
932             continue;
933         }
934         break;
935      // ^^^^^
936         continue;
937      // ^^^^^^^^
938     }
939 }
940 "#,
941         );
942     }
943
944     #[test]
945     fn test_hl_break_while() {
946         check(
947             r#"
948 fn foo() {
949     'outer: while true {
950  // ^^^^^^^^^^^^^
951          break;
952       // ^^^^^
953          'inner: while true {
954             break;
955             'innermost: while true {
956                 break 'outer;
957              // ^^^^^^^^^^^^
958                 break 'inner;
959             }
960             break$0 'outer;
961          // ^^^^^^^^^^^^
962             break;
963         }
964         break;
965      // ^^^^^
966     }
967 }
968 "#,
969         );
970     }
971
972     #[test]
973     fn test_hl_break_labeled_block() {
974         check(
975             r#"
976 fn foo() {
977     'outer: {
978  // ^^^^^^^
979          break;
980       // ^^^^^
981          'inner: {
982             break;
983             'innermost: {
984                 break 'outer;
985              // ^^^^^^^^^^^^
986                 break 'inner;
987             }
988             break$0 'outer;
989          // ^^^^^^^^^^^^
990             break;
991         }
992         break;
993      // ^^^^^
994     }
995 }
996 "#,
997         );
998     }
999
1000     #[test]
1001     fn test_hl_break_unlabeled_loop() {
1002         check(
1003             r#"
1004 fn foo() {
1005     loop {
1006  // ^^^^
1007         break$0;
1008      // ^^^^^
1009     }
1010 }
1011 "#,
1012         );
1013     }
1014
1015     #[test]
1016     fn test_hl_break_unlabeled_block_in_loop() {
1017         check(
1018             r#"
1019 fn foo() {
1020     loop {
1021  // ^^^^
1022         {
1023             break$0;
1024          // ^^^^^
1025         }
1026     }
1027 }
1028 "#,
1029         );
1030     }
1031
1032     #[test]
1033     fn test_hl_field_shorthand() {
1034         check(
1035             r#"
1036 struct Struct { field: u32 }
1037               //^^^^^
1038 fn function(field: u32) {
1039           //^^^^^
1040     Struct { field$0 }
1041            //^^^^^ read
1042 }
1043 "#,
1044         );
1045     }
1046
1047     #[test]
1048     fn test_hl_disabled_ref_local() {
1049         let config = HighlightRelatedConfig {
1050             references: false,
1051             break_points: true,
1052             exit_points: true,
1053             yield_points: true,
1054         };
1055
1056         let ra_fixture = r#"
1057 fn foo() {
1058     let x$0 = 5;
1059     let y = x * 2;
1060 }"#;
1061
1062         check_with_config(ra_fixture, config);
1063     }
1064
1065     #[test]
1066     fn test_hl_disabled_ref_local_preserved_break() {
1067         let config = HighlightRelatedConfig {
1068             references: false,
1069             break_points: true,
1070             exit_points: true,
1071             yield_points: true,
1072         };
1073
1074         let ra_fixture = r#"
1075 fn foo() {
1076     let x$0 = 5;
1077     let y = x * 2;
1078
1079     loop {
1080         break;
1081     }
1082 }"#;
1083
1084         check_with_config(ra_fixture, config.clone());
1085
1086         let ra_fixture = r#"
1087 fn foo() {
1088     let x = 5;
1089     let y = x * 2;
1090
1091     loop$0 {
1092 //  ^^^^
1093         break;
1094 //      ^^^^^
1095     }
1096 }"#;
1097
1098         check_with_config(ra_fixture, config);
1099     }
1100
1101     #[test]
1102     fn test_hl_disabled_ref_local_preserved_yield() {
1103         let config = HighlightRelatedConfig {
1104             references: false,
1105             break_points: true,
1106             exit_points: true,
1107             yield_points: true,
1108         };
1109
1110         let ra_fixture = r#"
1111 async fn foo() {
1112     let x$0 = 5;
1113     let y = x * 2;
1114
1115     0.await;
1116 }"#;
1117
1118         check_with_config(ra_fixture, config.clone());
1119
1120         let ra_fixture = r#"
1121     async fn foo() {
1122 //  ^^^^^
1123         let x = 5;
1124         let y = x * 2;
1125
1126         0.await$0;
1127 //        ^^^^^
1128 }"#;
1129
1130         check_with_config(ra_fixture, config);
1131     }
1132
1133     #[test]
1134     fn test_hl_disabled_ref_local_preserved_exit() {
1135         let config = HighlightRelatedConfig {
1136             references: false,
1137             break_points: true,
1138             exit_points: true,
1139             yield_points: true,
1140         };
1141
1142         let ra_fixture = r#"
1143 fn foo() -> i32 {
1144     let x$0 = 5;
1145     let y = x * 2;
1146
1147     if true {
1148         return y;
1149     }
1150
1151     0?
1152 }"#;
1153
1154         check_with_config(ra_fixture, config.clone());
1155
1156         let ra_fixture = r#"
1157 fn foo() ->$0 i32 {
1158     let x = 5;
1159     let y = x * 2;
1160
1161     if true {
1162         return y;
1163 //      ^^^^^^
1164     }
1165
1166     0?
1167 //   ^
1168 "#;
1169
1170         check_with_config(ra_fixture, config);
1171     }
1172
1173     #[test]
1174     fn test_hl_disabled_break() {
1175         let config = HighlightRelatedConfig {
1176             references: true,
1177             break_points: false,
1178             exit_points: true,
1179             yield_points: true,
1180         };
1181
1182         let ra_fixture = r#"
1183 fn foo() {
1184     loop {
1185         break$0;
1186     }
1187 }"#;
1188
1189         check_with_config(ra_fixture, config);
1190     }
1191
1192     #[test]
1193     fn test_hl_disabled_yield() {
1194         let config = HighlightRelatedConfig {
1195             references: true,
1196             break_points: true,
1197             exit_points: true,
1198             yield_points: false,
1199         };
1200
1201         let ra_fixture = r#"
1202 async$0 fn foo() {
1203     0.await;
1204 }"#;
1205
1206         check_with_config(ra_fixture, config);
1207     }
1208
1209     #[test]
1210     fn test_hl_disabled_exit() {
1211         let config = HighlightRelatedConfig {
1212             references: true,
1213             break_points: true,
1214             exit_points: false,
1215             yield_points: true,
1216         };
1217
1218         let ra_fixture = r#"
1219 fn foo() ->$0 i32 {
1220     if true {
1221         return -1;
1222     }
1223
1224     42
1225 }"#;
1226
1227         check_with_config(ra_fixture, config);
1228     }
1229 }