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.rs333
1 files changed, 260 insertions, 73 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 0b53ebe69..6ac44c2c0 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -1,5 +1,6 @@
1mod tags; 1mod tags;
2mod html; 2mod html;
3mod injection;
3#[cfg(test)] 4#[cfg(test)]
4mod tests; 5mod tests;
5 6
@@ -10,14 +11,14 @@ use ra_ide_db::{
10}; 11};
11use ra_prof::profile; 12use ra_prof::profile;
12use ra_syntax::{ 13use ra_syntax::{
13 ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue}, 14 ast::{self, HasFormatSpecifier},
14 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, 15 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
15 SyntaxKind::*, 16 SyntaxKind::*,
16 SyntaxToken, TextRange, WalkEvent, T, 17 TextRange, WalkEvent, T,
17}; 18};
18use rustc_hash::FxHashMap; 19use rustc_hash::FxHashMap;
19 20
20use crate::{call_info::ActiveParameter, Analysis, FileId}; 21use crate::FileId;
21 22
22use ast::FormatSpecifier; 23use ast::FormatSpecifier;
23pub(crate) use html::highlight_as_html; 24pub(crate) use html::highlight_as_html;
@@ -43,6 +44,7 @@ pub(crate) fn highlight(
43 db: &RootDatabase, 44 db: &RootDatabase,
44 file_id: FileId, 45 file_id: FileId,
45 range_to_highlight: Option<TextRange>, 46 range_to_highlight: Option<TextRange>,
47 syntactic_name_ref_highlighting: bool,
46) -> Vec<HighlightedRange> { 48) -> Vec<HighlightedRange> {
47 let _p = profile("highlight"); 49 let _p = profile("highlight");
48 let sema = Semantics::new(db); 50 let sema = Semantics::new(db);
@@ -103,6 +105,7 @@ pub(crate) fn highlight(
103 if let Some((highlight, binding_hash)) = highlight_element( 105 if let Some((highlight, binding_hash)) = highlight_element(
104 &sema, 106 &sema,
105 &mut bindings_shadow_count, 107 &mut bindings_shadow_count,
108 syntactic_name_ref_highlighting,
106 name.syntax().clone().into(), 109 name.syntax().clone().into(),
107 ) { 110 ) {
108 stack.add(HighlightedRange { 111 stack.add(HighlightedRange {
@@ -118,7 +121,23 @@ pub(crate) fn highlight(
118 assert!(current_macro_call == Some(mc)); 121 assert!(current_macro_call == Some(mc));
119 current_macro_call = None; 122 current_macro_call = None;
120 format_string = None; 123 format_string = None;
121 continue; 124 }
125 _ => (),
126 }
127
128 // Check for Rust code in documentation
129 match &event {
130 WalkEvent::Leave(NodeOrToken::Node(node)) => {
131 if let Some((doctest, range_mapping, new_comments)) =
132 injection::extract_doc_comments(node)
133 {
134 injection::highlight_doc_comment(
135 doctest,
136 range_mapping,
137 new_comments,
138 &mut stack,
139 );
140 }
122 } 141 }
123 _ => (), 142 _ => (),
124 } 143 }
@@ -130,7 +149,7 @@ pub(crate) fn highlight(
130 149
131 let range = element.text_range(); 150 let range = element.text_range();
132 151
133 let element_to_highlight = if current_macro_call.is_some() { 152 let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT {
134 // Inside a macro -- expand it first 153 // Inside a macro -- expand it first
135 let token = match element.clone().into_token() { 154 let token = match element.clone().into_token() {
136 Some(it) if it.parent().kind() == TOKEN_TREE => it, 155 Some(it) if it.parent().kind() == TOKEN_TREE => it,
@@ -142,23 +161,25 @@ pub(crate) fn highlight(
142 // Check if macro takes a format string and remember it for highlighting later. 161 // Check if macro takes a format string and remember it for highlighting later.
143 // The macros that accept a format string expand to a compiler builtin macros 162 // The macros that accept a format string expand to a compiler builtin macros
144 // `format_args` and `format_args_nl`. 163 // `format_args` and `format_args_nl`.
145 if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) { 164 if let Some(name) = parent
146 if let Some(name) = 165 .parent()
147 fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref()) 166 .and_then(ast::MacroCall::cast)
148 { 167 .and_then(|mc| mc.path())
149 match name.text().as_str() { 168 .and_then(|p| p.segment())
150 "format_args" | "format_args_nl" => { 169 .and_then(|s| s.name_ref())
151 format_string = parent 170 {
152 .children_with_tokens() 171 match name.text().as_str() {
153 .filter(|t| t.kind() != WHITESPACE) 172 "format_args" | "format_args_nl" => {
154 .nth(1) 173 format_string = parent
155 .filter(|e| { 174 .children_with_tokens()
156 ast::String::can_cast(e.kind()) 175 .filter(|t| t.kind() != WHITESPACE)
157 || ast::RawString::can_cast(e.kind()) 176 .nth(1)
158 }) 177 .filter(|e| {
159 } 178 ast::String::can_cast(e.kind())
160 _ => {} 179 || ast::RawString::can_cast(e.kind())
180 })
161 } 181 }
182 _ => {}
162 } 183 }
163 } 184 }
164 185
@@ -173,22 +194,25 @@ pub(crate) fn highlight(
173 194
174 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { 195 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
175 let expanded = element_to_highlight.as_token().unwrap().clone(); 196 let expanded = element_to_highlight.as_token().unwrap().clone();
176 if highlight_injection(&mut stack, &sema, token, expanded).is_some() { 197 if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() {
177 continue; 198 continue;
178 } 199 }
179 } 200 }
180 201
181 let is_format_string = format_string.as_ref() == Some(&element_to_highlight); 202 let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
182 203
183 if let Some((highlight, binding_hash)) = 204 if let Some((highlight, binding_hash)) = highlight_element(
184 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone()) 205 &sema,
185 { 206 &mut bindings_shadow_count,
207 syntactic_name_ref_highlighting,
208 element_to_highlight.clone(),
209 ) {
186 stack.add(HighlightedRange { range, highlight, binding_hash }); 210 stack.add(HighlightedRange { range, highlight, binding_hash });
187 if let Some(string) = 211 if let Some(string) =
188 element_to_highlight.as_token().cloned().and_then(ast::String::cast) 212 element_to_highlight.as_token().cloned().and_then(ast::String::cast)
189 { 213 {
190 stack.push();
191 if is_format_string { 214 if is_format_string {
215 stack.push();
192 string.lex_format_specifier(|piece_range, kind| { 216 string.lex_format_specifier(|piece_range, kind| {
193 if let Some(highlight) = highlight_format_specifier(kind) { 217 if let Some(highlight) = highlight_format_specifier(kind) {
194 stack.add(HighlightedRange { 218 stack.add(HighlightedRange {
@@ -198,13 +222,27 @@ pub(crate) fn highlight(
198 }); 222 });
199 } 223 }
200 }); 224 });
225 stack.pop();
226 }
227 // Highlight escape sequences
228 if let Some(char_ranges) = string.char_ranges() {
229 stack.push();
230 for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
231 if string.text()[piece_range.start().into()..].starts_with('\\') {
232 stack.add(HighlightedRange {
233 range: piece_range + range.start(),
234 highlight: HighlightTag::EscapeSequence.into(),
235 binding_hash: None,
236 });
237 }
238 }
239 stack.pop_and_inject(None);
201 } 240 }
202 stack.pop();
203 } else if let Some(string) = 241 } else if let Some(string) =
204 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) 242 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
205 { 243 {
206 stack.push();
207 if is_format_string { 244 if is_format_string {
245 stack.push();
208 string.lex_format_specifier(|piece_range, kind| { 246 string.lex_format_specifier(|piece_range, kind| {
209 if let Some(highlight) = highlight_format_specifier(kind) { 247 if let Some(highlight) = highlight_format_specifier(kind) {
210 stack.add(HighlightedRange { 248 stack.add(HighlightedRange {
@@ -214,8 +252,8 @@ pub(crate) fn highlight(
214 }); 252 });
215 } 253 }
216 }); 254 });
255 stack.pop();
217 } 256 }
218 stack.pop();
219 } 257 }
220 } 258 }
221 } 259 }
@@ -259,9 +297,8 @@ impl HighlightedRangeStack {
259 let mut parent = prev.pop().unwrap(); 297 let mut parent = prev.pop().unwrap();
260 for ele in children { 298 for ele in children {
261 assert!(parent.range.contains_range(ele.range)); 299 assert!(parent.range.contains_range(ele.range));
262 let mut cloned = parent.clone(); 300
263 parent.range = TextRange::new(parent.range.start(), ele.range.start()); 301 let cloned = Self::intersect(&mut parent, &ele);
264 cloned.range = TextRange::new(ele.range.end(), cloned.range.end());
265 if !parent.range.is_empty() { 302 if !parent.range.is_empty() {
266 prev.push(parent); 303 prev.push(parent);
267 } 304 }
@@ -274,6 +311,92 @@ impl HighlightedRangeStack {
274 } 311 }
275 } 312 }
276 313
314 /// Intersects the `HighlightedRange` `parent` with `child`.
315 /// `parent` is mutated in place, becoming the range before `child`.
316 /// Returns the range (of the same type as `parent`) *after* `child`.
317 fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange {
318 assert!(parent.range.contains_range(child.range));
319
320 let mut cloned = parent.clone();
321 parent.range = TextRange::new(parent.range.start(), child.range.start());
322 cloned.range = TextRange::new(child.range.end(), cloned.range.end());
323
324 cloned
325 }
326
327 /// Remove the `HighlightRange` of `parent` that's currently covered by `child`.
328 fn intersect_partial(parent: &mut HighlightedRange, child: &HighlightedRange) {
329 assert!(
330 parent.range.start() <= child.range.start()
331 && parent.range.end() >= child.range.start()
332 && child.range.end() > parent.range.end()
333 );
334
335 parent.range = TextRange::new(parent.range.start(), child.range.start());
336 }
337
338 /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`)
339 /// can only modify the last range currently on the stack.
340 /// Can be used to do injections that span multiple ranges, like the
341 /// doctest injection below.
342 /// If `overwrite_parent` is non-optional, the highlighting of the parent range
343 /// is overwritten with the argument.
344 ///
345 /// Note that `pop` can be simulated by `pop_and_inject(false)` but the
346 /// latter is computationally more expensive.
347 fn pop_and_inject(&mut self, overwrite_parent: Option<Highlight>) {
348 let mut children = self.stack.pop().unwrap();
349 let prev = self.stack.last_mut().unwrap();
350 children.sort_by_key(|range| range.range.start());
351 prev.sort_by_key(|range| range.range.start());
352
353 for child in children {
354 if let Some(idx) =
355 prev.iter().position(|parent| parent.range.contains_range(child.range))
356 {
357 if let Some(tag) = overwrite_parent {
358 prev[idx].highlight = tag;
359 }
360
361 let cloned = Self::intersect(&mut prev[idx], &child);
362 let insert_idx = if prev[idx].range.is_empty() {
363 prev.remove(idx);
364 idx
365 } else {
366 idx + 1
367 };
368 prev.insert(insert_idx, child);
369 if !cloned.range.is_empty() {
370 prev.insert(insert_idx + 1, cloned);
371 }
372 } else {
373 let maybe_idx =
374 prev.iter().position(|parent| parent.range.contains(child.range.start()));
375 match (overwrite_parent, maybe_idx) {
376 (Some(_), Some(idx)) => {
377 Self::intersect_partial(&mut prev[idx], &child);
378 let insert_idx = if prev[idx].range.is_empty() {
379 prev.remove(idx);
380 idx
381 } else {
382 idx + 1
383 };
384 prev.insert(insert_idx, child);
385 }
386 (_, None) => {
387 let idx = prev
388 .binary_search_by_key(&child.range.start(), |range| range.range.start())
389 .unwrap_or_else(|x| x);
390 prev.insert(idx, child);
391 }
392 _ => {
393 unreachable!("child range should be completely contained in parent range");
394 }
395 }
396 }
397 }
398 }
399
277 fn add(&mut self, range: HighlightedRange) { 400 fn add(&mut self, range: HighlightedRange) {
278 self.stack 401 self.stack
279 .last_mut() 402 .last_mut()
@@ -335,6 +458,7 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
335fn highlight_element( 458fn highlight_element(
336 sema: &Semantics<RootDatabase>, 459 sema: &Semantics<RootDatabase>,
337 bindings_shadow_count: &mut FxHashMap<Name, u32>, 460 bindings_shadow_count: &mut FxHashMap<Name, u32>,
461 syntactic_name_ref_highlighting: bool,
338 element: SyntaxElement, 462 element: SyntaxElement,
339) -> Option<(Highlight, Option<u64>)> { 463) -> Option<(Highlight, Option<u64>)> {
340 let db = sema.db; 464 let db = sema.db;
@@ -363,6 +487,7 @@ fn highlight_element(
363 highlight_name(db, def) | HighlightModifier::Definition 487 highlight_name(db, def) | HighlightModifier::Definition
364 } 488 }
365 Some(NameClass::ConstReference(def)) => highlight_name(db, def), 489 Some(NameClass::ConstReference(def)) => highlight_name(db, def),
490 Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(),
366 None => highlight_name_by_syntax(name) | HighlightModifier::Definition, 491 None => highlight_name_by_syntax(name) | HighlightModifier::Definition,
367 } 492 }
368 } 493 }
@@ -387,12 +512,20 @@ fn highlight_element(
387 } 512 }
388 NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), 513 NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(),
389 }, 514 },
515 None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref),
390 None => HighlightTag::UnresolvedReference.into(), 516 None => HighlightTag::UnresolvedReference.into(),
391 } 517 }
392 } 518 }
393 519
394 // Simple token-based highlighting 520 // Simple token-based highlighting
395 COMMENT => HighlightTag::Comment.into(), 521 COMMENT => {
522 let comment = element.into_token().and_then(ast::Comment::cast)?;
523 let h = HighlightTag::Comment;
524 match comment.kind().doc {
525 Some(_) => h | HighlightModifier::Documentation,
526 None => h.into(),
527 }
528 }
396 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(), 529 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
397 ATTR => HighlightTag::Attribute.into(), 530 ATTR => HighlightTag::Attribute.into(),
398 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), 531 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
@@ -406,6 +539,21 @@ fn highlight_element(
406 _ => h, 539 _ => h,
407 } 540 }
408 } 541 }
542 T![*] => {
543 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
544
545 let expr = prefix_expr.expr()?;
546 let ty = sema.type_of_expr(&expr)?;
547 if !ty.is_raw_ptr() {
548 return None;
549 } else {
550 HighlightTag::Operator | HighlightModifier::Unsafe
551 }
552 }
553 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
554 Highlight::new(HighlightTag::Macro)
555 }
556 p if p.is_punct() => HighlightTag::Punctuation.into(),
409 557
410 k if k.is_keyword() => { 558 k if k.is_keyword() => {
411 let h = Highlight::new(HighlightTag::Keyword); 559 let h = Highlight::new(HighlightTag::Keyword);
@@ -419,10 +567,31 @@ fn highlight_element(
419 | T![return] 567 | T![return]
420 | T![while] 568 | T![while]
421 | T![in] => h | HighlightModifier::ControlFlow, 569 | T![in] => h | HighlightModifier::ControlFlow,
422 T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow, 570 T![for] if !is_child_of_impl(&element) => h | HighlightModifier::ControlFlow,
423 T![unsafe] => h | HighlightModifier::Unsafe, 571 T![unsafe] => h | HighlightModifier::Unsafe,
424 T![true] | T![false] => HighlightTag::BoolLiteral.into(), 572 T![true] | T![false] => HighlightTag::BoolLiteral.into(),
425 T![self] => HighlightTag::SelfKeyword.into(), 573 T![self] => {
574 let self_param_is_mut = element
575 .parent()
576 .and_then(ast::SelfParam::cast)
577 .and_then(|p| p.mut_token())
578 .is_some();
579 // closure to enforce lazyness
580 let self_path = || {
581 sema.resolve_path(&element.parent()?.parent().and_then(ast::Path::cast)?)
582 };
583 if self_param_is_mut
584 || matches!(self_path(),
585 Some(hir::PathResolution::Local(local))
586 if local.is_self(db)
587 && (local.is_mut(db) || local.ty(db).is_mutable_reference())
588 )
589 {
590 HighlightTag::SelfKeyword | HighlightModifier::Mutable
591 } else {
592 HighlightTag::SelfKeyword.into()
593 }
594 }
426 _ => h, 595 _ => h,
427 } 596 }
428 } 597 }
@@ -445,7 +614,7 @@ fn highlight_element(
445 } 614 }
446} 615}
447 616
448fn is_child_of_impl(element: SyntaxElement) -> bool { 617fn is_child_of_impl(element: &SyntaxElement) -> bool {
449 match element.parent() { 618 match element.parent() {
450 Some(e) => e.kind() == IMPL_DEF, 619 Some(e) => e.kind() == IMPL_DEF,
451 _ => false, 620 _ => false,
@@ -458,7 +627,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
458 Definition::Field(_) => HighlightTag::Field, 627 Definition::Field(_) => HighlightTag::Field,
459 Definition::ModuleDef(def) => match def { 628 Definition::ModuleDef(def) => match def {
460 hir::ModuleDef::Module(_) => HighlightTag::Module, 629 hir::ModuleDef::Module(_) => HighlightTag::Module,
461 hir::ModuleDef::Function(_) => HighlightTag::Function, 630 hir::ModuleDef::Function(func) => {
631 let mut h = HighlightTag::Function.into();
632 if func.is_unsafe(db) {
633 h |= HighlightModifier::Unsafe;
634 }
635 return h;
636 }
462 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, 637 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
463 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, 638 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum,
464 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, 639 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union,
@@ -477,9 +652,10 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
477 }, 652 },
478 Definition::SelfType(_) => HighlightTag::SelfType, 653 Definition::SelfType(_) => HighlightTag::SelfType,
479 Definition::TypeParam(_) => HighlightTag::TypeParam, 654 Definition::TypeParam(_) => HighlightTag::TypeParam,
480 // FIXME: distinguish between locals and parameters
481 Definition::Local(local) => { 655 Definition::Local(local) => {
482 let mut h = Highlight::new(HighlightTag::Local); 656 let tag =
657 if local.is_param(db) { HighlightTag::ValueParam } else { HighlightTag::Local };
658 let mut h = Highlight::new(tag);
483 if local.is_mut(db) || local.ty(db).is_mutable_reference() { 659 if local.is_mut(db) || local.ty(db).is_mutable_reference() {
484 h |= HighlightModifier::Mutable; 660 h |= HighlightModifier::Mutable;
485 } 661 }
@@ -517,41 +693,52 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
517 tag.into() 693 tag.into()
518} 694}
519 695
520fn highlight_injection( 696fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight {
521 acc: &mut HighlightedRangeStack, 697 let default = HighlightTag::UnresolvedReference;
522 sema: &Semantics<RootDatabase>,
523 literal: ast::RawString,
524 expanded: SyntaxToken,
525) -> Option<()> {
526 let active_parameter = ActiveParameter::at_token(&sema, expanded)?;
527 if !active_parameter.name.starts_with("ra_fixture") {
528 return None;
529 }
530 let value = literal.value()?;
531 let (analysis, tmp_file_id) = Analysis::from_single_file(value);
532
533 if let Some(range) = literal.open_quote_text_range() {
534 acc.add(HighlightedRange {
535 range,
536 highlight: HighlightTag::StringLiteral.into(),
537 binding_hash: None,
538 })
539 }
540 698
541 for mut h in analysis.highlight(tmp_file_id).unwrap() { 699 let parent = match name.syntax().parent() {
542 if let Some(r) = literal.map_range_up(h.range) { 700 Some(it) => it,
543 h.range = r; 701 _ => return default.into(),
544 acc.add(h) 702 };
545 }
546 }
547 703
548 if let Some(range) = literal.close_quote_text_range() { 704 let tag = match parent.kind() {
549 acc.add(HighlightedRange { 705 METHOD_CALL_EXPR => HighlightTag::Function,
550 range, 706 FIELD_EXPR => HighlightTag::Field,
551 highlight: HighlightTag::StringLiteral.into(), 707 PATH_SEGMENT => {
552 binding_hash: None, 708 let path = match parent.parent().and_then(ast::Path::cast) {
553 }) 709 Some(it) => it,
554 } 710 _ => return default.into(),
711 };
712 let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
713 Some(it) => it,
714 _ => {
715 // within path, decide whether it is module or adt by checking for uppercase name
716 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
717 HighlightTag::Struct
718 } else {
719 HighlightTag::Module
720 }
721 .into();
722 }
723 };
724 let parent = match expr.syntax().parent() {
725 Some(it) => it,
726 None => return default.into(),
727 };
555 728
556 Some(()) 729 match parent.kind() {
730 CALL_EXPR => HighlightTag::Function,
731 _ => {
732 if name.text().chars().next().unwrap_or_default().is_uppercase() {
733 HighlightTag::Struct
734 } else {
735 HighlightTag::Constant
736 }
737 }
738 }
739 }
740 _ => default,
741 };
742
743 tag.into()
557} 744}