diff options
Diffstat (limited to 'crates/ide/src/syntax_highlighting.rs')
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 821 |
1 files changed, 63 insertions, 758 deletions
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index ba0085244..f2d4da78d 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -1,35 +1,39 @@ | |||
1 | pub(crate) mod tags; | ||
2 | |||
3 | mod highlights; | ||
4 | mod injector; | ||
5 | |||
6 | mod highlight; | ||
1 | mod format; | 7 | mod format; |
2 | mod html; | ||
3 | mod injection; | ||
4 | mod macro_rules; | 8 | mod macro_rules; |
5 | pub(crate) mod tags; | 9 | mod inject; |
10 | |||
11 | mod html; | ||
6 | #[cfg(test)] | 12 | #[cfg(test)] |
7 | mod tests; | 13 | mod tests; |
8 | 14 | ||
9 | use hir::{AsAssocItem, Local, Name, Semantics, VariantDef}; | 15 | use hir::{Name, Semantics}; |
10 | use ide_db::{ | 16 | use ide_db::RootDatabase; |
11 | defs::{Definition, NameClass, NameRefClass}, | ||
12 | RootDatabase, | ||
13 | }; | ||
14 | use rustc_hash::FxHashMap; | 17 | use rustc_hash::FxHashMap; |
15 | use syntax::{ | 18 | use syntax::{ |
16 | ast::{self, HasFormatSpecifier}, | 19 | ast::{self, HasFormatSpecifier}, |
17 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, | 20 | AstNode, AstToken, Direction, NodeOrToken, |
18 | SyntaxKind::{self, *}, | 21 | SyntaxKind::*, |
19 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, | 22 | SyntaxNode, TextRange, WalkEvent, T, |
20 | }; | 23 | }; |
21 | 24 | ||
22 | use crate::{ | 25 | use crate::{ |
23 | syntax_highlighting::{ | 26 | syntax_highlighting::{ |
24 | format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter, tags::Highlight, | 27 | format::highlight_format_string, highlights::Highlights, |
28 | macro_rules::MacroRulesHighlighter, tags::Highlight, | ||
25 | }, | 29 | }, |
26 | FileId, HighlightModifier, HighlightTag, SymbolKind, | 30 | FileId, HlMod, HlTag, SymbolKind, |
27 | }; | 31 | }; |
28 | 32 | ||
29 | pub(crate) use html::highlight_as_html; | 33 | pub(crate) use html::highlight_as_html; |
30 | 34 | ||
31 | #[derive(Debug, Clone)] | 35 | #[derive(Debug, Clone, Copy)] |
32 | pub struct HighlightedRange { | 36 | pub struct HlRange { |
33 | pub range: TextRange, | 37 | pub range: TextRange, |
34 | pub highlight: Highlight, | 38 | pub highlight: Highlight, |
35 | pub binding_hash: Option<u64>, | 39 | pub binding_hash: Option<u64>, |
@@ -49,7 +53,7 @@ pub(crate) fn highlight( | |||
49 | file_id: FileId, | 53 | file_id: FileId, |
50 | range_to_highlight: Option<TextRange>, | 54 | range_to_highlight: Option<TextRange>, |
51 | syntactic_name_ref_highlighting: bool, | 55 | syntactic_name_ref_highlighting: bool, |
52 | ) -> Vec<HighlightedRange> { | 56 | ) -> Vec<HlRange> { |
53 | let _p = profile::span("highlight"); | 57 | let _p = profile::span("highlight"); |
54 | let sema = Semantics::new(db); | 58 | let sema = Semantics::new(db); |
55 | 59 | ||
@@ -68,28 +72,30 @@ pub(crate) fn highlight( | |||
68 | } | 72 | } |
69 | }; | 73 | }; |
70 | 74 | ||
75 | let mut hl = highlights::Highlights::new(range_to_highlight); | ||
76 | traverse(&mut hl, &sema, &root, range_to_highlight, syntactic_name_ref_highlighting); | ||
77 | hl.to_vec() | ||
78 | } | ||
79 | |||
80 | fn traverse( | ||
81 | hl: &mut Highlights, | ||
82 | sema: &Semantics<RootDatabase>, | ||
83 | root: &SyntaxNode, | ||
84 | range_to_highlight: TextRange, | ||
85 | syntactic_name_ref_highlighting: bool, | ||
86 | ) { | ||
71 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); | 87 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); |
72 | // We use a stack for the DFS traversal below. | ||
73 | // When we leave a node, the we use it to flatten the highlighted ranges. | ||
74 | let mut stack = HighlightedRangeStack::new(); | ||
75 | 88 | ||
76 | let mut current_macro_call: Option<ast::MacroCall> = None; | 89 | let mut current_macro_call: Option<ast::MacroCall> = None; |
77 | let mut current_macro_rules: Option<ast::MacroRules> = None; | 90 | let mut current_macro_rules: Option<ast::MacroRules> = None; |
78 | let mut format_string_highlighter = FormatStringHighlighter::default(); | ||
79 | let mut macro_rules_highlighter = MacroRulesHighlighter::default(); | 91 | let mut macro_rules_highlighter = MacroRulesHighlighter::default(); |
80 | let mut inside_attribute = false; | 92 | let mut inside_attribute = false; |
81 | 93 | ||
82 | // Walk all nodes, keeping track of whether we are inside a macro or not. | 94 | // Walk all nodes, keeping track of whether we are inside a macro or not. |
83 | // If in macro, expand it first and highlight the expanded code. | 95 | // If in macro, expand it first and highlight the expanded code. |
84 | for event in root.preorder_with_tokens() { | 96 | for event in root.preorder_with_tokens() { |
85 | match &event { | ||
86 | WalkEvent::Enter(_) => stack.push(), | ||
87 | WalkEvent::Leave(_) => stack.pop(), | ||
88 | }; | ||
89 | |||
90 | let event_range = match &event { | 97 | let event_range = match &event { |
91 | WalkEvent::Enter(it) => it.text_range(), | 98 | WalkEvent::Enter(it) | WalkEvent::Leave(it) => it.text_range(), |
92 | WalkEvent::Leave(it) => it.text_range(), | ||
93 | }; | 99 | }; |
94 | 100 | ||
95 | // Element outside of the viewport, no need to highlight | 101 | // Element outside of the viewport, no need to highlight |
@@ -101,9 +107,9 @@ pub(crate) fn highlight( | |||
101 | match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) { | 107 | match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) { |
102 | WalkEvent::Enter(Some(mc)) => { | 108 | WalkEvent::Enter(Some(mc)) => { |
103 | if let Some(range) = macro_call_range(&mc) { | 109 | if let Some(range) = macro_call_range(&mc) { |
104 | stack.add(HighlightedRange { | 110 | hl.add(HlRange { |
105 | range, | 111 | range, |
106 | highlight: HighlightTag::Symbol(SymbolKind::Macro).into(), | 112 | highlight: HlTag::Symbol(SymbolKind::Macro).into(), |
107 | binding_hash: None, | 113 | binding_hash: None, |
108 | }); | 114 | }); |
109 | } | 115 | } |
@@ -113,7 +119,6 @@ pub(crate) fn highlight( | |||
113 | WalkEvent::Leave(Some(mc)) => { | 119 | WalkEvent::Leave(Some(mc)) => { |
114 | assert_eq!(current_macro_call, Some(mc)); | 120 | assert_eq!(current_macro_call, Some(mc)); |
115 | current_macro_call = None; | 121 | current_macro_call = None; |
116 | format_string_highlighter = FormatStringHighlighter::default(); | ||
117 | } | 122 | } |
118 | _ => (), | 123 | _ => (), |
119 | } | 124 | } |
@@ -131,33 +136,24 @@ pub(crate) fn highlight( | |||
131 | } | 136 | } |
132 | _ => (), | 137 | _ => (), |
133 | } | 138 | } |
134 | |||
135 | match &event { | 139 | match &event { |
136 | // Check for Rust code in documentation | ||
137 | WalkEvent::Leave(NodeOrToken::Node(node)) => { | ||
138 | if ast::Attr::can_cast(node.kind()) { | ||
139 | inside_attribute = false | ||
140 | } | ||
141 | if let Some((doctest, range_mapping, new_comments)) = | ||
142 | injection::extract_doc_comments(node) | ||
143 | { | ||
144 | injection::highlight_doc_comment( | ||
145 | doctest, | ||
146 | range_mapping, | ||
147 | new_comments, | ||
148 | &mut stack, | ||
149 | ); | ||
150 | } | ||
151 | } | ||
152 | WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { | 140 | WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { |
153 | inside_attribute = true | 141 | inside_attribute = true |
154 | } | 142 | } |
143 | WalkEvent::Leave(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { | ||
144 | inside_attribute = false | ||
145 | } | ||
155 | _ => (), | 146 | _ => (), |
156 | } | 147 | } |
157 | 148 | ||
158 | let element = match event { | 149 | let element = match event { |
159 | WalkEvent::Enter(it) => it, | 150 | WalkEvent::Enter(it) => it, |
160 | WalkEvent::Leave(_) => continue, | 151 | WalkEvent::Leave(it) => { |
152 | if let Some(node) = it.as_node() { | ||
153 | inject::doc_comment(hl, node); | ||
154 | } | ||
155 | continue; | ||
156 | } | ||
161 | }; | 157 | }; |
162 | 158 | ||
163 | let range = element.text_range(); | 159 | let range = element.text_range(); |
@@ -177,8 +173,6 @@ pub(crate) fn highlight( | |||
177 | let token = sema.descend_into_macros(token.clone()); | 173 | let token = sema.descend_into_macros(token.clone()); |
178 | let parent = token.parent(); | 174 | let parent = token.parent(); |
179 | 175 | ||
180 | format_string_highlighter.check_for_format_string(&parent); | ||
181 | |||
182 | // We only care Name and Name_ref | 176 | // We only care Name and Name_ref |
183 | match (token.kind(), parent.kind()) { | 177 | match (token.kind(), parent.kind()) { |
184 | (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), | 178 | (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), |
@@ -191,213 +185,45 @@ pub(crate) fn highlight( | |||
191 | if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) { | 185 | if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) { |
192 | if token.is_raw() { | 186 | if token.is_raw() { |
193 | let expanded = element_to_highlight.as_token().unwrap().clone(); | 187 | let expanded = element_to_highlight.as_token().unwrap().clone(); |
194 | if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() { | 188 | if inject::ra_fixture(hl, &sema, token, expanded).is_some() { |
195 | continue; | 189 | continue; |
196 | } | 190 | } |
197 | } | 191 | } |
198 | } | 192 | } |
199 | 193 | ||
200 | if let Some((mut highlight, binding_hash)) = highlight_element( | 194 | if let Some(_) = macro_rules_highlighter.highlight(element_to_highlight.clone()) { |
195 | continue; | ||
196 | } | ||
197 | |||
198 | if let Some((mut highlight, binding_hash)) = highlight::element( | ||
201 | &sema, | 199 | &sema, |
202 | &mut bindings_shadow_count, | 200 | &mut bindings_shadow_count, |
203 | syntactic_name_ref_highlighting, | 201 | syntactic_name_ref_highlighting, |
204 | element_to_highlight.clone(), | 202 | element_to_highlight.clone(), |
205 | ) { | 203 | ) { |
206 | if inside_attribute { | 204 | if inside_attribute { |
207 | highlight = highlight | HighlightModifier::Attribute; | 205 | highlight = highlight | HlMod::Attribute; |
208 | } | ||
209 | |||
210 | if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() { | ||
211 | stack.add(HighlightedRange { range, highlight, binding_hash }); | ||
212 | } | 206 | } |
213 | 207 | ||
214 | if let Some(string) = | 208 | hl.add(HlRange { range, highlight, binding_hash }); |
215 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | ||
216 | { | ||
217 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); | ||
218 | // Highlight escape sequences | ||
219 | if let Some(char_ranges) = string.char_ranges() { | ||
220 | stack.push(); | ||
221 | for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { | ||
222 | if string.text()[piece_range.start().into()..].starts_with('\\') { | ||
223 | stack.add(HighlightedRange { | ||
224 | range: piece_range + range.start(), | ||
225 | highlight: HighlightTag::EscapeSequence.into(), | ||
226 | binding_hash: None, | ||
227 | }); | ||
228 | } | ||
229 | } | ||
230 | stack.pop_and_inject(None); | ||
231 | } | ||
232 | } | ||
233 | } | 209 | } |
234 | } | ||
235 | |||
236 | stack.flattened() | ||
237 | } | ||
238 | |||
239 | #[derive(Debug)] | ||
240 | struct HighlightedRangeStack { | ||
241 | stack: Vec<Vec<HighlightedRange>>, | ||
242 | } | ||
243 | |||
244 | /// We use a stack to implement the flattening logic for the highlighted | ||
245 | /// syntax ranges. | ||
246 | impl HighlightedRangeStack { | ||
247 | fn new() -> Self { | ||
248 | Self { stack: vec![Vec::new()] } | ||
249 | } | ||
250 | |||
251 | fn push(&mut self) { | ||
252 | self.stack.push(Vec::new()); | ||
253 | } | ||
254 | |||
255 | /// Flattens the highlighted ranges. | ||
256 | /// | ||
257 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
258 | /// 1) parent-range: Attribute [0, 23) | ||
259 | /// 2) child-range: String [16, 21) | ||
260 | /// | ||
261 | /// The following code implements the flattening, for our example this results to: | ||
262 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
263 | fn pop(&mut self) { | ||
264 | let children = self.stack.pop().unwrap(); | ||
265 | let prev = self.stack.last_mut().unwrap(); | ||
266 | let needs_flattening = !children.is_empty() | ||
267 | && !prev.is_empty() | ||
268 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
269 | if !needs_flattening { | ||
270 | prev.extend(children); | ||
271 | } else { | ||
272 | let mut parent = prev.pop().unwrap(); | ||
273 | for ele in children { | ||
274 | assert!(parent.range.contains_range(ele.range)); | ||
275 | |||
276 | let cloned = Self::intersect(&mut parent, &ele); | ||
277 | if !parent.range.is_empty() { | ||
278 | prev.push(parent); | ||
279 | } | ||
280 | prev.push(ele); | ||
281 | parent = cloned; | ||
282 | } | ||
283 | if !parent.range.is_empty() { | ||
284 | prev.push(parent); | ||
285 | } | ||
286 | } | ||
287 | } | ||
288 | |||
289 | /// Intersects the `HighlightedRange` `parent` with `child`. | ||
290 | /// `parent` is mutated in place, becoming the range before `child`. | ||
291 | /// Returns the range (of the same type as `parent`) *after* `child`. | ||
292 | fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange { | ||
293 | assert!(parent.range.contains_range(child.range)); | ||
294 | |||
295 | let mut cloned = parent.clone(); | ||
296 | parent.range = TextRange::new(parent.range.start(), child.range.start()); | ||
297 | cloned.range = TextRange::new(child.range.end(), cloned.range.end()); | ||
298 | 210 | ||
299 | cloned | 211 | if let Some(string) = element_to_highlight.as_token().cloned().and_then(ast::String::cast) { |
300 | } | 212 | highlight_format_string(hl, &string, range); |
301 | 213 | // Highlight escape sequences | |
302 | /// Remove the `HighlightRange` of `parent` that's currently covered by `child`. | 214 | if let Some(char_ranges) = string.char_ranges() { |
303 | fn intersect_partial(parent: &mut HighlightedRange, child: &HighlightedRange) { | 215 | for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { |
304 | assert!( | 216 | if string.text()[piece_range.start().into()..].starts_with('\\') { |
305 | parent.range.start() <= child.range.start() | 217 | hl.add(HlRange { |
306 | && parent.range.end() >= child.range.start() | 218 | range: piece_range + range.start(), |
307 | && child.range.end() > parent.range.end() | 219 | highlight: HlTag::EscapeSequence.into(), |
308 | ); | 220 | binding_hash: None, |
309 | 221 | }); | |
310 | parent.range = TextRange::new(parent.range.start(), child.range.start()); | ||
311 | } | ||
312 | |||
313 | /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`) | ||
314 | /// can only modify the last range currently on the stack. | ||
315 | /// Can be used to do injections that span multiple ranges, like the | ||
316 | /// doctest injection below. | ||
317 | /// If `overwrite_parent` is non-optional, the highlighting of the parent range | ||
318 | /// is overwritten with the argument. | ||
319 | /// | ||
320 | /// Note that `pop` can be simulated by `pop_and_inject(false)` but the | ||
321 | /// latter is computationally more expensive. | ||
322 | fn pop_and_inject(&mut self, overwrite_parent: Option<Highlight>) { | ||
323 | let mut children = self.stack.pop().unwrap(); | ||
324 | let prev = self.stack.last_mut().unwrap(); | ||
325 | children.sort_by_key(|range| range.range.start()); | ||
326 | prev.sort_by_key(|range| range.range.start()); | ||
327 | |||
328 | for child in children { | ||
329 | if let Some(idx) = | ||
330 | prev.iter().position(|parent| parent.range.contains_range(child.range)) | ||
331 | { | ||
332 | if let Some(tag) = overwrite_parent { | ||
333 | prev[idx].highlight = tag; | ||
334 | } | ||
335 | |||
336 | let cloned = Self::intersect(&mut prev[idx], &child); | ||
337 | let insert_idx = if prev[idx].range.is_empty() { | ||
338 | prev.remove(idx); | ||
339 | idx | ||
340 | } else { | ||
341 | idx + 1 | ||
342 | }; | ||
343 | prev.insert(insert_idx, child); | ||
344 | if !cloned.range.is_empty() { | ||
345 | prev.insert(insert_idx + 1, cloned); | ||
346 | } | ||
347 | } else { | ||
348 | let maybe_idx = | ||
349 | prev.iter().position(|parent| parent.range.contains(child.range.start())); | ||
350 | match (overwrite_parent, maybe_idx) { | ||
351 | (Some(_), Some(idx)) => { | ||
352 | Self::intersect_partial(&mut prev[idx], &child); | ||
353 | let insert_idx = if prev[idx].range.is_empty() { | ||
354 | prev.remove(idx); | ||
355 | idx | ||
356 | } else { | ||
357 | idx + 1 | ||
358 | }; | ||
359 | prev.insert(insert_idx, child); | ||
360 | } | ||
361 | (_, None) => { | ||
362 | let idx = prev | ||
363 | .binary_search_by_key(&child.range.start(), |range| range.range.start()) | ||
364 | .unwrap_or_else(|x| x); | ||
365 | prev.insert(idx, child); | ||
366 | } | ||
367 | _ => { | ||
368 | unreachable!("child range should be completely contained in parent range"); | ||
369 | } | 222 | } |
370 | } | 223 | } |
371 | } | 224 | } |
372 | } | 225 | } |
373 | } | 226 | } |
374 | |||
375 | fn add(&mut self, range: HighlightedRange) { | ||
376 | self.stack | ||
377 | .last_mut() | ||
378 | .expect("during DFS traversal, the stack must not be empty") | ||
379 | .push(range) | ||
380 | } | ||
381 | |||
382 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
383 | assert_eq!( | ||
384 | self.stack.len(), | ||
385 | 1, | ||
386 | "after DFS traversal, the stack should only contain a single element" | ||
387 | ); | ||
388 | let mut res = self.stack.pop().unwrap(); | ||
389 | res.sort_by_key(|range| range.range.start()); | ||
390 | // Check that ranges are sorted and disjoint | ||
391 | for (left, right) in res.iter().zip(res.iter().skip(1)) { | ||
392 | assert!( | ||
393 | left.range.end() <= right.range.start(), | ||
394 | "left: {:#?}, right: {:#?}", | ||
395 | left, | ||
396 | right | ||
397 | ); | ||
398 | } | ||
399 | res | ||
400 | } | ||
401 | } | 227 | } |
402 | 228 | ||
403 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 229 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { |
@@ -415,524 +241,3 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | |||
415 | 241 | ||
416 | Some(TextRange::new(range_start, range_end)) | 242 | Some(TextRange::new(range_start, range_end)) |
417 | } | 243 | } |
418 | |||
419 | /// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly. | ||
420 | fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool { | ||
421 | while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) { | ||
422 | if parent.kind() != *kind { | ||
423 | return false; | ||
424 | } | ||
425 | |||
426 | // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value | ||
427 | // in the same pattern is unstable: rust-lang/rust#68354. | ||
428 | node = node.parent().unwrap().into(); | ||
429 | kinds = rest; | ||
430 | } | ||
431 | |||
432 | // Only true if we matched all expected kinds | ||
433 | kinds.len() == 0 | ||
434 | } | ||
435 | |||
436 | fn is_consumed_lvalue( | ||
437 | node: NodeOrToken<SyntaxNode, SyntaxToken>, | ||
438 | local: &Local, | ||
439 | db: &RootDatabase, | ||
440 | ) -> bool { | ||
441 | // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming. | ||
442 | parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db) | ||
443 | } | ||
444 | |||
445 | fn highlight_element( | ||
446 | sema: &Semantics<RootDatabase>, | ||
447 | bindings_shadow_count: &mut FxHashMap<Name, u32>, | ||
448 | syntactic_name_ref_highlighting: bool, | ||
449 | element: SyntaxElement, | ||
450 | ) -> Option<(Highlight, Option<u64>)> { | ||
451 | let db = sema.db; | ||
452 | let mut binding_hash = None; | ||
453 | let highlight: Highlight = match element.kind() { | ||
454 | FN => { | ||
455 | bindings_shadow_count.clear(); | ||
456 | return None; | ||
457 | } | ||
458 | |||
459 | // Highlight definitions depending on the "type" of the definition. | ||
460 | NAME => { | ||
461 | let name = element.into_node().and_then(ast::Name::cast).unwrap(); | ||
462 | let name_kind = NameClass::classify(sema, &name); | ||
463 | |||
464 | if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { | ||
465 | if let Some(name) = local.name(db) { | ||
466 | let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); | ||
467 | *shadow_count += 1; | ||
468 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) | ||
469 | } | ||
470 | }; | ||
471 | |||
472 | match name_kind { | ||
473 | Some(NameClass::ExternCrate(_)) => HighlightTag::Symbol(SymbolKind::Module).into(), | ||
474 | Some(NameClass::Definition(def)) => { | ||
475 | highlight_def(db, def) | HighlightModifier::Definition | ||
476 | } | ||
477 | Some(NameClass::ConstReference(def)) => highlight_def(db, def), | ||
478 | Some(NameClass::PatFieldShorthand { field_ref, .. }) => { | ||
479 | let mut h = HighlightTag::Symbol(SymbolKind::Field).into(); | ||
480 | if let Definition::Field(field) = field_ref { | ||
481 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
482 | h |= HighlightModifier::Unsafe; | ||
483 | } | ||
484 | } | ||
485 | |||
486 | h | ||
487 | } | ||
488 | None => highlight_name_by_syntax(name) | HighlightModifier::Definition, | ||
489 | } | ||
490 | } | ||
491 | |||
492 | // Highlight references like the definitions they resolve to | ||
493 | NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => { | ||
494 | // even though we track whether we are in an attribute or not we still need this special case | ||
495 | // as otherwise we would emit unresolved references for name refs inside attributes | ||
496 | Highlight::from(HighlightTag::Symbol(SymbolKind::Function)) | ||
497 | } | ||
498 | NAME_REF => { | ||
499 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | ||
500 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { | ||
501 | match NameRefClass::classify(sema, &name_ref) { | ||
502 | Some(name_kind) => match name_kind { | ||
503 | NameRefClass::ExternCrate(_) => { | ||
504 | HighlightTag::Symbol(SymbolKind::Module).into() | ||
505 | } | ||
506 | NameRefClass::Definition(def) => { | ||
507 | if let Definition::Local(local) = &def { | ||
508 | if let Some(name) = local.name(db) { | ||
509 | let shadow_count = | ||
510 | bindings_shadow_count.entry(name.clone()).or_default(); | ||
511 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) | ||
512 | } | ||
513 | }; | ||
514 | |||
515 | let mut h = highlight_def(db, def); | ||
516 | |||
517 | if let Definition::Local(local) = &def { | ||
518 | if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) { | ||
519 | h |= HighlightModifier::Consuming; | ||
520 | } | ||
521 | } | ||
522 | |||
523 | if let Some(parent) = name_ref.syntax().parent() { | ||
524 | if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { | ||
525 | if let Definition::Field(field) = def { | ||
526 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
527 | h |= HighlightModifier::Unsafe; | ||
528 | } | ||
529 | } | ||
530 | } | ||
531 | } | ||
532 | |||
533 | h | ||
534 | } | ||
535 | NameRefClass::FieldShorthand { .. } => { | ||
536 | HighlightTag::Symbol(SymbolKind::Field).into() | ||
537 | } | ||
538 | }, | ||
539 | None if syntactic_name_ref_highlighting => { | ||
540 | highlight_name_ref_by_syntax(name_ref, sema) | ||
541 | } | ||
542 | None => HighlightTag::UnresolvedReference.into(), | ||
543 | } | ||
544 | }) | ||
545 | } | ||
546 | |||
547 | // Simple token-based highlighting | ||
548 | COMMENT => { | ||
549 | let comment = element.into_token().and_then(ast::Comment::cast)?; | ||
550 | let h = HighlightTag::Comment; | ||
551 | match comment.kind().doc { | ||
552 | Some(_) => h | HighlightModifier::Documentation, | ||
553 | None => h.into(), | ||
554 | } | ||
555 | } | ||
556 | STRING | BYTE_STRING => HighlightTag::StringLiteral.into(), | ||
557 | ATTR => HighlightTag::Attribute.into(), | ||
558 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), | ||
559 | BYTE => HighlightTag::ByteLiteral.into(), | ||
560 | CHAR => HighlightTag::CharLiteral.into(), | ||
561 | QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow, | ||
562 | LIFETIME => { | ||
563 | let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap(); | ||
564 | |||
565 | match NameClass::classify_lifetime(sema, &lifetime) { | ||
566 | Some(NameClass::Definition(def)) => { | ||
567 | highlight_def(db, def) | HighlightModifier::Definition | ||
568 | } | ||
569 | None => match NameRefClass::classify_lifetime(sema, &lifetime) { | ||
570 | Some(NameRefClass::Definition(def)) => highlight_def(db, def), | ||
571 | _ => Highlight::new(HighlightTag::Symbol(SymbolKind::LifetimeParam)), | ||
572 | }, | ||
573 | _ => { | ||
574 | Highlight::new(HighlightTag::Symbol(SymbolKind::LifetimeParam)) | ||
575 | | HighlightModifier::Definition | ||
576 | } | ||
577 | } | ||
578 | } | ||
579 | p if p.is_punct() => match p { | ||
580 | T![&] => { | ||
581 | let h = HighlightTag::Operator.into(); | ||
582 | let is_unsafe = element | ||
583 | .parent() | ||
584 | .and_then(ast::RefExpr::cast) | ||
585 | .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr)) | ||
586 | .unwrap_or(false); | ||
587 | if is_unsafe { | ||
588 | h | HighlightModifier::Unsafe | ||
589 | } else { | ||
590 | h | ||
591 | } | ||
592 | } | ||
593 | T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => { | ||
594 | HighlightTag::Operator.into() | ||
595 | } | ||
596 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { | ||
597 | HighlightTag::Symbol(SymbolKind::Macro).into() | ||
598 | } | ||
599 | T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => { | ||
600 | HighlightTag::BuiltinType.into() | ||
601 | } | ||
602 | T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => { | ||
603 | HighlightTag::Keyword.into() | ||
604 | } | ||
605 | T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
606 | let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; | ||
607 | |||
608 | let expr = prefix_expr.expr()?; | ||
609 | let ty = sema.type_of_expr(&expr)?; | ||
610 | if ty.is_raw_ptr() { | ||
611 | HighlightTag::Operator | HighlightModifier::Unsafe | ||
612 | } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() { | ||
613 | HighlightTag::Operator.into() | ||
614 | } else { | ||
615 | HighlightTag::Punctuation.into() | ||
616 | } | ||
617 | } | ||
618 | T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
619 | let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; | ||
620 | |||
621 | let expr = prefix_expr.expr()?; | ||
622 | match expr { | ||
623 | ast::Expr::Literal(_) => HighlightTag::NumericLiteral, | ||
624 | _ => HighlightTag::Operator, | ||
625 | } | ||
626 | .into() | ||
627 | } | ||
628 | _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
629 | HighlightTag::Operator.into() | ||
630 | } | ||
631 | _ if element.parent().and_then(ast::BinExpr::cast).is_some() => { | ||
632 | HighlightTag::Operator.into() | ||
633 | } | ||
634 | _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => { | ||
635 | HighlightTag::Operator.into() | ||
636 | } | ||
637 | _ if element.parent().and_then(ast::RangePat::cast).is_some() => { | ||
638 | HighlightTag::Operator.into() | ||
639 | } | ||
640 | _ if element.parent().and_then(ast::RestPat::cast).is_some() => { | ||
641 | HighlightTag::Operator.into() | ||
642 | } | ||
643 | _ if element.parent().and_then(ast::Attr::cast).is_some() => { | ||
644 | HighlightTag::Attribute.into() | ||
645 | } | ||
646 | _ => HighlightTag::Punctuation.into(), | ||
647 | }, | ||
648 | |||
649 | k if k.is_keyword() => { | ||
650 | let h = Highlight::new(HighlightTag::Keyword); | ||
651 | match k { | ||
652 | T![break] | ||
653 | | T![continue] | ||
654 | | T![else] | ||
655 | | T![if] | ||
656 | | T![loop] | ||
657 | | T![match] | ||
658 | | T![return] | ||
659 | | T![while] | ||
660 | | T![in] => h | HighlightModifier::ControlFlow, | ||
661 | T![for] if !is_child_of_impl(&element) => h | HighlightModifier::ControlFlow, | ||
662 | T![unsafe] => h | HighlightModifier::Unsafe, | ||
663 | T![true] | T![false] => HighlightTag::BoolLiteral.into(), | ||
664 | T![self] => { | ||
665 | let self_param_is_mut = element | ||
666 | .parent() | ||
667 | .and_then(ast::SelfParam::cast) | ||
668 | .and_then(|p| p.mut_token()) | ||
669 | .is_some(); | ||
670 | let self_path = &element | ||
671 | .parent() | ||
672 | .as_ref() | ||
673 | .and_then(SyntaxNode::parent) | ||
674 | .and_then(ast::Path::cast) | ||
675 | .and_then(|p| sema.resolve_path(&p)); | ||
676 | let mut h = HighlightTag::Symbol(SymbolKind::SelfParam).into(); | ||
677 | if self_param_is_mut | ||
678 | || matches!(self_path, | ||
679 | Some(hir::PathResolution::Local(local)) | ||
680 | if local.is_self(db) | ||
681 | && (local.is_mut(db) || local.ty(db).is_mutable_reference()) | ||
682 | ) | ||
683 | { | ||
684 | h |= HighlightModifier::Mutable | ||
685 | } | ||
686 | |||
687 | if let Some(hir::PathResolution::Local(local)) = self_path { | ||
688 | if is_consumed_lvalue(element, &local, db) { | ||
689 | h |= HighlightModifier::Consuming; | ||
690 | } | ||
691 | } | ||
692 | |||
693 | h | ||
694 | } | ||
695 | T![ref] => element | ||
696 | .parent() | ||
697 | .and_then(ast::IdentPat::cast) | ||
698 | .and_then(|ident_pat| { | ||
699 | if sema.is_unsafe_ident_pat(&ident_pat) { | ||
700 | Some(HighlightModifier::Unsafe) | ||
701 | } else { | ||
702 | None | ||
703 | } | ||
704 | }) | ||
705 | .map(|modifier| h | modifier) | ||
706 | .unwrap_or(h), | ||
707 | _ => h, | ||
708 | } | ||
709 | } | ||
710 | |||
711 | _ => return None, | ||
712 | }; | ||
713 | |||
714 | return Some((highlight, binding_hash)); | ||
715 | |||
716 | fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 { | ||
717 | fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 { | ||
718 | use std::{collections::hash_map::DefaultHasher, hash::Hasher}; | ||
719 | |||
720 | let mut hasher = DefaultHasher::new(); | ||
721 | x.hash(&mut hasher); | ||
722 | hasher.finish() | ||
723 | } | ||
724 | |||
725 | hash((name, shadow_count)) | ||
726 | } | ||
727 | } | ||
728 | |||
729 | fn is_child_of_impl(element: &SyntaxElement) -> bool { | ||
730 | match element.parent() { | ||
731 | Some(e) => e.kind() == IMPL, | ||
732 | _ => false, | ||
733 | } | ||
734 | } | ||
735 | |||
736 | fn highlight_func_by_name_ref( | ||
737 | sema: &Semantics<RootDatabase>, | ||
738 | name_ref: &ast::NameRef, | ||
739 | ) -> Option<Highlight> { | ||
740 | let method_call = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?; | ||
741 | highlight_method_call(sema, &method_call) | ||
742 | } | ||
743 | |||
744 | fn highlight_method_call( | ||
745 | sema: &Semantics<RootDatabase>, | ||
746 | method_call: &ast::MethodCallExpr, | ||
747 | ) -> Option<Highlight> { | ||
748 | let func = sema.resolve_method_call(&method_call)?; | ||
749 | let mut h = HighlightTag::Symbol(SymbolKind::Function).into(); | ||
750 | h |= HighlightModifier::Associated; | ||
751 | if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { | ||
752 | h |= HighlightModifier::Unsafe; | ||
753 | } | ||
754 | if let Some(self_param) = func.self_param(sema.db) { | ||
755 | match self_param.access(sema.db) { | ||
756 | hir::Access::Shared => (), | ||
757 | hir::Access::Exclusive => h |= HighlightModifier::Mutable, | ||
758 | hir::Access::Owned => { | ||
759 | if let Some(receiver_ty) = | ||
760 | method_call.receiver().and_then(|it| sema.type_of_expr(&it)) | ||
761 | { | ||
762 | if !receiver_ty.is_copy(sema.db) { | ||
763 | h |= HighlightModifier::Consuming | ||
764 | } | ||
765 | } | ||
766 | } | ||
767 | } | ||
768 | } | ||
769 | Some(h) | ||
770 | } | ||
771 | |||
772 | fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { | ||
773 | match def { | ||
774 | Definition::Macro(_) => HighlightTag::Symbol(SymbolKind::Macro), | ||
775 | Definition::Field(_) => HighlightTag::Symbol(SymbolKind::Field), | ||
776 | Definition::ModuleDef(def) => match def { | ||
777 | hir::ModuleDef::Module(_) => HighlightTag::Symbol(SymbolKind::Module), | ||
778 | hir::ModuleDef::Function(func) => { | ||
779 | let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::Function)); | ||
780 | if func.as_assoc_item(db).is_some() { | ||
781 | h |= HighlightModifier::Associated; | ||
782 | if func.self_param(db).is_none() { | ||
783 | h |= HighlightModifier::Static | ||
784 | } | ||
785 | } | ||
786 | if func.is_unsafe(db) { | ||
787 | h |= HighlightModifier::Unsafe; | ||
788 | } | ||
789 | return h; | ||
790 | } | ||
791 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Symbol(SymbolKind::Struct), | ||
792 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Symbol(SymbolKind::Enum), | ||
793 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Symbol(SymbolKind::Union), | ||
794 | hir::ModuleDef::Variant(_) => HighlightTag::Symbol(SymbolKind::Variant), | ||
795 | hir::ModuleDef::Const(konst) => { | ||
796 | let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::Const)); | ||
797 | if konst.as_assoc_item(db).is_some() { | ||
798 | h |= HighlightModifier::Associated | ||
799 | } | ||
800 | return h; | ||
801 | } | ||
802 | hir::ModuleDef::Trait(_) => HighlightTag::Symbol(SymbolKind::Trait), | ||
803 | hir::ModuleDef::TypeAlias(type_) => { | ||
804 | let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::TypeAlias)); | ||
805 | if type_.as_assoc_item(db).is_some() { | ||
806 | h |= HighlightModifier::Associated | ||
807 | } | ||
808 | return h; | ||
809 | } | ||
810 | hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType, | ||
811 | hir::ModuleDef::Static(s) => { | ||
812 | let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::Static)); | ||
813 | if s.is_mut(db) { | ||
814 | h |= HighlightModifier::Mutable; | ||
815 | h |= HighlightModifier::Unsafe; | ||
816 | } | ||
817 | return h; | ||
818 | } | ||
819 | }, | ||
820 | Definition::SelfType(_) => HighlightTag::Symbol(SymbolKind::Impl), | ||
821 | Definition::TypeParam(_) => HighlightTag::Symbol(SymbolKind::TypeParam), | ||
822 | Definition::ConstParam(_) => HighlightTag::Symbol(SymbolKind::ConstParam), | ||
823 | Definition::Local(local) => { | ||
824 | let tag = if local.is_param(db) { | ||
825 | HighlightTag::Symbol(SymbolKind::ValueParam) | ||
826 | } else { | ||
827 | HighlightTag::Symbol(SymbolKind::Local) | ||
828 | }; | ||
829 | let mut h = Highlight::new(tag); | ||
830 | if local.is_mut(db) || local.ty(db).is_mutable_reference() { | ||
831 | h |= HighlightModifier::Mutable; | ||
832 | } | ||
833 | if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) { | ||
834 | h |= HighlightModifier::Callable; | ||
835 | } | ||
836 | return h; | ||
837 | } | ||
838 | Definition::LifetimeParam(_) => HighlightTag::Symbol(SymbolKind::LifetimeParam), | ||
839 | Definition::Label(_) => HighlightTag::Symbol(SymbolKind::Label), | ||
840 | } | ||
841 | .into() | ||
842 | } | ||
843 | |||
844 | fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | ||
845 | let default = HighlightTag::UnresolvedReference; | ||
846 | |||
847 | let parent = match name.syntax().parent() { | ||
848 | Some(it) => it, | ||
849 | _ => return default.into(), | ||
850 | }; | ||
851 | |||
852 | let tag = match parent.kind() { | ||
853 | STRUCT => HighlightTag::Symbol(SymbolKind::Struct), | ||
854 | ENUM => HighlightTag::Symbol(SymbolKind::Enum), | ||
855 | VARIANT => HighlightTag::Symbol(SymbolKind::Variant), | ||
856 | UNION => HighlightTag::Symbol(SymbolKind::Union), | ||
857 | TRAIT => HighlightTag::Symbol(SymbolKind::Trait), | ||
858 | TYPE_ALIAS => HighlightTag::Symbol(SymbolKind::TypeAlias), | ||
859 | TYPE_PARAM => HighlightTag::Symbol(SymbolKind::TypeParam), | ||
860 | RECORD_FIELD => HighlightTag::Symbol(SymbolKind::Field), | ||
861 | MODULE => HighlightTag::Symbol(SymbolKind::Module), | ||
862 | FN => HighlightTag::Symbol(SymbolKind::Function), | ||
863 | CONST => HighlightTag::Symbol(SymbolKind::Const), | ||
864 | STATIC => HighlightTag::Symbol(SymbolKind::Static), | ||
865 | IDENT_PAT => HighlightTag::Symbol(SymbolKind::Local), | ||
866 | _ => default, | ||
867 | }; | ||
868 | |||
869 | tag.into() | ||
870 | } | ||
871 | |||
872 | fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight { | ||
873 | let default = HighlightTag::UnresolvedReference; | ||
874 | |||
875 | let parent = match name.syntax().parent() { | ||
876 | Some(it) => it, | ||
877 | _ => return default.into(), | ||
878 | }; | ||
879 | |||
880 | match parent.kind() { | ||
881 | METHOD_CALL_EXPR => { | ||
882 | return ast::MethodCallExpr::cast(parent) | ||
883 | .and_then(|method_call| highlight_method_call(sema, &method_call)) | ||
884 | .unwrap_or_else(|| HighlightTag::Symbol(SymbolKind::Function).into()); | ||
885 | } | ||
886 | FIELD_EXPR => { | ||
887 | let h = HighlightTag::Symbol(SymbolKind::Field); | ||
888 | let is_union = ast::FieldExpr::cast(parent) | ||
889 | .and_then(|field_expr| { | ||
890 | let field = sema.resolve_field(&field_expr)?; | ||
891 | Some(if let VariantDef::Union(_) = field.parent_def(sema.db) { | ||
892 | true | ||
893 | } else { | ||
894 | false | ||
895 | }) | ||
896 | }) | ||
897 | .unwrap_or(false); | ||
898 | if is_union { | ||
899 | h | HighlightModifier::Unsafe | ||
900 | } else { | ||
901 | h.into() | ||
902 | } | ||
903 | } | ||
904 | PATH_SEGMENT => { | ||
905 | let path = match parent.parent().and_then(ast::Path::cast) { | ||
906 | Some(it) => it, | ||
907 | _ => return default.into(), | ||
908 | }; | ||
909 | let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) { | ||
910 | Some(it) => it, | ||
911 | _ => { | ||
912 | // within path, decide whether it is module or adt by checking for uppercase name | ||
913 | return if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
914 | HighlightTag::Symbol(SymbolKind::Struct) | ||
915 | } else { | ||
916 | HighlightTag::Symbol(SymbolKind::Module) | ||
917 | } | ||
918 | .into(); | ||
919 | } | ||
920 | }; | ||
921 | let parent = match expr.syntax().parent() { | ||
922 | Some(it) => it, | ||
923 | None => return default.into(), | ||
924 | }; | ||
925 | |||
926 | match parent.kind() { | ||
927 | CALL_EXPR => HighlightTag::Symbol(SymbolKind::Function).into(), | ||
928 | _ => if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
929 | HighlightTag::Symbol(SymbolKind::Struct) | ||
930 | } else { | ||
931 | HighlightTag::Symbol(SymbolKind::Const) | ||
932 | } | ||
933 | .into(), | ||
934 | } | ||
935 | } | ||
936 | _ => default.into(), | ||
937 | } | ||
938 | } | ||