]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/highlight_related.rs
Return all usages inside macros in usage searches
[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   // ^^^
428 fn foo() {
429     let bar: bar = bar();
430           // ^^^
431                 // ^^^
432 }
433 "#,
434         );
435         check(
436             r#"
437 macro_rules! foo {
438     ($ident:ident) => {
439         fn $ident() -> $ident { loop {} }
440         struct $ident;
441     }
442 }
443
444 foo!(bar);
445   // ^^^
446   // ^^^
447 fn foo() {
448     let bar: bar$0 = bar();
449           // ^^^
450 }
451 "#,
452         );
453     }
454
455     #[test]
456     fn test_hl_yield_points() {
457         check(
458             r#"
459 pub async fn foo() {
460  // ^^^^^
461     let x = foo()
462         .await$0
463       // ^^^^^
464         .await;
465       // ^^^^^
466     || { 0.await };
467     (async { 0.await }).await
468                      // ^^^^^
469 }
470 "#,
471         );
472     }
473
474     #[test]
475     fn test_hl_yield_points2() {
476         check(
477             r#"
478 pub async$0 fn foo() {
479  // ^^^^^
480     let x = foo()
481         .await
482       // ^^^^^
483         .await;
484       // ^^^^^
485     || { 0.await };
486     (async { 0.await }).await
487                      // ^^^^^
488 }
489 "#,
490         );
491     }
492
493     #[test]
494     fn test_hl_yield_nested_fn() {
495         check(
496             r#"
497 async fn foo() {
498     async fn foo2() {
499  // ^^^^^
500         async fn foo3() {
501             0.await
502         }
503         0.await$0
504        // ^^^^^
505     }
506     0.await
507 }
508 "#,
509         );
510     }
511
512     #[test]
513     fn test_hl_yield_nested_async_blocks() {
514         check(
515             r#"
516 async fn foo() {
517     (async {
518   // ^^^^^
519         (async {
520            0.await
521         }).await$0 }
522         // ^^^^^
523     ).await;
524 }
525 "#,
526         );
527     }
528
529     #[test]
530     fn test_hl_exit_points() {
531         check(
532             r#"
533 fn foo() -> u32 {
534     if true {
535         return$0 0;
536      // ^^^^^^
537     }
538
539     0?;
540   // ^
541     0xDEAD_BEEF
542  // ^^^^^^^^^^^
543 }
544 "#,
545         );
546     }
547
548     #[test]
549     fn test_hl_exit_points2() {
550         check(
551             r#"
552 fn foo() ->$0 u32 {
553     if true {
554         return 0;
555      // ^^^^^^
556     }
557
558     0?;
559   // ^
560     0xDEAD_BEEF
561  // ^^^^^^^^^^^
562 }
563 "#,
564         );
565     }
566
567     #[test]
568     fn test_hl_exit_points3() {
569         check(
570             r#"
571 fn$0 foo() -> u32 {
572     if true {
573         return 0;
574      // ^^^^^^
575     }
576
577     0?;
578   // ^
579     0xDEAD_BEEF
580  // ^^^^^^^^^^^
581 }
582 "#,
583         );
584     }
585
586     #[test]
587     fn test_hl_prefer_ref_over_tail_exit() {
588         check(
589             r#"
590 fn foo() -> u32 {
591 // ^^^
592     if true {
593         return 0;
594     }
595
596     0?;
597
598     foo$0()
599  // ^^^
600 }
601 "#,
602         );
603     }
604
605     #[test]
606     fn test_hl_never_call_is_exit_point() {
607         check(
608             r#"
609 struct Never;
610 impl Never {
611     fn never(self) -> ! { loop {} }
612 }
613 macro_rules! never {
614     () => { never() }
615 }
616 fn never() -> ! { loop {} }
617 fn foo() ->$0 u32 {
618     never();
619  // ^^^^^^^
620     never!();
621  // FIXME sema doesn't give us types for macrocalls
622
623     Never.never();
624  // ^^^^^^^^^^^^^
625
626     0
627  // ^
628 }
629 "#,
630         );
631     }
632
633     #[test]
634     fn test_hl_inner_tail_exit_points() {
635         check(
636             r#"
637 fn foo() ->$0 u32 {
638     if true {
639         unsafe {
640             return 5;
641          // ^^^^^^
642             5
643          // ^
644         }
645     } else if false {
646         0
647      // ^
648     } else {
649         match 5 {
650             6 => 100,
651               // ^^^
652             7 => loop {
653                 break 5;
654              // ^^^^^
655             }
656             8 => 'a: loop {
657                 'b: loop {
658                     break 'a 5;
659                  // ^^^^^
660                     break 'b 5;
661                     break 5;
662                 };
663             }
664             //
665             _ => 500,
666               // ^^^
667         }
668     }
669 }
670 "#,
671         );
672     }
673
674     #[test]
675     fn test_hl_inner_tail_exit_points_labeled_block() {
676         check(
677             r#"
678 fn foo() ->$0 u32 {
679     'foo: {
680         break 'foo 0;
681      // ^^^^^
682         loop {
683             break;
684             break 'foo 0;
685          // ^^^^^
686         }
687         0
688      // ^
689     }
690 }
691 "#,
692         );
693     }
694
695     #[test]
696     fn test_hl_break_loop() {
697         check(
698             r#"
699 fn foo() {
700     'outer: loop {
701  // ^^^^^^^^^^^^
702          break;
703       // ^^^^^
704          'inner: loop {
705             break;
706             'innermost: loop {
707                 break 'outer;
708              // ^^^^^^^^^^^^
709                 break 'inner;
710             }
711             break$0 'outer;
712          // ^^^^^^^^^^^^
713             break;
714         }
715         break;
716      // ^^^^^
717     }
718 }
719 "#,
720         );
721     }
722
723     #[test]
724     fn test_hl_break_loop2() {
725         check(
726             r#"
727 fn foo() {
728     'outer: loop {
729         break;
730         'inner: loop {
731      // ^^^^^^^^^^^^
732             break;
733          // ^^^^^
734             'innermost: loop {
735                 break 'outer;
736                 break 'inner;
737              // ^^^^^^^^^^^^
738             }
739             break 'outer;
740             break$0;
741          // ^^^^^
742         }
743         break;
744     }
745 }
746 "#,
747         );
748     }
749
750     #[test]
751     fn test_hl_break_for() {
752         check(
753             r#"
754 fn foo() {
755     'outer: for _ in () {
756  // ^^^^^^^^^^^
757          break;
758       // ^^^^^
759          'inner: for _ in () {
760             break;
761             'innermost: for _ in () {
762                 break 'outer;
763              // ^^^^^^^^^^^^
764                 break 'inner;
765             }
766             break$0 'outer;
767          // ^^^^^^^^^^^^
768             break;
769         }
770         break;
771      // ^^^^^
772     }
773 }
774 "#,
775         );
776     }
777
778     #[test]
779     fn test_hl_break_while() {
780         check(
781             r#"
782 fn foo() {
783     'outer: while true {
784  // ^^^^^^^^^^^^^
785          break;
786       // ^^^^^
787          'inner: while true {
788             break;
789             'innermost: while true {
790                 break 'outer;
791              // ^^^^^^^^^^^^
792                 break 'inner;
793             }
794             break$0 'outer;
795          // ^^^^^^^^^^^^
796             break;
797         }
798         break;
799      // ^^^^^
800     }
801 }
802 "#,
803         );
804     }
805
806     #[test]
807     fn test_hl_break_labeled_block() {
808         check(
809             r#"
810 fn foo() {
811     'outer: {
812  // ^^^^^^^
813          break;
814       // ^^^^^
815          'inner: {
816             break;
817             'innermost: {
818                 break 'outer;
819              // ^^^^^^^^^^^^
820                 break 'inner;
821             }
822             break$0 'outer;
823          // ^^^^^^^^^^^^
824             break;
825         }
826         break;
827      // ^^^^^
828     }
829 }
830 "#,
831         );
832     }
833
834     #[test]
835     fn test_hl_break_unlabeled_loop() {
836         check(
837             r#"
838 fn foo() {
839     loop {
840  // ^^^^
841         break$0;
842      // ^^^^^
843     }
844 }
845 "#,
846         );
847     }
848
849     #[test]
850     fn test_hl_break_unlabeled_block_in_loop() {
851         check(
852             r#"
853 fn foo() {
854     loop {
855  // ^^^^
856         {
857             break$0;
858          // ^^^^^
859         }
860     }
861 }
862 "#,
863         );
864     }
865
866     #[test]
867     fn test_hl_field_shorthand() {
868         check(
869             r#"
870 struct Struct { field: u32 }
871               //^^^^^
872 fn function(field: u32) {
873           //^^^^^
874     Struct { field$0 }
875            //^^^^^ read
876            //^^^^^ read
877 }
878 "#,
879         );
880     }
881
882     #[test]
883     fn test_hl_disabled_ref_local() {
884         let config = HighlightRelatedConfig {
885             references: false,
886             break_points: true,
887             exit_points: true,
888             yield_points: true,
889         };
890
891         let ra_fixture = r#"
892 fn foo() {
893     let x$0 = 5;
894     let y = x * 2;
895 }"#;
896
897         check_with_config(ra_fixture, config);
898     }
899
900     #[test]
901     fn test_hl_disabled_ref_local_preserved_break() {
902         let config = HighlightRelatedConfig {
903             references: false,
904             break_points: true,
905             exit_points: true,
906             yield_points: true,
907         };
908
909         let ra_fixture = r#"
910 fn foo() {
911     let x$0 = 5;
912     let y = x * 2;
913
914     loop {
915         break;
916     }
917 }"#;
918
919         check_with_config(ra_fixture, config.clone());
920
921         let ra_fixture = r#"
922 fn foo() {
923     let x = 5;
924     let y = x * 2;
925
926     loop$0 {
927 //  ^^^^
928         break;
929 //      ^^^^^
930     }
931 }"#;
932
933         check_with_config(ra_fixture, config);
934     }
935
936     #[test]
937     fn test_hl_disabled_ref_local_preserved_yield() {
938         let config = HighlightRelatedConfig {
939             references: false,
940             break_points: true,
941             exit_points: true,
942             yield_points: true,
943         };
944
945         let ra_fixture = r#"
946 async fn foo() {
947     let x$0 = 5;
948     let y = x * 2;
949
950     0.await;
951 }"#;
952
953         check_with_config(ra_fixture, config.clone());
954
955         let ra_fixture = r#"
956     async fn foo() {
957 //  ^^^^^
958         let x = 5;
959         let y = x * 2;
960
961         0.await$0;
962 //        ^^^^^
963 }"#;
964
965         check_with_config(ra_fixture, config);
966     }
967
968     #[test]
969     fn test_hl_disabled_ref_local_preserved_exit() {
970         let config = HighlightRelatedConfig {
971             references: false,
972             break_points: true,
973             exit_points: true,
974             yield_points: true,
975         };
976
977         let ra_fixture = r#"
978 fn foo() -> i32 {
979     let x$0 = 5;
980     let y = x * 2;
981
982     if true {
983         return y;
984     }
985
986     0?
987 }"#;
988
989         check_with_config(ra_fixture, config.clone());
990
991         let ra_fixture = r#"
992 fn foo() ->$0 i32 {
993     let x = 5;
994     let y = x * 2;
995
996     if true {
997         return y;
998 //      ^^^^^^
999     }
1000
1001     0?
1002 //   ^
1003 "#;
1004
1005         check_with_config(ra_fixture, config);
1006     }
1007
1008     #[test]
1009     fn test_hl_disabled_break() {
1010         let config = HighlightRelatedConfig {
1011             references: true,
1012             break_points: false,
1013             exit_points: true,
1014             yield_points: true,
1015         };
1016
1017         let ra_fixture = r#"
1018 fn foo() {
1019     loop {
1020         break$0;
1021     }
1022 }"#;
1023
1024         check_with_config(ra_fixture, config);
1025     }
1026
1027     #[test]
1028     fn test_hl_disabled_yield() {
1029         let config = HighlightRelatedConfig {
1030             references: true,
1031             break_points: true,
1032             exit_points: true,
1033             yield_points: false,
1034         };
1035
1036         let ra_fixture = r#"
1037 async$0 fn foo() {
1038     0.await;
1039 }"#;
1040
1041         check_with_config(ra_fixture, config);
1042     }
1043
1044     #[test]
1045     fn test_hl_disabled_exit() {
1046         let config = HighlightRelatedConfig {
1047             references: true,
1048             break_points: true,
1049             exit_points: false,
1050             yield_points: true,
1051         };
1052
1053         let ra_fixture = r#"
1054 fn foo() ->$0 i32 {
1055     if true {
1056         return -1;
1057     }
1058
1059     42
1060 }"#;
1061
1062         check_with_config(ra_fixture, config);
1063     }
1064 }