diff options
author | Igor Aleksanov <[email protected]> | 2020-08-14 05:34:07 +0100 |
---|---|---|
committer | Igor Aleksanov <[email protected]> | 2020-08-14 05:34:07 +0100 |
commit | c26c911ec1e6c2ad1dcb7d155a6a1d528839ad1a (patch) | |
tree | 7cff36c38234be0afb65273146d8247083a5cfeb /crates/ide/src/syntax_highlighting.rs | |
parent | 3c018bf84de5c693b5ee1c6bec0fed3b201c2060 (diff) | |
parent | f1f73649a686dc6e6449afc35e0fa6fed00e225d (diff) |
Merge branch 'master' into add-disable-diagnostics
Diffstat (limited to 'crates/ide/src/syntax_highlighting.rs')
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 872 |
1 files changed, 872 insertions, 0 deletions
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs new file mode 100644 index 000000000..5d7c7e8d0 --- /dev/null +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -0,0 +1,872 @@ | |||
1 | mod tags; | ||
2 | mod html; | ||
3 | mod injection; | ||
4 | #[cfg(test)] | ||
5 | mod tests; | ||
6 | |||
7 | use hir::{Name, Semantics, VariantDef}; | ||
8 | use ide_db::{ | ||
9 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | ||
10 | RootDatabase, | ||
11 | }; | ||
12 | use rustc_hash::FxHashMap; | ||
13 | use syntax::{ | ||
14 | ast::{self, HasFormatSpecifier}, | ||
15 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, | ||
16 | SyntaxKind::*, | ||
17 | TextRange, WalkEvent, T, | ||
18 | }; | ||
19 | |||
20 | use crate::FileId; | ||
21 | |||
22 | use ast::FormatSpecifier; | ||
23 | pub(crate) use html::highlight_as_html; | ||
24 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; | ||
25 | |||
26 | #[derive(Debug, Clone)] | ||
27 | pub struct HighlightedRange { | ||
28 | pub range: TextRange, | ||
29 | pub highlight: Highlight, | ||
30 | pub binding_hash: Option<u64>, | ||
31 | } | ||
32 | |||
33 | // Feature: Semantic Syntax Highlighting | ||
34 | // | ||
35 | // rust-analyzer highlights the code semantically. | ||
36 | // For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait. | ||
37 | // rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token. | ||
38 | // It's up to the client to map those to specific colors. | ||
39 | // | ||
40 | // The general rule is that a reference to an entity gets colored the same way as the entity itself. | ||
41 | // We also give special modifier for `mut` and `&mut` local variables. | ||
42 | pub(crate) fn highlight( | ||
43 | db: &RootDatabase, | ||
44 | file_id: FileId, | ||
45 | range_to_highlight: Option<TextRange>, | ||
46 | syntactic_name_ref_highlighting: bool, | ||
47 | ) -> Vec<HighlightedRange> { | ||
48 | let _p = profile::span("highlight"); | ||
49 | let sema = Semantics::new(db); | ||
50 | |||
51 | // Determine the root based on the given range. | ||
52 | let (root, range_to_highlight) = { | ||
53 | let source_file = sema.parse(file_id); | ||
54 | match range_to_highlight { | ||
55 | Some(range) => { | ||
56 | let node = match source_file.syntax().covering_element(range) { | ||
57 | NodeOrToken::Node(it) => it, | ||
58 | NodeOrToken::Token(it) => it.parent(), | ||
59 | }; | ||
60 | (node, range) | ||
61 | } | ||
62 | None => (source_file.syntax().clone(), source_file.syntax().text_range()), | ||
63 | } | ||
64 | }; | ||
65 | |||
66 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); | ||
67 | // We use a stack for the DFS traversal below. | ||
68 | // When we leave a node, the we use it to flatten the highlighted ranges. | ||
69 | let mut stack = HighlightedRangeStack::new(); | ||
70 | |||
71 | let mut current_macro_call: Option<ast::MacroCall> = None; | ||
72 | let mut format_string: Option<SyntaxElement> = None; | ||
73 | |||
74 | // Walk all nodes, keeping track of whether we are inside a macro or not. | ||
75 | // If in macro, expand it first and highlight the expanded code. | ||
76 | for event in root.preorder_with_tokens() { | ||
77 | match &event { | ||
78 | WalkEvent::Enter(_) => stack.push(), | ||
79 | WalkEvent::Leave(_) => stack.pop(), | ||
80 | }; | ||
81 | |||
82 | let event_range = match &event { | ||
83 | WalkEvent::Enter(it) => it.text_range(), | ||
84 | WalkEvent::Leave(it) => it.text_range(), | ||
85 | }; | ||
86 | |||
87 | // Element outside of the viewport, no need to highlight | ||
88 | if range_to_highlight.intersect(event_range).is_none() { | ||
89 | continue; | ||
90 | } | ||
91 | |||
92 | // Track "inside macro" state | ||
93 | match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) { | ||
94 | WalkEvent::Enter(Some(mc)) => { | ||
95 | current_macro_call = Some(mc.clone()); | ||
96 | if let Some(range) = macro_call_range(&mc) { | ||
97 | stack.add(HighlightedRange { | ||
98 | range, | ||
99 | highlight: HighlightTag::Macro.into(), | ||
100 | binding_hash: None, | ||
101 | }); | ||
102 | } | ||
103 | if let Some(name) = mc.is_macro_rules() { | ||
104 | if let Some((highlight, binding_hash)) = highlight_element( | ||
105 | &sema, | ||
106 | &mut bindings_shadow_count, | ||
107 | syntactic_name_ref_highlighting, | ||
108 | name.syntax().clone().into(), | ||
109 | ) { | ||
110 | stack.add(HighlightedRange { | ||
111 | range: name.syntax().text_range(), | ||
112 | highlight, | ||
113 | binding_hash, | ||
114 | }); | ||
115 | } | ||
116 | } | ||
117 | continue; | ||
118 | } | ||
119 | WalkEvent::Leave(Some(mc)) => { | ||
120 | assert!(current_macro_call == Some(mc)); | ||
121 | current_macro_call = None; | ||
122 | format_string = None; | ||
123 | } | ||
124 | _ => (), | ||
125 | } | ||
126 | |||
127 | // Check for Rust code in documentation | ||
128 | match &event { | ||
129 | WalkEvent::Leave(NodeOrToken::Node(node)) => { | ||
130 | if let Some((doctest, range_mapping, new_comments)) = | ||
131 | injection::extract_doc_comments(node) | ||
132 | { | ||
133 | injection::highlight_doc_comment( | ||
134 | doctest, | ||
135 | range_mapping, | ||
136 | new_comments, | ||
137 | &mut stack, | ||
138 | ); | ||
139 | } | ||
140 | } | ||
141 | _ => (), | ||
142 | } | ||
143 | |||
144 | let element = match event { | ||
145 | WalkEvent::Enter(it) => it, | ||
146 | WalkEvent::Leave(_) => continue, | ||
147 | }; | ||
148 | |||
149 | let range = element.text_range(); | ||
150 | |||
151 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { | ||
152 | // Inside a macro -- expand it first | ||
153 | let token = match element.clone().into_token() { | ||
154 | Some(it) if it.parent().kind() == TOKEN_TREE => it, | ||
155 | _ => continue, | ||
156 | }; | ||
157 | let token = sema.descend_into_macros(token.clone()); | ||
158 | let parent = token.parent(); | ||
159 | |||
160 | // Check if macro takes a format string and remember it for highlighting later. | ||
161 | // The macros that accept a format string expand to a compiler builtin macros | ||
162 | // `format_args` and `format_args_nl`. | ||
163 | if let Some(name) = parent | ||
164 | .parent() | ||
165 | .and_then(ast::MacroCall::cast) | ||
166 | .and_then(|mc| mc.path()) | ||
167 | .and_then(|p| p.segment()) | ||
168 | .and_then(|s| s.name_ref()) | ||
169 | { | ||
170 | match name.text().as_str() { | ||
171 | "format_args" | "format_args_nl" => { | ||
172 | format_string = parent | ||
173 | .children_with_tokens() | ||
174 | .filter(|t| t.kind() != WHITESPACE) | ||
175 | .nth(1) | ||
176 | .filter(|e| { | ||
177 | ast::String::can_cast(e.kind()) | ||
178 | || ast::RawString::can_cast(e.kind()) | ||
179 | }) | ||
180 | } | ||
181 | _ => {} | ||
182 | } | ||
183 | } | ||
184 | |||
185 | // We only care Name and Name_ref | ||
186 | match (token.kind(), parent.kind()) { | ||
187 | (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), | ||
188 | _ => token.into(), | ||
189 | } | ||
190 | } else { | ||
191 | element.clone() | ||
192 | }; | ||
193 | |||
194 | if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { | ||
195 | let expanded = element_to_highlight.as_token().unwrap().clone(); | ||
196 | if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() { | ||
197 | continue; | ||
198 | } | ||
199 | } | ||
200 | |||
201 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); | ||
202 | |||
203 | if let Some((highlight, binding_hash)) = highlight_element( | ||
204 | &sema, | ||
205 | &mut bindings_shadow_count, | ||
206 | syntactic_name_ref_highlighting, | ||
207 | element_to_highlight.clone(), | ||
208 | ) { | ||
209 | stack.add(HighlightedRange { range, highlight, binding_hash }); | ||
210 | if let Some(string) = | ||
211 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | ||
212 | { | ||
213 | if is_format_string { | ||
214 | stack.push(); | ||
215 | string.lex_format_specifier(|piece_range, kind| { | ||
216 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
217 | stack.add(HighlightedRange { | ||
218 | range: piece_range + range.start(), | ||
219 | highlight: highlight.into(), | ||
220 | binding_hash: None, | ||
221 | }); | ||
222 | } | ||
223 | }); | ||
224 | stack.pop(); | ||
225 | } | ||
226 | // Highlight escape sequences | ||
227 | if let Some(char_ranges) = string.char_ranges() { | ||
228 | stack.push(); | ||
229 | for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { | ||
230 | if string.text()[piece_range.start().into()..].starts_with('\\') { | ||
231 | stack.add(HighlightedRange { | ||
232 | range: piece_range + range.start(), | ||
233 | highlight: HighlightTag::EscapeSequence.into(), | ||
234 | binding_hash: None, | ||
235 | }); | ||
236 | } | ||
237 | } | ||
238 | stack.pop_and_inject(None); | ||
239 | } | ||
240 | } else if let Some(string) = | ||
241 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) | ||
242 | { | ||
243 | if is_format_string { | ||
244 | stack.push(); | ||
245 | string.lex_format_specifier(|piece_range, kind| { | ||
246 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
247 | stack.add(HighlightedRange { | ||
248 | range: piece_range + range.start(), | ||
249 | highlight: highlight.into(), | ||
250 | binding_hash: None, | ||
251 | }); | ||
252 | } | ||
253 | }); | ||
254 | stack.pop(); | ||
255 | } | ||
256 | } | ||
257 | } | ||
258 | } | ||
259 | |||
260 | stack.flattened() | ||
261 | } | ||
262 | |||
263 | #[derive(Debug)] | ||
264 | struct HighlightedRangeStack { | ||
265 | stack: Vec<Vec<HighlightedRange>>, | ||
266 | } | ||
267 | |||
268 | /// We use a stack to implement the flattening logic for the highlighted | ||
269 | /// syntax ranges. | ||
270 | impl HighlightedRangeStack { | ||
271 | fn new() -> Self { | ||
272 | Self { stack: vec![Vec::new()] } | ||
273 | } | ||
274 | |||
275 | fn push(&mut self) { | ||
276 | self.stack.push(Vec::new()); | ||
277 | } | ||
278 | |||
279 | /// Flattens the highlighted ranges. | ||
280 | /// | ||
281 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
282 | /// 1) parent-range: Attribute [0, 23) | ||
283 | /// 2) child-range: String [16, 21) | ||
284 | /// | ||
285 | /// The following code implements the flattening, for our example this results to: | ||
286 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
287 | fn pop(&mut self) { | ||
288 | let children = self.stack.pop().unwrap(); | ||
289 | let prev = self.stack.last_mut().unwrap(); | ||
290 | let needs_flattening = !children.is_empty() | ||
291 | && !prev.is_empty() | ||
292 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
293 | if !needs_flattening { | ||
294 | prev.extend(children); | ||
295 | } else { | ||
296 | let mut parent = prev.pop().unwrap(); | ||
297 | for ele in children { | ||
298 | assert!(parent.range.contains_range(ele.range)); | ||
299 | |||
300 | let cloned = Self::intersect(&mut parent, &ele); | ||
301 | if !parent.range.is_empty() { | ||
302 | prev.push(parent); | ||
303 | } | ||
304 | prev.push(ele); | ||
305 | parent = cloned; | ||
306 | } | ||
307 | if !parent.range.is_empty() { | ||
308 | prev.push(parent); | ||
309 | } | ||
310 | } | ||
311 | } | ||
312 | |||
313 | /// Intersects the `HighlightedRange` `parent` with `child`. | ||
314 | /// `parent` is mutated in place, becoming the range before `child`. | ||
315 | /// Returns the range (of the same type as `parent`) *after* `child`. | ||
316 | fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange { | ||
317 | assert!(parent.range.contains_range(child.range)); | ||
318 | |||
319 | let mut cloned = parent.clone(); | ||
320 | parent.range = TextRange::new(parent.range.start(), child.range.start()); | ||
321 | cloned.range = TextRange::new(child.range.end(), cloned.range.end()); | ||
322 | |||
323 | cloned | ||
324 | } | ||
325 | |||
326 | /// Remove the `HighlightRange` of `parent` that's currently covered by `child`. | ||
327 | fn intersect_partial(parent: &mut HighlightedRange, child: &HighlightedRange) { | ||
328 | assert!( | ||
329 | parent.range.start() <= child.range.start() | ||
330 | && parent.range.end() >= child.range.start() | ||
331 | && child.range.end() > parent.range.end() | ||
332 | ); | ||
333 | |||
334 | parent.range = TextRange::new(parent.range.start(), child.range.start()); | ||
335 | } | ||
336 | |||
337 | /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`) | ||
338 | /// can only modify the last range currently on the stack. | ||
339 | /// Can be used to do injections that span multiple ranges, like the | ||
340 | /// doctest injection below. | ||
341 | /// If `overwrite_parent` is non-optional, the highlighting of the parent range | ||
342 | /// is overwritten with the argument. | ||
343 | /// | ||
344 | /// Note that `pop` can be simulated by `pop_and_inject(false)` but the | ||
345 | /// latter is computationally more expensive. | ||
346 | fn pop_and_inject(&mut self, overwrite_parent: Option<Highlight>) { | ||
347 | let mut children = self.stack.pop().unwrap(); | ||
348 | let prev = self.stack.last_mut().unwrap(); | ||
349 | children.sort_by_key(|range| range.range.start()); | ||
350 | prev.sort_by_key(|range| range.range.start()); | ||
351 | |||
352 | for child in children { | ||
353 | if let Some(idx) = | ||
354 | prev.iter().position(|parent| parent.range.contains_range(child.range)) | ||
355 | { | ||
356 | if let Some(tag) = overwrite_parent { | ||
357 | prev[idx].highlight = tag; | ||
358 | } | ||
359 | |||
360 | let cloned = Self::intersect(&mut prev[idx], &child); | ||
361 | let insert_idx = if prev[idx].range.is_empty() { | ||
362 | prev.remove(idx); | ||
363 | idx | ||
364 | } else { | ||
365 | idx + 1 | ||
366 | }; | ||
367 | prev.insert(insert_idx, child); | ||
368 | if !cloned.range.is_empty() { | ||
369 | prev.insert(insert_idx + 1, cloned); | ||
370 | } | ||
371 | } else { | ||
372 | let maybe_idx = | ||
373 | prev.iter().position(|parent| parent.range.contains(child.range.start())); | ||
374 | match (overwrite_parent, maybe_idx) { | ||
375 | (Some(_), Some(idx)) => { | ||
376 | Self::intersect_partial(&mut prev[idx], &child); | ||
377 | let insert_idx = if prev[idx].range.is_empty() { | ||
378 | prev.remove(idx); | ||
379 | idx | ||
380 | } else { | ||
381 | idx + 1 | ||
382 | }; | ||
383 | prev.insert(insert_idx, child); | ||
384 | } | ||
385 | (_, None) => { | ||
386 | let idx = prev | ||
387 | .binary_search_by_key(&child.range.start(), |range| range.range.start()) | ||
388 | .unwrap_or_else(|x| x); | ||
389 | prev.insert(idx, child); | ||
390 | } | ||
391 | _ => { | ||
392 | unreachable!("child range should be completely contained in parent range"); | ||
393 | } | ||
394 | } | ||
395 | } | ||
396 | } | ||
397 | } | ||
398 | |||
399 | fn add(&mut self, range: HighlightedRange) { | ||
400 | self.stack | ||
401 | .last_mut() | ||
402 | .expect("during DFS traversal, the stack must not be empty") | ||
403 | .push(range) | ||
404 | } | ||
405 | |||
406 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
407 | assert_eq!( | ||
408 | self.stack.len(), | ||
409 | 1, | ||
410 | "after DFS traversal, the stack should only contain a single element" | ||
411 | ); | ||
412 | let mut res = self.stack.pop().unwrap(); | ||
413 | res.sort_by_key(|range| range.range.start()); | ||
414 | // Check that ranges are sorted and disjoint | ||
415 | assert!(res | ||
416 | .iter() | ||
417 | .zip(res.iter().skip(1)) | ||
418 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
419 | res | ||
420 | } | ||
421 | } | ||
422 | |||
423 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | ||
424 | Some(match kind { | ||
425 | FormatSpecifier::Open | ||
426 | | FormatSpecifier::Close | ||
427 | | FormatSpecifier::Colon | ||
428 | | FormatSpecifier::Fill | ||
429 | | FormatSpecifier::Align | ||
430 | | FormatSpecifier::Sign | ||
431 | | FormatSpecifier::NumberSign | ||
432 | | FormatSpecifier::DollarSign | ||
433 | | FormatSpecifier::Dot | ||
434 | | FormatSpecifier::Asterisk | ||
435 | | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, | ||
436 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
437 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
438 | }) | ||
439 | } | ||
440 | |||
441 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | ||
442 | let path = macro_call.path()?; | ||
443 | let name_ref = path.segment()?.name_ref()?; | ||
444 | |||
445 | let range_start = name_ref.syntax().text_range().start(); | ||
446 | let mut range_end = name_ref.syntax().text_range().end(); | ||
447 | for sibling in path.syntax().siblings_with_tokens(Direction::Next) { | ||
448 | match sibling.kind() { | ||
449 | T![!] | IDENT => range_end = sibling.text_range().end(), | ||
450 | _ => (), | ||
451 | } | ||
452 | } | ||
453 | |||
454 | Some(TextRange::new(range_start, range_end)) | ||
455 | } | ||
456 | |||
457 | fn is_possibly_unsafe(name_ref: &ast::NameRef) -> bool { | ||
458 | name_ref | ||
459 | .syntax() | ||
460 | .parent() | ||
461 | .and_then(|parent| { | ||
462 | ast::FieldExpr::cast(parent.clone()) | ||
463 | .map(|_| true) | ||
464 | .or_else(|| ast::RecordPatField::cast(parent).map(|_| true)) | ||
465 | }) | ||
466 | .unwrap_or(false) | ||
467 | } | ||
468 | |||
469 | fn highlight_element( | ||
470 | sema: &Semantics<RootDatabase>, | ||
471 | bindings_shadow_count: &mut FxHashMap<Name, u32>, | ||
472 | syntactic_name_ref_highlighting: bool, | ||
473 | element: SyntaxElement, | ||
474 | ) -> Option<(Highlight, Option<u64>)> { | ||
475 | let db = sema.db; | ||
476 | let mut binding_hash = None; | ||
477 | let highlight: Highlight = match element.kind() { | ||
478 | FN => { | ||
479 | bindings_shadow_count.clear(); | ||
480 | return None; | ||
481 | } | ||
482 | |||
483 | // Highlight definitions depending on the "type" of the definition. | ||
484 | NAME => { | ||
485 | let name = element.into_node().and_then(ast::Name::cast).unwrap(); | ||
486 | let name_kind = classify_name(sema, &name); | ||
487 | |||
488 | if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { | ||
489 | if let Some(name) = local.name(db) { | ||
490 | let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); | ||
491 | *shadow_count += 1; | ||
492 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) | ||
493 | } | ||
494 | }; | ||
495 | |||
496 | match name_kind { | ||
497 | Some(NameClass::ExternCrate(_)) => HighlightTag::Module.into(), | ||
498 | Some(NameClass::Definition(def)) => { | ||
499 | highlight_name(sema, db, def, None, false) | HighlightModifier::Definition | ||
500 | } | ||
501 | Some(NameClass::ConstReference(def)) => highlight_name(sema, db, def, None, false), | ||
502 | Some(NameClass::FieldShorthand { field, .. }) => { | ||
503 | let mut h = HighlightTag::Field.into(); | ||
504 | if let Definition::Field(field) = field { | ||
505 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
506 | h |= HighlightModifier::Unsafe; | ||
507 | } | ||
508 | } | ||
509 | |||
510 | h | ||
511 | } | ||
512 | None => highlight_name_by_syntax(name) | HighlightModifier::Definition, | ||
513 | } | ||
514 | } | ||
515 | |||
516 | // Highlight references like the definitions they resolve to | ||
517 | NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => { | ||
518 | Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute | ||
519 | } | ||
520 | NAME_REF => { | ||
521 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | ||
522 | let possibly_unsafe = is_possibly_unsafe(&name_ref); | ||
523 | match classify_name_ref(sema, &name_ref) { | ||
524 | Some(name_kind) => match name_kind { | ||
525 | NameRefClass::ExternCrate(_) => HighlightTag::Module.into(), | ||
526 | NameRefClass::Definition(def) => { | ||
527 | if let Definition::Local(local) = &def { | ||
528 | if let Some(name) = local.name(db) { | ||
529 | let shadow_count = | ||
530 | bindings_shadow_count.entry(name.clone()).or_default(); | ||
531 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) | ||
532 | } | ||
533 | }; | ||
534 | highlight_name(sema, db, def, Some(name_ref), possibly_unsafe) | ||
535 | } | ||
536 | NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), | ||
537 | }, | ||
538 | None if syntactic_name_ref_highlighting => { | ||
539 | highlight_name_ref_by_syntax(name_ref, sema) | ||
540 | } | ||
541 | None => HighlightTag::UnresolvedReference.into(), | ||
542 | } | ||
543 | } | ||
544 | |||
545 | // Simple token-based highlighting | ||
546 | COMMENT => { | ||
547 | let comment = element.into_token().and_then(ast::Comment::cast)?; | ||
548 | let h = HighlightTag::Comment; | ||
549 | match comment.kind().doc { | ||
550 | Some(_) => h | HighlightModifier::Documentation, | ||
551 | None => h.into(), | ||
552 | } | ||
553 | } | ||
554 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(), | ||
555 | ATTR => HighlightTag::Attribute.into(), | ||
556 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), | ||
557 | BYTE => HighlightTag::ByteLiteral.into(), | ||
558 | CHAR => HighlightTag::CharLiteral.into(), | ||
559 | QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow, | ||
560 | LIFETIME => { | ||
561 | let h = Highlight::new(HighlightTag::Lifetime); | ||
562 | match element.parent().map(|it| it.kind()) { | ||
563 | Some(LIFETIME_PARAM) | Some(LABEL) => h | HighlightModifier::Definition, | ||
564 | _ => h, | ||
565 | } | ||
566 | } | ||
567 | p if p.is_punct() => match p { | ||
568 | T![&] => { | ||
569 | let h = HighlightTag::Operator.into(); | ||
570 | let is_unsafe = element | ||
571 | .parent() | ||
572 | .and_then(ast::RefExpr::cast) | ||
573 | .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr)) | ||
574 | .unwrap_or(false); | ||
575 | if is_unsafe { | ||
576 | h | HighlightModifier::Unsafe | ||
577 | } else { | ||
578 | h | ||
579 | } | ||
580 | } | ||
581 | T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] => HighlightTag::Operator.into(), | ||
582 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { | ||
583 | HighlightTag::Macro.into() | ||
584 | } | ||
585 | T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => { | ||
586 | HighlightTag::Keyword.into() | ||
587 | } | ||
588 | T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
589 | let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; | ||
590 | |||
591 | let expr = prefix_expr.expr()?; | ||
592 | let ty = sema.type_of_expr(&expr)?; | ||
593 | if ty.is_raw_ptr() { | ||
594 | HighlightTag::Operator | HighlightModifier::Unsafe | ||
595 | } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() { | ||
596 | HighlightTag::Operator.into() | ||
597 | } else { | ||
598 | HighlightTag::Punctuation.into() | ||
599 | } | ||
600 | } | ||
601 | T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
602 | HighlightTag::NumericLiteral.into() | ||
603 | } | ||
604 | _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
605 | HighlightTag::Operator.into() | ||
606 | } | ||
607 | _ if element.parent().and_then(ast::BinExpr::cast).is_some() => { | ||
608 | HighlightTag::Operator.into() | ||
609 | } | ||
610 | _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => { | ||
611 | HighlightTag::Operator.into() | ||
612 | } | ||
613 | _ if element.parent().and_then(ast::RangePat::cast).is_some() => { | ||
614 | HighlightTag::Operator.into() | ||
615 | } | ||
616 | _ if element.parent().and_then(ast::RestPat::cast).is_some() => { | ||
617 | HighlightTag::Operator.into() | ||
618 | } | ||
619 | _ if element.parent().and_then(ast::Attr::cast).is_some() => { | ||
620 | HighlightTag::Attribute.into() | ||
621 | } | ||
622 | _ => HighlightTag::Punctuation.into(), | ||
623 | }, | ||
624 | |||
625 | k if k.is_keyword() => { | ||
626 | let h = Highlight::new(HighlightTag::Keyword); | ||
627 | match k { | ||
628 | T![break] | ||
629 | | T![continue] | ||
630 | | T![else] | ||
631 | | T![if] | ||
632 | | T![loop] | ||
633 | | T![match] | ||
634 | | T![return] | ||
635 | | T![while] | ||
636 | | T![in] => h | HighlightModifier::ControlFlow, | ||
637 | T![for] if !is_child_of_impl(&element) => h | HighlightModifier::ControlFlow, | ||
638 | T![unsafe] => h | HighlightModifier::Unsafe, | ||
639 | T![true] | T![false] => HighlightTag::BoolLiteral.into(), | ||
640 | T![self] => { | ||
641 | let self_param_is_mut = element | ||
642 | .parent() | ||
643 | .and_then(ast::SelfParam::cast) | ||
644 | .and_then(|p| p.mut_token()) | ||
645 | .is_some(); | ||
646 | // closure to enforce lazyness | ||
647 | let self_path = || { | ||
648 | sema.resolve_path(&element.parent()?.parent().and_then(ast::Path::cast)?) | ||
649 | }; | ||
650 | if self_param_is_mut | ||
651 | || matches!(self_path(), | ||
652 | Some(hir::PathResolution::Local(local)) | ||
653 | if local.is_self(db) | ||
654 | && (local.is_mut(db) || local.ty(db).is_mutable_reference()) | ||
655 | ) | ||
656 | { | ||
657 | HighlightTag::SelfKeyword | HighlightModifier::Mutable | ||
658 | } else { | ||
659 | HighlightTag::SelfKeyword.into() | ||
660 | } | ||
661 | } | ||
662 | T![ref] => element | ||
663 | .parent() | ||
664 | .and_then(ast::IdentPat::cast) | ||
665 | .and_then(|ident_pat| { | ||
666 | if sema.is_unsafe_ident_pat(&ident_pat) { | ||
667 | Some(HighlightModifier::Unsafe) | ||
668 | } else { | ||
669 | None | ||
670 | } | ||
671 | }) | ||
672 | .map(|modifier| h | modifier) | ||
673 | .unwrap_or(h), | ||
674 | _ => h, | ||
675 | } | ||
676 | } | ||
677 | |||
678 | _ => return None, | ||
679 | }; | ||
680 | |||
681 | return Some((highlight, binding_hash)); | ||
682 | |||
683 | fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 { | ||
684 | fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 { | ||
685 | use std::{collections::hash_map::DefaultHasher, hash::Hasher}; | ||
686 | |||
687 | let mut hasher = DefaultHasher::new(); | ||
688 | x.hash(&mut hasher); | ||
689 | hasher.finish() | ||
690 | } | ||
691 | |||
692 | hash((name, shadow_count)) | ||
693 | } | ||
694 | } | ||
695 | |||
696 | fn is_child_of_impl(element: &SyntaxElement) -> bool { | ||
697 | match element.parent() { | ||
698 | Some(e) => e.kind() == IMPL, | ||
699 | _ => false, | ||
700 | } | ||
701 | } | ||
702 | |||
703 | fn highlight_name( | ||
704 | sema: &Semantics<RootDatabase>, | ||
705 | db: &RootDatabase, | ||
706 | def: Definition, | ||
707 | name_ref: Option<ast::NameRef>, | ||
708 | possibly_unsafe: bool, | ||
709 | ) -> Highlight { | ||
710 | match def { | ||
711 | Definition::Macro(_) => HighlightTag::Macro, | ||
712 | Definition::Field(field) => { | ||
713 | let mut h = HighlightTag::Field.into(); | ||
714 | if possibly_unsafe { | ||
715 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
716 | h |= HighlightModifier::Unsafe; | ||
717 | } | ||
718 | } | ||
719 | |||
720 | return h; | ||
721 | } | ||
722 | Definition::ModuleDef(def) => match def { | ||
723 | hir::ModuleDef::Module(_) => HighlightTag::Module, | ||
724 | hir::ModuleDef::Function(func) => { | ||
725 | let mut h = HighlightTag::Function.into(); | ||
726 | if func.is_unsafe(db) { | ||
727 | h |= HighlightModifier::Unsafe; | ||
728 | } else { | ||
729 | let is_unsafe = name_ref | ||
730 | .and_then(|name_ref| name_ref.syntax().parent()) | ||
731 | .and_then(ast::MethodCallExpr::cast) | ||
732 | .map(|method_call_expr| sema.is_unsafe_method_call(method_call_expr)) | ||
733 | .unwrap_or(false); | ||
734 | if is_unsafe { | ||
735 | h |= HighlightModifier::Unsafe; | ||
736 | } | ||
737 | } | ||
738 | return h; | ||
739 | } | ||
740 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, | ||
741 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, | ||
742 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, | ||
743 | hir::ModuleDef::EnumVariant(_) => HighlightTag::EnumVariant, | ||
744 | hir::ModuleDef::Const(_) => HighlightTag::Constant, | ||
745 | hir::ModuleDef::Trait(_) => HighlightTag::Trait, | ||
746 | hir::ModuleDef::TypeAlias(_) => HighlightTag::TypeAlias, | ||
747 | hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType, | ||
748 | hir::ModuleDef::Static(s) => { | ||
749 | let mut h = Highlight::new(HighlightTag::Static); | ||
750 | if s.is_mut(db) { | ||
751 | h |= HighlightModifier::Mutable; | ||
752 | h |= HighlightModifier::Unsafe; | ||
753 | } | ||
754 | return h; | ||
755 | } | ||
756 | }, | ||
757 | Definition::SelfType(_) => HighlightTag::SelfType, | ||
758 | Definition::TypeParam(_) => HighlightTag::TypeParam, | ||
759 | Definition::Local(local) => { | ||
760 | let tag = | ||
761 | if local.is_param(db) { HighlightTag::ValueParam } else { HighlightTag::Local }; | ||
762 | let mut h = Highlight::new(tag); | ||
763 | if local.is_mut(db) || local.ty(db).is_mutable_reference() { | ||
764 | h |= HighlightModifier::Mutable; | ||
765 | } | ||
766 | return h; | ||
767 | } | ||
768 | } | ||
769 | .into() | ||
770 | } | ||
771 | |||
772 | fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | ||
773 | let default = HighlightTag::UnresolvedReference; | ||
774 | |||
775 | let parent = match name.syntax().parent() { | ||
776 | Some(it) => it, | ||
777 | _ => return default.into(), | ||
778 | }; | ||
779 | |||
780 | let tag = match parent.kind() { | ||
781 | STRUCT => HighlightTag::Struct, | ||
782 | ENUM => HighlightTag::Enum, | ||
783 | UNION => HighlightTag::Union, | ||
784 | TRAIT => HighlightTag::Trait, | ||
785 | TYPE_ALIAS => HighlightTag::TypeAlias, | ||
786 | TYPE_PARAM => HighlightTag::TypeParam, | ||
787 | RECORD_FIELD => HighlightTag::Field, | ||
788 | MODULE => HighlightTag::Module, | ||
789 | FN => HighlightTag::Function, | ||
790 | CONST => HighlightTag::Constant, | ||
791 | STATIC => HighlightTag::Static, | ||
792 | VARIANT => HighlightTag::EnumVariant, | ||
793 | IDENT_PAT => HighlightTag::Local, | ||
794 | _ => default, | ||
795 | }; | ||
796 | |||
797 | tag.into() | ||
798 | } | ||
799 | |||
800 | fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight { | ||
801 | let default = HighlightTag::UnresolvedReference; | ||
802 | |||
803 | let parent = match name.syntax().parent() { | ||
804 | Some(it) => it, | ||
805 | _ => return default.into(), | ||
806 | }; | ||
807 | |||
808 | match parent.kind() { | ||
809 | METHOD_CALL_EXPR => { | ||
810 | let mut h = Highlight::new(HighlightTag::Function); | ||
811 | let is_unsafe = ast::MethodCallExpr::cast(parent) | ||
812 | .map(|method_call_expr| sema.is_unsafe_method_call(method_call_expr)) | ||
813 | .unwrap_or(false); | ||
814 | if is_unsafe { | ||
815 | h |= HighlightModifier::Unsafe; | ||
816 | } | ||
817 | |||
818 | h | ||
819 | } | ||
820 | FIELD_EXPR => { | ||
821 | let h = HighlightTag::Field; | ||
822 | let is_union = ast::FieldExpr::cast(parent) | ||
823 | .and_then(|field_expr| { | ||
824 | let field = sema.resolve_field(&field_expr)?; | ||
825 | Some(if let VariantDef::Union(_) = field.parent_def(sema.db) { | ||
826 | true | ||
827 | } else { | ||
828 | false | ||
829 | }) | ||
830 | }) | ||
831 | .unwrap_or(false); | ||
832 | if is_union { | ||
833 | h | HighlightModifier::Unsafe | ||
834 | } else { | ||
835 | h.into() | ||
836 | } | ||
837 | } | ||
838 | PATH_SEGMENT => { | ||
839 | let path = match parent.parent().and_then(ast::Path::cast) { | ||
840 | Some(it) => it, | ||
841 | _ => return default.into(), | ||
842 | }; | ||
843 | let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) { | ||
844 | Some(it) => it, | ||
845 | _ => { | ||
846 | // within path, decide whether it is module or adt by checking for uppercase name | ||
847 | return if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
848 | HighlightTag::Struct | ||
849 | } else { | ||
850 | HighlightTag::Module | ||
851 | } | ||
852 | .into(); | ||
853 | } | ||
854 | }; | ||
855 | let parent = match expr.syntax().parent() { | ||
856 | Some(it) => it, | ||
857 | None => return default.into(), | ||
858 | }; | ||
859 | |||
860 | match parent.kind() { | ||
861 | CALL_EXPR => HighlightTag::Function.into(), | ||
862 | _ => if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
863 | HighlightTag::Struct.into() | ||
864 | } else { | ||
865 | HighlightTag::Constant | ||
866 | } | ||
867 | .into(), | ||
868 | } | ||
869 | } | ||
870 | _ => default.into(), | ||
871 | } | ||
872 | } | ||