diff options
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting.rs')
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting.rs | 148 |
1 files changed, 101 insertions, 47 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 0b53ebe69..ab45c364a 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | mod tags; | 1 | mod tags; |
2 | mod html; | 2 | mod html; |
3 | mod injection; | ||
3 | #[cfg(test)] | 4 | #[cfg(test)] |
4 | mod tests; | 5 | mod tests; |
5 | 6 | ||
@@ -10,14 +11,14 @@ use ra_ide_db::{ | |||
10 | }; | 11 | }; |
11 | use ra_prof::profile; | 12 | use ra_prof::profile; |
12 | use ra_syntax::{ | 13 | use 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 | }; |
18 | use rustc_hash::FxHashMap; | 19 | use rustc_hash::FxHashMap; |
19 | 20 | ||
20 | use crate::{call_info::ActiveParameter, Analysis, FileId}; | 21 | use crate::FileId; |
21 | 22 | ||
22 | use ast::FormatSpecifier; | 23 | use ast::FormatSpecifier; |
23 | pub(crate) use html::highlight_as_html; | 24 | pub(crate) use html::highlight_as_html; |
@@ -123,6 +124,23 @@ pub(crate) fn highlight( | |||
123 | _ => (), | 124 | _ => (), |
124 | } | 125 | } |
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 | |||
126 | let element = match event { | 144 | let element = match event { |
127 | WalkEvent::Enter(it) => it, | 145 | WalkEvent::Enter(it) => it, |
128 | WalkEvent::Leave(_) => continue, | 146 | WalkEvent::Leave(_) => continue, |
@@ -173,7 +191,7 @@ pub(crate) fn highlight( | |||
173 | 191 | ||
174 | if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { | 192 | if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { |
175 | let expanded = element_to_highlight.as_token().unwrap().clone(); | 193 | let expanded = element_to_highlight.as_token().unwrap().clone(); |
176 | if highlight_injection(&mut stack, &sema, token, expanded).is_some() { | 194 | if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() { |
177 | continue; | 195 | continue; |
178 | } | 196 | } |
179 | } | 197 | } |
@@ -259,9 +277,8 @@ impl HighlightedRangeStack { | |||
259 | let mut parent = prev.pop().unwrap(); | 277 | let mut parent = prev.pop().unwrap(); |
260 | for ele in children { | 278 | for ele in children { |
261 | assert!(parent.range.contains_range(ele.range)); | 279 | assert!(parent.range.contains_range(ele.range)); |
262 | let mut cloned = parent.clone(); | 280 | |
263 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | 281 | let cloned = Self::intersect(&mut parent, &ele); |
264 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
265 | if !parent.range.is_empty() { | 282 | if !parent.range.is_empty() { |
266 | prev.push(parent); | 283 | prev.push(parent); |
267 | } | 284 | } |
@@ -274,6 +291,62 @@ impl HighlightedRangeStack { | |||
274 | } | 291 | } |
275 | } | 292 | } |
276 | 293 | ||
294 | /// Intersects the `HighlightedRange` `parent` with `child`. | ||
295 | /// `parent` is mutated in place, becoming the range before `child`. | ||
296 | /// Returns the range (of the same type as `parent`) *after* `child`. | ||
297 | fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange { | ||
298 | assert!(parent.range.contains_range(child.range)); | ||
299 | |||
300 | let mut cloned = parent.clone(); | ||
301 | parent.range = TextRange::new(parent.range.start(), child.range.start()); | ||
302 | cloned.range = TextRange::new(child.range.end(), cloned.range.end()); | ||
303 | |||
304 | cloned | ||
305 | } | ||
306 | |||
307 | /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`) | ||
308 | /// can only modify the last range currently on the stack. | ||
309 | /// Can be used to do injections that span multiple ranges, like the | ||
310 | /// doctest injection below. | ||
311 | /// If `delete` is set to true, the parent range is deleted instead of | ||
312 | /// intersected. | ||
313 | /// | ||
314 | /// Note that `pop` can be simulated by `pop_and_inject(false)` but the | ||
315 | /// latter is computationally more expensive. | ||
316 | fn pop_and_inject(&mut self, delete: bool) { | ||
317 | let mut children = self.stack.pop().unwrap(); | ||
318 | let prev = self.stack.last_mut().unwrap(); | ||
319 | children.sort_by_key(|range| range.range.start()); | ||
320 | prev.sort_by_key(|range| range.range.start()); | ||
321 | |||
322 | for child in children { | ||
323 | if let Some(idx) = | ||
324 | prev.iter().position(|parent| parent.range.contains_range(child.range)) | ||
325 | { | ||
326 | let cloned = Self::intersect(&mut prev[idx], &child); | ||
327 | let insert_idx = if delete || prev[idx].range.is_empty() { | ||
328 | prev.remove(idx); | ||
329 | idx | ||
330 | } else { | ||
331 | idx + 1 | ||
332 | }; | ||
333 | prev.insert(insert_idx, child); | ||
334 | if !delete && !cloned.range.is_empty() { | ||
335 | prev.insert(insert_idx + 1, cloned); | ||
336 | } | ||
337 | } else if let Some(_idx) = | ||
338 | prev.iter().position(|parent| parent.range.contains(child.range.start())) | ||
339 | { | ||
340 | unreachable!("child range should be completely contained in parent range"); | ||
341 | } else { | ||
342 | let idx = prev | ||
343 | .binary_search_by_key(&child.range.start(), |range| range.range.start()) | ||
344 | .unwrap_or_else(|x| x); | ||
345 | prev.insert(idx, child); | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | |||
277 | fn add(&mut self, range: HighlightedRange) { | 350 | fn add(&mut self, range: HighlightedRange) { |
278 | self.stack | 351 | self.stack |
279 | .last_mut() | 352 | .last_mut() |
@@ -363,6 +436,7 @@ fn highlight_element( | |||
363 | highlight_name(db, def) | HighlightModifier::Definition | 436 | highlight_name(db, def) | HighlightModifier::Definition |
364 | } | 437 | } |
365 | Some(NameClass::ConstReference(def)) => highlight_name(db, def), | 438 | Some(NameClass::ConstReference(def)) => highlight_name(db, def), |
439 | Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(), | ||
366 | None => highlight_name_by_syntax(name) | HighlightModifier::Definition, | 440 | None => highlight_name_by_syntax(name) | HighlightModifier::Definition, |
367 | } | 441 | } |
368 | } | 442 | } |
@@ -406,6 +480,19 @@ fn highlight_element( | |||
406 | _ => h, | 480 | _ => h, |
407 | } | 481 | } |
408 | } | 482 | } |
483 | T![*] => { | ||
484 | let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; | ||
485 | |||
486 | let expr = prefix_expr.expr()?; | ||
487 | let ty = sema.type_of_expr(&expr)?; | ||
488 | if !ty.is_raw_ptr() { | ||
489 | return None; | ||
490 | } | ||
491 | |||
492 | let mut h = Highlight::new(HighlightTag::Operator); | ||
493 | h |= HighlightModifier::Unsafe; | ||
494 | h | ||
495 | } | ||
409 | 496 | ||
410 | k if k.is_keyword() => { | 497 | k if k.is_keyword() => { |
411 | let h = Highlight::new(HighlightTag::Keyword); | 498 | let h = Highlight::new(HighlightTag::Keyword); |
@@ -458,7 +545,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | |||
458 | Definition::Field(_) => HighlightTag::Field, | 545 | Definition::Field(_) => HighlightTag::Field, |
459 | Definition::ModuleDef(def) => match def { | 546 | Definition::ModuleDef(def) => match def { |
460 | hir::ModuleDef::Module(_) => HighlightTag::Module, | 547 | hir::ModuleDef::Module(_) => HighlightTag::Module, |
461 | hir::ModuleDef::Function(_) => HighlightTag::Function, | 548 | hir::ModuleDef::Function(func) => { |
549 | let mut h = HighlightTag::Function.into(); | ||
550 | if func.is_unsafe(db) { | ||
551 | h |= HighlightModifier::Unsafe; | ||
552 | } | ||
553 | return h; | ||
554 | } | ||
462 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, | 555 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, |
463 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, | 556 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, |
464 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, | 557 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, |
@@ -516,42 +609,3 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | |||
516 | 609 | ||
517 | tag.into() | 610 | tag.into() |
518 | } | 611 | } |
519 | |||
520 | fn highlight_injection( | ||
521 | acc: &mut HighlightedRangeStack, | ||
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 | |||
541 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | ||
542 | if let Some(r) = literal.map_range_up(h.range) { | ||
543 | h.range = r; | ||
544 | acc.add(h) | ||
545 | } | ||
546 | } | ||
547 | |||
548 | if let Some(range) = literal.close_quote_text_range() { | ||
549 | acc.add(HighlightedRange { | ||
550 | range, | ||
551 | highlight: HighlightTag::StringLiteral.into(), | ||
552 | binding_hash: None, | ||
553 | }) | ||
554 | } | ||
555 | |||
556 | Some(()) | ||
557 | } | ||