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.rs223
1 files changed, 133 insertions, 90 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index be57eeb0a..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,
@@ -291,6 +223,81 @@ pub(crate) fn highlight(
291 stack.flattened() 223 stack.flattened()
292} 224}
293 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
294fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { 301fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
295 Some(match kind { 302 Some(match kind {
296 FormatSpecifier::Open 303 FormatSpecifier::Open
@@ -361,7 +368,9 @@ fn highlight_element(
361 } 368 }
362 369
363 // Highlight references like the definitions they resolve to 370 // Highlight references like the definitions they resolve to
364 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 }
365 NAME_REF => { 374 NAME_REF => {
366 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();
367 match classify_name_ref(sema, &name_ref) { 376 match classify_name_ref(sema, &name_ref) {
@@ -389,6 +398,7 @@ fn highlight_element(
389 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), 398 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
390 BYTE => HighlightTag::ByteLiteral.into(), 399 BYTE => HighlightTag::ByteLiteral.into(),
391 CHAR => HighlightTag::CharLiteral.into(), 400 CHAR => HighlightTag::CharLiteral.into(),
401 QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow,
392 LIFETIME => { 402 LIFETIME => {
393 let h = Highlight::new(HighlightTag::Lifetime); 403 let h = Highlight::new(HighlightTag::Lifetime);
394 match element.parent().map(|it| it.kind()) { 404 match element.parent().map(|it| it.kind()) {
@@ -396,6 +406,23 @@ fn highlight_element(
396 _ => h, 406 _ => h,
397 } 407 }
398 } 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 }
399 426
400 k if k.is_keyword() => { 427 k if k.is_keyword() => {
401 let h = Highlight::new(HighlightTag::Keyword); 428 let h = Highlight::new(HighlightTag::Keyword);
@@ -411,6 +438,8 @@ fn highlight_element(
411 | T![in] => h | HighlightModifier::ControlFlow, 438 | T![in] => h | HighlightModifier::ControlFlow,
412 T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow, 439 T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow,
413 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(),
414 _ => h, 443 _ => h,
415 } 444 }
416 } 445 }
@@ -446,7 +475,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
446 Definition::Field(_) => HighlightTag::Field, 475 Definition::Field(_) => HighlightTag::Field,
447 Definition::ModuleDef(def) => match def { 476 Definition::ModuleDef(def) => match def {
448 hir::ModuleDef::Module(_) => HighlightTag::Module, 477 hir::ModuleDef::Module(_) => HighlightTag::Module,
449 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 }
450 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, 485 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
451 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, 486 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum,
452 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, 487 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union,
@@ -478,23 +513,31 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
478} 513}
479 514
480fn highlight_name_by_syntax(name: ast::Name) -> Highlight { 515fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
481 let default = HighlightTag::Function.into(); 516 let default = HighlightTag::UnresolvedReference;
482 517
483 let parent = match name.syntax().parent() { 518 let parent = match name.syntax().parent() {
484 Some(it) => it, 519 Some(it) => it,
485 _ => return default, 520 _ => return default.into(),
486 }; 521 };
487 522
488 match parent.kind() { 523 let tag = match parent.kind() {
489 STRUCT_DEF => HighlightTag::Struct.into(), 524 STRUCT_DEF => HighlightTag::Struct,
490 ENUM_DEF => HighlightTag::Enum.into(), 525 ENUM_DEF => HighlightTag::Enum,
491 UNION_DEF => HighlightTag::Union.into(), 526 UNION_DEF => HighlightTag::Union,
492 TRAIT_DEF => HighlightTag::Trait.into(), 527 TRAIT_DEF => HighlightTag::Trait,
493 TYPE_ALIAS_DEF => HighlightTag::TypeAlias.into(), 528 TYPE_ALIAS_DEF => HighlightTag::TypeAlias,
494 TYPE_PARAM => HighlightTag::TypeParam.into(), 529 TYPE_PARAM => HighlightTag::TypeParam,
495 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,
496 _ => default, 537 _ => default,
497 } 538 };
539
540 tag.into()
498} 541}
499 542
500fn highlight_injection( 543fn highlight_injection(