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