aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/syntax_highlighting.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting.rs')
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs256
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
3mod tags; 1mod tags;
4mod html; 2mod 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
36struct 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//
42impl 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
110pub(crate) fn highlight( 42pub(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)]
227struct HighlightedRangeStack {
228 stack: Vec<Vec<HighlightedRange>>,
229}
230
231/// We use a stack to implement the flattening logic for the highlighted
232/// syntax ranges.
233impl 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
281fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { 301fn 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
465fn is_child_of_impl(element: SyntaxElement) -> bool {
466 match element.parent() {
467 Some(e) => e.kind() == IMPL_DEF,
468 _ => false,
469 }
470}
471
422fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { 472fn 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
453fn highlight_name_by_syntax(name: ast::Name) -> Highlight { 515fn 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
473fn highlight_injection( 543fn highlight_injection(