diff options
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting.rs')
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting.rs | 256 |
1 files changed, 163 insertions, 93 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 6658c7bb2..19ecd54d6 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! Implements syntax highlighting. | ||
2 | |||
3 | mod tags; | 1 | mod tags; |
4 | mod html; | 2 | mod html; |
5 | #[cfg(test)] | 3 | #[cfg(test)] |
@@ -32,81 +30,15 @@ pub struct HighlightedRange { | |||
32 | pub binding_hash: Option<u64>, | 30 | pub binding_hash: Option<u64>, |
33 | } | 31 | } |
34 | 32 | ||
35 | #[derive(Debug)] | 33 | // Feature: Semantic Syntax Highlighting |
36 | struct HighlightedRangeStack { | 34 | // |
37 | stack: Vec<Vec<HighlightedRange>>, | 35 | // rust-analyzer highlights the code semantically. |
38 | } | 36 | // For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait. |
39 | 37 | // rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token. | |
40 | /// We use a stack to implement the flattening logic for the highlighted | 38 | // It's up to the client to map those to specific colors. |
41 | /// syntax ranges. | 39 | // |
42 | impl HighlightedRangeStack { | 40 | // The general rule is that a reference to an entity gets colored the same way as the entity itself. |
43 | fn new() -> Self { | 41 | // We also give special modifier for `mut` and `&mut` local variables. |
44 | Self { stack: vec![Vec::new()] } | ||
45 | } | ||
46 | |||
47 | fn push(&mut self) { | ||
48 | self.stack.push(Vec::new()); | ||
49 | } | ||
50 | |||
51 | /// Flattens the highlighted ranges. | ||
52 | /// | ||
53 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
54 | /// 1) parent-range: Attribute [0, 23) | ||
55 | /// 2) child-range: String [16, 21) | ||
56 | /// | ||
57 | /// The following code implements the flattening, for our example this results to: | ||
58 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
59 | fn pop(&mut self) { | ||
60 | let children = self.stack.pop().unwrap(); | ||
61 | let prev = self.stack.last_mut().unwrap(); | ||
62 | let needs_flattening = !children.is_empty() | ||
63 | && !prev.is_empty() | ||
64 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
65 | if !needs_flattening { | ||
66 | prev.extend(children); | ||
67 | } else { | ||
68 | let mut parent = prev.pop().unwrap(); | ||
69 | for ele in children { | ||
70 | assert!(parent.range.contains_range(ele.range)); | ||
71 | let mut cloned = parent.clone(); | ||
72 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | ||
73 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
74 | if !parent.range.is_empty() { | ||
75 | prev.push(parent); | ||
76 | } | ||
77 | prev.push(ele); | ||
78 | parent = cloned; | ||
79 | } | ||
80 | if !parent.range.is_empty() { | ||
81 | prev.push(parent); | ||
82 | } | ||
83 | } | ||
84 | } | ||
85 | |||
86 | fn add(&mut self, range: HighlightedRange) { | ||
87 | self.stack | ||
88 | .last_mut() | ||
89 | .expect("during DFS traversal, the stack must not be empty") | ||
90 | .push(range) | ||
91 | } | ||
92 | |||
93 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
94 | assert_eq!( | ||
95 | self.stack.len(), | ||
96 | 1, | ||
97 | "after DFS traversal, the stack should only contain a single element" | ||
98 | ); | ||
99 | let mut res = self.stack.pop().unwrap(); | ||
100 | res.sort_by_key(|range| range.range.start()); | ||
101 | // Check that ranges are sorted and disjoint | ||
102 | assert!(res | ||
103 | .iter() | ||
104 | .zip(res.iter().skip(1)) | ||
105 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
106 | res | ||
107 | } | ||
108 | } | ||
109 | |||
110 | pub(crate) fn highlight( | 42 | pub(crate) fn highlight( |
111 | db: &RootDatabase, | 43 | db: &RootDatabase, |
112 | file_id: FileId, | 44 | file_id: FileId, |
@@ -167,6 +99,19 @@ pub(crate) fn highlight( | |||
167 | binding_hash: None, | 99 | binding_hash: None, |
168 | }); | 100 | }); |
169 | } | 101 | } |
102 | if let Some(name) = mc.is_macro_rules() { | ||
103 | if let Some((highlight, binding_hash)) = highlight_element( | ||
104 | &sema, | ||
105 | &mut bindings_shadow_count, | ||
106 | name.syntax().clone().into(), | ||
107 | ) { | ||
108 | stack.add(HighlightedRange { | ||
109 | range: name.syntax().text_range(), | ||
110 | highlight, | ||
111 | binding_hash, | ||
112 | }); | ||
113 | } | ||
114 | } | ||
170 | continue; | 115 | continue; |
171 | } | 116 | } |
172 | WalkEvent::Leave(Some(mc)) => { | 117 | WalkEvent::Leave(Some(mc)) => { |
@@ -278,6 +223,81 @@ pub(crate) fn highlight( | |||
278 | stack.flattened() | 223 | stack.flattened() |
279 | } | 224 | } |
280 | 225 | ||
226 | #[derive(Debug)] | ||
227 | struct HighlightedRangeStack { | ||
228 | stack: Vec<Vec<HighlightedRange>>, | ||
229 | } | ||
230 | |||
231 | /// We use a stack to implement the flattening logic for the highlighted | ||
232 | /// syntax ranges. | ||
233 | impl HighlightedRangeStack { | ||
234 | fn new() -> Self { | ||
235 | Self { stack: vec![Vec::new()] } | ||
236 | } | ||
237 | |||
238 | fn push(&mut self) { | ||
239 | self.stack.push(Vec::new()); | ||
240 | } | ||
241 | |||
242 | /// Flattens the highlighted ranges. | ||
243 | /// | ||
244 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
245 | /// 1) parent-range: Attribute [0, 23) | ||
246 | /// 2) child-range: String [16, 21) | ||
247 | /// | ||
248 | /// The following code implements the flattening, for our example this results to: | ||
249 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
250 | fn pop(&mut self) { | ||
251 | let children = self.stack.pop().unwrap(); | ||
252 | let prev = self.stack.last_mut().unwrap(); | ||
253 | let needs_flattening = !children.is_empty() | ||
254 | && !prev.is_empty() | ||
255 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
256 | if !needs_flattening { | ||
257 | prev.extend(children); | ||
258 | } else { | ||
259 | let mut parent = prev.pop().unwrap(); | ||
260 | for ele in children { | ||
261 | assert!(parent.range.contains_range(ele.range)); | ||
262 | let mut cloned = parent.clone(); | ||
263 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | ||
264 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
265 | if !parent.range.is_empty() { | ||
266 | prev.push(parent); | ||
267 | } | ||
268 | prev.push(ele); | ||
269 | parent = cloned; | ||
270 | } | ||
271 | if !parent.range.is_empty() { | ||
272 | prev.push(parent); | ||
273 | } | ||
274 | } | ||
275 | } | ||
276 | |||
277 | fn add(&mut self, range: HighlightedRange) { | ||
278 | self.stack | ||
279 | .last_mut() | ||
280 | .expect("during DFS traversal, the stack must not be empty") | ||
281 | .push(range) | ||
282 | } | ||
283 | |||
284 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
285 | assert_eq!( | ||
286 | self.stack.len(), | ||
287 | 1, | ||
288 | "after DFS traversal, the stack should only contain a single element" | ||
289 | ); | ||
290 | let mut res = self.stack.pop().unwrap(); | ||
291 | res.sort_by_key(|range| range.range.start()); | ||
292 | // Check that ranges are sorted and disjoint | ||
293 | assert!(res | ||
294 | .iter() | ||
295 | .zip(res.iter().skip(1)) | ||
296 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
297 | res | ||
298 | } | ||
299 | } | ||
300 | |||
281 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | 301 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { |
282 | Some(match kind { | 302 | Some(match kind { |
283 | FormatSpecifier::Open | 303 | FormatSpecifier::Open |
@@ -348,7 +368,9 @@ fn highlight_element( | |||
348 | } | 368 | } |
349 | 369 | ||
350 | // Highlight references like the definitions they resolve to | 370 | // Highlight references like the definitions they resolve to |
351 | NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => return None, | 371 | NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => { |
372 | Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute | ||
373 | } | ||
352 | NAME_REF => { | 374 | NAME_REF => { |
353 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | 375 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); |
354 | match classify_name_ref(sema, &name_ref) { | 376 | match classify_name_ref(sema, &name_ref) { |
@@ -376,6 +398,7 @@ fn highlight_element( | |||
376 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), | 398 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), |
377 | BYTE => HighlightTag::ByteLiteral.into(), | 399 | BYTE => HighlightTag::ByteLiteral.into(), |
378 | CHAR => HighlightTag::CharLiteral.into(), | 400 | CHAR => HighlightTag::CharLiteral.into(), |
401 | QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow, | ||
379 | LIFETIME => { | 402 | LIFETIME => { |
380 | let h = Highlight::new(HighlightTag::Lifetime); | 403 | let h = Highlight::new(HighlightTag::Lifetime); |
381 | match element.parent().map(|it| it.kind()) { | 404 | match element.parent().map(|it| it.kind()) { |
@@ -383,6 +406,23 @@ fn highlight_element( | |||
383 | _ => h, | 406 | _ => h, |
384 | } | 407 | } |
385 | } | 408 | } |
409 | PREFIX_EXPR => { | ||
410 | let prefix_expr = element.into_node().and_then(ast::PrefixExpr::cast)?; | ||
411 | match prefix_expr.op_kind() { | ||
412 | Some(ast::PrefixOp::Deref) => {} | ||
413 | _ => return None, | ||
414 | } | ||
415 | |||
416 | let expr = prefix_expr.expr()?; | ||
417 | let ty = sema.type_of_expr(&expr)?; | ||
418 | if !ty.is_raw_ptr() { | ||
419 | return None; | ||
420 | } | ||
421 | |||
422 | let mut h = Highlight::new(HighlightTag::Operator); | ||
423 | h |= HighlightModifier::Unsafe; | ||
424 | h | ||
425 | } | ||
386 | 426 | ||
387 | k if k.is_keyword() => { | 427 | k if k.is_keyword() => { |
388 | let h = Highlight::new(HighlightTag::Keyword); | 428 | let h = Highlight::new(HighlightTag::Keyword); |
@@ -390,13 +430,16 @@ fn highlight_element( | |||
390 | T![break] | 430 | T![break] |
391 | | T![continue] | 431 | | T![continue] |
392 | | T![else] | 432 | | T![else] |
393 | | T![for] | ||
394 | | T![if] | 433 | | T![if] |
395 | | T![loop] | 434 | | T![loop] |
396 | | T![match] | 435 | | T![match] |
397 | | T![return] | 436 | | T![return] |
398 | | T![while] => h | HighlightModifier::ControlFlow, | 437 | | T![while] |
438 | | T![in] => h | HighlightModifier::ControlFlow, | ||
439 | T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow, | ||
399 | T![unsafe] => h | HighlightModifier::Unsafe, | 440 | T![unsafe] => h | HighlightModifier::Unsafe, |
441 | T![true] | T![false] => HighlightTag::BoolLiteral.into(), | ||
442 | T![self] => HighlightTag::SelfKeyword.into(), | ||
400 | _ => h, | 443 | _ => h, |
401 | } | 444 | } |
402 | } | 445 | } |
@@ -419,22 +462,41 @@ fn highlight_element( | |||
419 | } | 462 | } |
420 | } | 463 | } |
421 | 464 | ||
465 | fn is_child_of_impl(element: SyntaxElement) -> bool { | ||
466 | match element.parent() { | ||
467 | Some(e) => e.kind() == IMPL_DEF, | ||
468 | _ => false, | ||
469 | } | ||
470 | } | ||
471 | |||
422 | fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | 472 | fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { |
423 | match def { | 473 | match def { |
424 | Definition::Macro(_) => HighlightTag::Macro, | 474 | Definition::Macro(_) => HighlightTag::Macro, |
425 | Definition::Field(_) => HighlightTag::Field, | 475 | Definition::Field(_) => HighlightTag::Field, |
426 | Definition::ModuleDef(def) => match def { | 476 | Definition::ModuleDef(def) => match def { |
427 | hir::ModuleDef::Module(_) => HighlightTag::Module, | 477 | hir::ModuleDef::Module(_) => HighlightTag::Module, |
428 | hir::ModuleDef::Function(_) => HighlightTag::Function, | 478 | hir::ModuleDef::Function(func) => { |
479 | let mut h = HighlightTag::Function.into(); | ||
480 | if func.is_unsafe(db) { | ||
481 | h |= HighlightModifier::Unsafe; | ||
482 | } | ||
483 | return h; | ||
484 | } | ||
429 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, | 485 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, |
430 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, | 486 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, |
431 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, | 487 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, |
432 | hir::ModuleDef::EnumVariant(_) => HighlightTag::EnumVariant, | 488 | hir::ModuleDef::EnumVariant(_) => HighlightTag::EnumVariant, |
433 | hir::ModuleDef::Const(_) => HighlightTag::Constant, | 489 | hir::ModuleDef::Const(_) => HighlightTag::Constant, |
434 | hir::ModuleDef::Static(_) => HighlightTag::Static, | ||
435 | hir::ModuleDef::Trait(_) => HighlightTag::Trait, | 490 | hir::ModuleDef::Trait(_) => HighlightTag::Trait, |
436 | hir::ModuleDef::TypeAlias(_) => HighlightTag::TypeAlias, | 491 | hir::ModuleDef::TypeAlias(_) => HighlightTag::TypeAlias, |
437 | hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType, | 492 | hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType, |
493 | hir::ModuleDef::Static(s) => { | ||
494 | let mut h = Highlight::new(HighlightTag::Static); | ||
495 | if s.is_mut(db) { | ||
496 | h |= HighlightModifier::Mutable; | ||
497 | } | ||
498 | return h; | ||
499 | } | ||
438 | }, | 500 | }, |
439 | Definition::SelfType(_) => HighlightTag::SelfType, | 501 | Definition::SelfType(_) => HighlightTag::SelfType, |
440 | Definition::TypeParam(_) => HighlightTag::TypeParam, | 502 | Definition::TypeParam(_) => HighlightTag::TypeParam, |
@@ -451,23 +513,31 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | |||
451 | } | 513 | } |
452 | 514 | ||
453 | fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | 515 | fn highlight_name_by_syntax(name: ast::Name) -> Highlight { |
454 | let default = HighlightTag::Function.into(); | 516 | let default = HighlightTag::UnresolvedReference; |
455 | 517 | ||
456 | let parent = match name.syntax().parent() { | 518 | let parent = match name.syntax().parent() { |
457 | Some(it) => it, | 519 | Some(it) => it, |
458 | _ => return default, | 520 | _ => return default.into(), |
459 | }; | 521 | }; |
460 | 522 | ||
461 | match parent.kind() { | 523 | let tag = match parent.kind() { |
462 | STRUCT_DEF => HighlightTag::Struct.into(), | 524 | STRUCT_DEF => HighlightTag::Struct, |
463 | ENUM_DEF => HighlightTag::Enum.into(), | 525 | ENUM_DEF => HighlightTag::Enum, |
464 | UNION_DEF => HighlightTag::Union.into(), | 526 | UNION_DEF => HighlightTag::Union, |
465 | TRAIT_DEF => HighlightTag::Trait.into(), | 527 | TRAIT_DEF => HighlightTag::Trait, |
466 | TYPE_ALIAS_DEF => HighlightTag::TypeAlias.into(), | 528 | TYPE_ALIAS_DEF => HighlightTag::TypeAlias, |
467 | TYPE_PARAM => HighlightTag::TypeParam.into(), | 529 | TYPE_PARAM => HighlightTag::TypeParam, |
468 | RECORD_FIELD_DEF => HighlightTag::Field.into(), | 530 | RECORD_FIELD_DEF => HighlightTag::Field, |
531 | MODULE => HighlightTag::Module, | ||
532 | FN_DEF => HighlightTag::Function, | ||
533 | CONST_DEF => HighlightTag::Constant, | ||
534 | STATIC_DEF => HighlightTag::Static, | ||
535 | ENUM_VARIANT => HighlightTag::EnumVariant, | ||
536 | BIND_PAT => HighlightTag::Local, | ||
469 | _ => default, | 537 | _ => default, |
470 | } | 538 | }; |
539 | |||
540 | tag.into() | ||
471 | } | 541 | } |
472 | 542 | ||
473 | fn highlight_injection( | 543 | fn highlight_injection( |