5 helpers::pick_best_token,
6 search::{FileReference, ReferenceAccess, SearchScope},
10 ast, match_ast, AstNode,
11 SyntaxKind::{ASYNC_KW, AWAIT_KW, QUESTION, RETURN_KW, THIN_ARROW},
12 SyntaxNode, SyntaxToken, TextRange, WalkEvent,
15 use crate::{display::TryToNav, references, NavigationTarget};
17 pub struct DocumentHighlight {
19 pub access: Option<ReferenceAccess>,
22 // Feature: Highlight Related
24 // Highlights constructs related to the thing under the cursor:
25 // - if on an identifier, highlights all references to that identifier in the current file
26 // - if on an `async` or `await token, highlights all yield points for that async context
27 // - if on a `return` token, `?` character or `->` return type arrow, highlights all exit points for that context
28 pub(crate) fn highlight_related(
29 sema: &Semantics<RootDatabase>,
30 position: FilePosition,
31 ) -> Option<Vec<DocumentHighlight>> {
32 let _p = profile::span("document_highlight");
33 let syntax = sema.parse(position.file_id).syntax().clone();
35 let token = pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind {
36 QUESTION => 2, // prefer `?` when the cursor is sandwiched like `await$0?`
37 AWAIT_KW | ASYNC_KW | THIN_ARROW | RETURN_KW => 1,
42 QUESTION | RETURN_KW | THIN_ARROW => highlight_exit_points(sema, token),
43 AWAIT_KW | ASYNC_KW => highlight_yield_points(token),
44 _ => highlight_references(sema, &syntax, position),
48 fn highlight_references(
49 sema: &Semantics<RootDatabase>,
51 FilePosition { offset, file_id }: FilePosition,
52 ) -> Option<Vec<DocumentHighlight>> {
53 let def = references::find_def(sema, syntax, offset)?;
54 let usages = def.usages(sema).set_scope(Some(SearchScope::single_file(file_id))).all();
56 let declaration = match def {
57 Definition::ModuleDef(hir::ModuleDef::Module(module)) => {
58 Some(NavigationTarget::from_module_to_decl(sema.db, module))
60 def => def.try_to_nav(sema.db),
62 .filter(|decl| decl.file_id == file_id)
64 let range = decl.focus_range?;
65 let access = references::decl_access(&def, syntax, range);
66 Some(DocumentHighlight { range, access })
69 let file_refs = usages.references.get(&file_id).map_or(&[][..], Vec::as_slice);
70 let mut res = Vec::with_capacity(file_refs.len() + 1);
71 res.extend(declaration);
75 .map(|&FileReference { access, range, .. }| DocumentHighlight { range, access }),
80 fn highlight_exit_points(
81 sema: &Semantics<RootDatabase>,
83 ) -> Option<Vec<DocumentHighlight>> {
85 sema: &Semantics<RootDatabase>,
86 body: Option<ast::Expr>,
87 ) -> Option<Vec<DocumentHighlight>> {
88 let mut highlights = Vec::new();
93 ast::ReturnExpr(expr) => if let Some(token) = expr.return_token() {
94 highlights.push(DocumentHighlight {
96 range: token.text_range(),
99 ast::TryExpr(try_) => if let Some(token) = try_.question_mark_token() {
100 highlights.push(DocumentHighlight {
102 range: token.text_range(),
105 ast::Expr(expr) => match expr {
106 ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => {
107 if sema.type_of_expr(&expr).map_or(false, |ty| ty.is_never()) {
108 highlights.push(DocumentHighlight {
110 range: expr.syntax().text_range(),
114 ast::Expr::EffectExpr(effect) => return effect.async_token().is_some() || effect.try_token().is_some(),
115 ast::Expr::ClosureExpr(_) => return true,
118 ast::Item(__) => return true,
119 // Don't look into const args
120 ast::Path(__) => return true,
126 let tail = match body {
127 ast::Expr::BlockExpr(b) => b.tail_expr(),
130 if let Some(tail) = tail {
131 highlights.push(DocumentHighlight { access: None, range: tail.syntax().text_range() });
135 for anc in token.ancestors() {
138 ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)),
139 ast::ClosureExpr(closure) => hl(sema, closure.body()),
140 ast::EffectExpr(effect) => if effect.async_token().is_some() || effect.try_token().is_some() {
141 hl(sema, effect.block_expr().map(ast::Expr::BlockExpr))
152 fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<DocumentHighlight>> {
154 async_token: Option<SyntaxToken>,
155 body: Option<ast::Expr>,
156 ) -> Option<Vec<DocumentHighlight>> {
157 let mut highlights = Vec::new();
158 highlights.push(DocumentHighlight { access: None, range: async_token?.text_range() });
159 if let Some(body) = body {
163 ast::AwaitExpr(expr) => if let Some(token) = expr.await_token() {
164 highlights.push(DocumentHighlight {
166 range: token.text_range(),
169 // All the following are different contexts so skip them
170 ast::EffectExpr(effect) => return effect.async_token().is_some() || effect.try_token().is_some(),
171 ast::ClosureExpr(__) => return true,
172 ast::Item(__) => return true,
173 // Don't look into const args
174 ast::Path(__) => return true,
183 for anc in token.ancestors() {
186 ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
187 ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr().map(ast::Expr::BlockExpr)),
188 ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()),
196 /// Preorder walk the expression node skipping a node's subtrees if the callback returns `true` for the node.
197 fn walk(expr: &ast::Expr, mut cb: impl FnMut(SyntaxNode) -> bool) {
198 let mut preorder = expr.syntax().preorder();
199 while let Some(event) = preorder.next() {
200 let node = match event {
201 WalkEvent::Enter(node) => node,
202 WalkEvent::Leave(_) => continue,
205 preorder.skip_subtree();
216 fn check(ra_fixture: &str) {
217 let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
218 let hls = analysis.highlight_related(pos).unwrap().unwrap();
220 let mut expected = annotations
222 .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
223 .collect::<Vec<_>>();
232 ReferenceAccess::Read => "read",
233 ReferenceAccess::Write => "write",
239 .collect::<Vec<_>>();
240 actual.sort_by_key(|(range, _)| range.start());
241 expected.sort_by_key(|(range, _)| range.start());
243 assert_eq!(expected, actual);
247 fn test_hl_module() {
260 fn test_hl_self_in_crate_root() {
269 fn test_hl_self_in_module() {
295 fn test_hl_yield_points() {
306 (async { 0.await }).await
314 fn test_hl_yield_points2() {
317 pub async$0 fn foo() {
325 (async { 0.await }).await
333 fn test_hl_yield_nested_fn() {
352 fn test_hl_yield_nested_async_blocks() {
369 fn test_hl_exit_points() {
388 fn test_hl_exit_points2() {
407 fn test_hl_prefer_ref_over_tail_exit() {
426 fn test_hl_never_call_is_exit_point() {
431 fn never(self) -> ! { loop {} }
436 fn never() -> ! { loop {} }
441 // FIXME sema doesnt give us types for macrocalls