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