]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/highlight_related.rs
1daaeb43fa2fed9166a6732b452aa490fe06cb94
[rust.git] / crates / ide / src / highlight_related.rs
1 use hir::Semantics;
2 use ide_db::{
3     base_db::FilePosition,
4     defs::Definition,
5     helpers::pick_best_token,
6     search::{FileReference, ReferenceAccess, SearchScope},
7     RootDatabase,
8 };
9 use syntax::{
10     ast, match_ast, AstNode,
11     SyntaxKind::{ASYNC_KW, AWAIT_KW, QUESTION, RETURN_KW, THIN_ARROW},
12     SyntaxNode, SyntaxToken, TextRange, WalkEvent,
13 };
14
15 use crate::{display::TryToNav, references, NavigationTarget};
16
17 pub struct DocumentHighlight {
18     pub range: TextRange,
19     pub access: Option<ReferenceAccess>,
20 }
21
22 // Feature: Highlight related
23 //
24 // Highlights exit points, yield points or the definition and all references of the item at the cursor location in the current file.
25 pub(crate) fn highlight_related(
26     sema: &Semantics<RootDatabase>,
27     position: FilePosition,
28 ) -> Option<Vec<DocumentHighlight>> {
29     let _p = profile::span("document_highlight");
30     let syntax = sema.parse(position.file_id).syntax().clone();
31
32     let token = pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind {
33         QUESTION => 2, // prefer `?` when the cursor is sandwiched like `await$0?`
34         AWAIT_KW | ASYNC_KW | THIN_ARROW | RETURN_KW => 1,
35         _ => 0,
36     })?;
37
38     match token.kind() {
39         QUESTION | RETURN_KW | THIN_ARROW => highlight_exit_points(token),
40         AWAIT_KW | ASYNC_KW => highlight_yield_points(token),
41         _ => highlight_references(sema, &syntax, position),
42     }
43 }
44
45 fn highlight_exit_points(_token: SyntaxToken) -> Option<Vec<DocumentHighlight>> {
46     None
47 }
48
49 fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<DocumentHighlight>> {
50     fn hl(
51         async_token: Option<SyntaxToken>,
52         body: Option<ast::BlockExpr>,
53     ) -> Option<Vec<DocumentHighlight>> {
54         let mut highlights = Vec::new();
55         highlights.push(DocumentHighlight { access: None, range: async_token?.text_range() });
56         if let Some(body) = body {
57             let mut preorder = body.syntax().preorder();
58             while let Some(event) = preorder.next() {
59                 let node = match event {
60                     WalkEvent::Enter(node) => node,
61                     WalkEvent::Leave(_) => continue,
62                 };
63                 match_ast! {
64                     match node {
65                         ast::AwaitExpr(expr) => if let Some(token) = expr.await_token() {
66                             highlights.push(DocumentHighlight {
67                                 access: None,
68                                 range: token.text_range(),
69                             });
70                         },
71                         ast::EffectExpr(__) => preorder.skip_subtree(),
72                         ast::ClosureExpr(__) => preorder.skip_subtree(),
73                         ast::Item(__) => preorder.skip_subtree(),
74                         ast::Path(__) => preorder.skip_subtree(),
75                         _ => (),
76                     }
77                 }
78             }
79         }
80         Some(highlights)
81     }
82     for anc in token.ancestors() {
83         return match_ast! {
84             match anc {
85                 ast::Fn(fn_) => hl(fn_.async_token(), fn_.body()),
86                 ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr()),
87                 ast::ClosureExpr(__) => None,
88                 _ => continue,
89             }
90         };
91     }
92     None
93 }
94
95 fn highlight_references(
96     sema: &Semantics<RootDatabase>,
97     syntax: &SyntaxNode,
98     FilePosition { offset, file_id }: FilePosition,
99 ) -> Option<Vec<DocumentHighlight>> {
100     let def = references::find_def(sema, syntax, offset)?;
101     let usages = def.usages(sema).set_scope(Some(SearchScope::single_file(file_id))).all();
102
103     let declaration = match def {
104         Definition::ModuleDef(hir::ModuleDef::Module(module)) => {
105             Some(NavigationTarget::from_module_to_decl(sema.db, module))
106         }
107         def => def.try_to_nav(sema.db),
108     }
109     .filter(|decl| decl.file_id == file_id)
110     .and_then(|decl| {
111         let range = decl.focus_range?;
112         let access = references::decl_access(&def, syntax, range);
113         Some(DocumentHighlight { range, access })
114     });
115
116     let file_refs = usages.references.get(&file_id).map_or(&[][..], Vec::as_slice);
117     let mut res = Vec::with_capacity(file_refs.len() + 1);
118     res.extend(declaration);
119     res.extend(
120         file_refs
121             .iter()
122             .map(|&FileReference { access, range, .. }| DocumentHighlight { range, access }),
123     );
124     Some(res)
125 }
126
127 #[cfg(test)]
128 mod tests {
129     use crate::fixture;
130
131     use super::*;
132
133     fn check(ra_fixture: &str) {
134         let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
135         let hls = analysis.highlight_related(pos).unwrap().unwrap();
136
137         let mut expected = annotations
138             .into_iter()
139             .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
140             .collect::<Vec<_>>();
141
142         let mut actual = hls
143             .into_iter()
144             .map(|hl| {
145                 (
146                     hl.range,
147                     hl.access.map(|it| {
148                         match it {
149                             ReferenceAccess::Read => "read",
150                             ReferenceAccess::Write => "write",
151                         }
152                         .to_string()
153                     }),
154                 )
155             })
156             .collect::<Vec<_>>();
157         actual.sort_by_key(|(range, _)| range.start());
158         expected.sort_by_key(|(range, _)| range.start());
159
160         assert_eq!(expected, actual);
161     }
162
163     #[test]
164     fn test_hl_module() {
165         check(
166             r#"
167 //- /lib.rs
168 mod foo$0;
169  // ^^^
170 //- /foo.rs
171 struct Foo;
172 "#,
173         );
174     }
175
176     #[test]
177     fn test_hl_self_in_crate_root() {
178         check(
179             r#"
180 use self$0;
181 "#,
182         );
183     }
184
185     #[test]
186     fn test_hl_self_in_module() {
187         check(
188             r#"
189 //- /lib.rs
190 mod foo;
191 //- /foo.rs
192 use self$0;
193 "#,
194         );
195     }
196
197     #[test]
198     fn test_hl_local() {
199         check(
200             r#"
201 fn foo() {
202     let mut bar = 3;
203          // ^^^ write
204     bar$0;
205  // ^^^ read
206 }
207 "#,
208         );
209     }
210
211     #[test]
212     fn test_hl_yield_points() {
213         check(
214             r#"
215 pub async fn foo() {
216  // ^^^^^
217     let x = foo()
218         .await$0
219       // ^^^^^
220         .await;
221       // ^^^^^
222     || { 0.await };
223     (async { 0.await }).await
224                      // ^^^^^
225 }
226 "#,
227         );
228     }
229
230     #[test]
231     fn test_hl_yield_points2() {
232         check(
233             r#"
234 pub async$0 fn foo() {
235  // ^^^^^
236     let x = foo()
237         .await
238       // ^^^^^
239         .await;
240       // ^^^^^
241     || { 0.await };
242     (async { 0.await }).await
243                      // ^^^^^
244 }
245 "#,
246         );
247     }
248
249     #[test]
250     fn test_hl_yield_nested_fn() {
251         check(
252             r#"
253 async fn foo() {
254     async fn foo2() {
255  // ^^^^^
256         async fn foo3() {
257             0.await
258         }
259         0.await$0
260        // ^^^^^
261     }
262     0.await
263 }
264 "#,
265         );
266     }
267
268     #[test]
269     fn test_hl_yield_nested_async_blocks() {
270         check(
271             r#"
272 async fn foo() {
273     (async {
274   // ^^^^^
275         (async {
276            0.await
277         }).await$0 }
278         // ^^^^^
279     ).await;
280 }
281 "#,
282         );
283     }
284 }