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