diff options
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 194 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/format.rs | 12 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/highlights.rs | 109 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/html.rs | 18 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/injection.rs | 85 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/injector.rs | 83 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/tags.rs | 5 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html | 31 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/tests.rs | 5 |
9 files changed, 268 insertions, 274 deletions
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index ba0085244..2eb63a0b7 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -1,3 +1,6 @@ | |||
1 | mod highlights; | ||
2 | mod injector; | ||
3 | |||
1 | mod format; | 4 | mod format; |
2 | mod html; | 5 | mod html; |
3 | mod injection; | 6 | mod injection; |
@@ -69,9 +72,7 @@ pub(crate) fn highlight( | |||
69 | }; | 72 | }; |
70 | 73 | ||
71 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); | 74 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); |
72 | // We use a stack for the DFS traversal below. | 75 | let mut stack = highlights::Highlights::new(range_to_highlight); |
73 | // When we leave a node, the we use it to flatten the highlighted ranges. | ||
74 | let mut stack = HighlightedRangeStack::new(); | ||
75 | 76 | ||
76 | let mut current_macro_call: Option<ast::MacroCall> = None; | 77 | let mut current_macro_call: Option<ast::MacroCall> = None; |
77 | let mut current_macro_rules: Option<ast::MacroRules> = None; | 78 | let mut current_macro_rules: Option<ast::MacroRules> = None; |
@@ -82,14 +83,8 @@ pub(crate) fn highlight( | |||
82 | // Walk all nodes, keeping track of whether we are inside a macro or not. | 83 | // Walk all nodes, keeping track of whether we are inside a macro or not. |
83 | // If in macro, expand it first and highlight the expanded code. | 84 | // If in macro, expand it first and highlight the expanded code. |
84 | for event in root.preorder_with_tokens() { | 85 | for event in root.preorder_with_tokens() { |
85 | match &event { | ||
86 | WalkEvent::Enter(_) => stack.push(), | ||
87 | WalkEvent::Leave(_) => stack.pop(), | ||
88 | }; | ||
89 | |||
90 | let event_range = match &event { | 86 | let event_range = match &event { |
91 | WalkEvent::Enter(it) => it.text_range(), | 87 | WalkEvent::Enter(it) | WalkEvent::Leave(it) => it.text_range(), |
92 | WalkEvent::Leave(it) => it.text_range(), | ||
93 | }; | 88 | }; |
94 | 89 | ||
95 | // Element outside of the viewport, no need to highlight | 90 | // Element outside of the viewport, no need to highlight |
@@ -138,15 +133,8 @@ pub(crate) fn highlight( | |||
138 | if ast::Attr::can_cast(node.kind()) { | 133 | if ast::Attr::can_cast(node.kind()) { |
139 | inside_attribute = false | 134 | inside_attribute = false |
140 | } | 135 | } |
141 | if let Some((doctest, range_mapping, new_comments)) = | 136 | if let Some((new_comments, inj)) = injection::extract_doc_comments(node) { |
142 | injection::extract_doc_comments(node) | 137 | injection::highlight_doc_comment(new_comments, inj, &mut stack); |
143 | { | ||
144 | injection::highlight_doc_comment( | ||
145 | doctest, | ||
146 | range_mapping, | ||
147 | new_comments, | ||
148 | &mut stack, | ||
149 | ); | ||
150 | } | 138 | } |
151 | } | 139 | } |
152 | WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { | 140 | WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { |
@@ -217,7 +205,6 @@ pub(crate) fn highlight( | |||
217 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); | 205 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
218 | // Highlight escape sequences | 206 | // Highlight escape sequences |
219 | if let Some(char_ranges) = string.char_ranges() { | 207 | if let Some(char_ranges) = string.char_ranges() { |
220 | stack.push(); | ||
221 | for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { | 208 | for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { |
222 | if string.text()[piece_range.start().into()..].starts_with('\\') { | 209 | if string.text()[piece_range.start().into()..].starts_with('\\') { |
223 | stack.add(HighlightedRange { | 210 | stack.add(HighlightedRange { |
@@ -227,177 +214,12 @@ pub(crate) fn highlight( | |||
227 | }); | 214 | }); |
228 | } | 215 | } |
229 | } | 216 | } |
230 | stack.pop_and_inject(None); | ||
231 | } | ||
232 | } | ||
233 | } | ||
234 | } | ||
235 | |||
236 | stack.flattened() | ||
237 | } | ||
238 | |||
239 | #[derive(Debug)] | ||
240 | struct HighlightedRangeStack { | ||
241 | stack: Vec<Vec<HighlightedRange>>, | ||
242 | } | ||
243 | |||
244 | /// We use a stack to implement the flattening logic for the highlighted | ||
245 | /// syntax ranges. | ||
246 | impl HighlightedRangeStack { | ||
247 | fn new() -> Self { | ||
248 | Self { stack: vec![Vec::new()] } | ||
249 | } | ||
250 | |||
251 | fn push(&mut self) { | ||
252 | self.stack.push(Vec::new()); | ||
253 | } | ||
254 | |||
255 | /// Flattens the highlighted ranges. | ||
256 | /// | ||
257 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
258 | /// 1) parent-range: Attribute [0, 23) | ||
259 | /// 2) child-range: String [16, 21) | ||
260 | /// | ||
261 | /// The following code implements the flattening, for our example this results to: | ||
262 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
263 | fn pop(&mut self) { | ||
264 | let children = self.stack.pop().unwrap(); | ||
265 | let prev = self.stack.last_mut().unwrap(); | ||
266 | let needs_flattening = !children.is_empty() | ||
267 | && !prev.is_empty() | ||
268 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
269 | if !needs_flattening { | ||
270 | prev.extend(children); | ||
271 | } else { | ||
272 | let mut parent = prev.pop().unwrap(); | ||
273 | for ele in children { | ||
274 | assert!(parent.range.contains_range(ele.range)); | ||
275 | |||
276 | let cloned = Self::intersect(&mut parent, &ele); | ||
277 | if !parent.range.is_empty() { | ||
278 | prev.push(parent); | ||
279 | } | ||
280 | prev.push(ele); | ||
281 | parent = cloned; | ||
282 | } | ||
283 | if !parent.range.is_empty() { | ||
284 | prev.push(parent); | ||
285 | } | ||
286 | } | ||
287 | } | ||
288 | |||
289 | /// Intersects the `HighlightedRange` `parent` with `child`. | ||
290 | /// `parent` is mutated in place, becoming the range before `child`. | ||
291 | /// Returns the range (of the same type as `parent`) *after* `child`. | ||
292 | fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange { | ||
293 | assert!(parent.range.contains_range(child.range)); | ||
294 | |||
295 | let mut cloned = parent.clone(); | ||
296 | parent.range = TextRange::new(parent.range.start(), child.range.start()); | ||
297 | cloned.range = TextRange::new(child.range.end(), cloned.range.end()); | ||
298 | |||
299 | cloned | ||
300 | } | ||
301 | |||
302 | /// Remove the `HighlightRange` of `parent` that's currently covered by `child`. | ||
303 | fn intersect_partial(parent: &mut HighlightedRange, child: &HighlightedRange) { | ||
304 | assert!( | ||
305 | parent.range.start() <= child.range.start() | ||
306 | && parent.range.end() >= child.range.start() | ||
307 | && child.range.end() > parent.range.end() | ||
308 | ); | ||
309 | |||
310 | parent.range = TextRange::new(parent.range.start(), child.range.start()); | ||
311 | } | ||
312 | |||
313 | /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`) | ||
314 | /// can only modify the last range currently on the stack. | ||
315 | /// Can be used to do injections that span multiple ranges, like the | ||
316 | /// doctest injection below. | ||
317 | /// If `overwrite_parent` is non-optional, the highlighting of the parent range | ||
318 | /// is overwritten with the argument. | ||
319 | /// | ||
320 | /// Note that `pop` can be simulated by `pop_and_inject(false)` but the | ||
321 | /// latter is computationally more expensive. | ||
322 | fn pop_and_inject(&mut self, overwrite_parent: Option<Highlight>) { | ||
323 | let mut children = self.stack.pop().unwrap(); | ||
324 | let prev = self.stack.last_mut().unwrap(); | ||
325 | children.sort_by_key(|range| range.range.start()); | ||
326 | prev.sort_by_key(|range| range.range.start()); | ||
327 | |||
328 | for child in children { | ||
329 | if let Some(idx) = | ||
330 | prev.iter().position(|parent| parent.range.contains_range(child.range)) | ||
331 | { | ||
332 | if let Some(tag) = overwrite_parent { | ||
333 | prev[idx].highlight = tag; | ||
334 | } | ||
335 | |||
336 | let cloned = Self::intersect(&mut prev[idx], &child); | ||
337 | let insert_idx = if prev[idx].range.is_empty() { | ||
338 | prev.remove(idx); | ||
339 | idx | ||
340 | } else { | ||
341 | idx + 1 | ||
342 | }; | ||
343 | prev.insert(insert_idx, child); | ||
344 | if !cloned.range.is_empty() { | ||
345 | prev.insert(insert_idx + 1, cloned); | ||
346 | } | ||
347 | } else { | ||
348 | let maybe_idx = | ||
349 | prev.iter().position(|parent| parent.range.contains(child.range.start())); | ||
350 | match (overwrite_parent, maybe_idx) { | ||
351 | (Some(_), Some(idx)) => { | ||
352 | Self::intersect_partial(&mut prev[idx], &child); | ||
353 | let insert_idx = if prev[idx].range.is_empty() { | ||
354 | prev.remove(idx); | ||
355 | idx | ||
356 | } else { | ||
357 | idx + 1 | ||
358 | }; | ||
359 | prev.insert(insert_idx, child); | ||
360 | } | ||
361 | (_, None) => { | ||
362 | let idx = prev | ||
363 | .binary_search_by_key(&child.range.start(), |range| range.range.start()) | ||
364 | .unwrap_or_else(|x| x); | ||
365 | prev.insert(idx, child); | ||
366 | } | ||
367 | _ => { | ||
368 | unreachable!("child range should be completely contained in parent range"); | ||
369 | } | ||
370 | } | 217 | } |
371 | } | 218 | } |
372 | } | 219 | } |
373 | } | 220 | } |
374 | 221 | ||
375 | fn add(&mut self, range: HighlightedRange) { | 222 | stack.to_vec() |
376 | self.stack | ||
377 | .last_mut() | ||
378 | .expect("during DFS traversal, the stack must not be empty") | ||
379 | .push(range) | ||
380 | } | ||
381 | |||
382 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
383 | assert_eq!( | ||
384 | self.stack.len(), | ||
385 | 1, | ||
386 | "after DFS traversal, the stack should only contain a single element" | ||
387 | ); | ||
388 | let mut res = self.stack.pop().unwrap(); | ||
389 | res.sort_by_key(|range| range.range.start()); | ||
390 | // Check that ranges are sorted and disjoint | ||
391 | for (left, right) in res.iter().zip(res.iter().skip(1)) { | ||
392 | assert!( | ||
393 | left.range.end() <= right.range.start(), | ||
394 | "left: {:#?}, right: {:#?}", | ||
395 | left, | ||
396 | right | ||
397 | ); | ||
398 | } | ||
399 | res | ||
400 | } | ||
401 | } | 223 | } |
402 | 224 | ||
403 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 225 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { |
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs index 26416022b..ab66b406c 100644 --- a/crates/ide/src/syntax_highlighting/format.rs +++ b/crates/ide/src/syntax_highlighting/format.rs | |||
@@ -4,9 +4,9 @@ use syntax::{ | |||
4 | AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, | 4 | AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{HighlightTag, HighlightedRange, SymbolKind}; |
8 | syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange, SymbolKind, | 8 | |
9 | }; | 9 | use super::highlights::Highlights; |
10 | 10 | ||
11 | #[derive(Default)] | 11 | #[derive(Default)] |
12 | pub(super) struct FormatStringHighlighter { | 12 | pub(super) struct FormatStringHighlighter { |
@@ -39,22 +39,20 @@ impl FormatStringHighlighter { | |||
39 | } | 39 | } |
40 | pub(super) fn highlight_format_string( | 40 | pub(super) fn highlight_format_string( |
41 | &self, | 41 | &self, |
42 | range_stack: &mut HighlightedRangeStack, | 42 | stack: &mut Highlights, |
43 | string: &impl HasFormatSpecifier, | 43 | string: &impl HasFormatSpecifier, |
44 | range: TextRange, | 44 | range: TextRange, |
45 | ) { | 45 | ) { |
46 | if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) { | 46 | if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) { |
47 | range_stack.push(); | ||
48 | string.lex_format_specifier(|piece_range, kind| { | 47 | string.lex_format_specifier(|piece_range, kind| { |
49 | if let Some(highlight) = highlight_format_specifier(kind) { | 48 | if let Some(highlight) = highlight_format_specifier(kind) { |
50 | range_stack.add(HighlightedRange { | 49 | stack.add(HighlightedRange { |
51 | range: piece_range + range.start(), | 50 | range: piece_range + range.start(), |
52 | highlight: highlight.into(), | 51 | highlight: highlight.into(), |
53 | binding_hash: None, | 52 | binding_hash: None, |
54 | }); | 53 | }); |
55 | } | 54 | } |
56 | }); | 55 | }); |
57 | range_stack.pop(); | ||
58 | } | 56 | } |
59 | } | 57 | } |
60 | } | 58 | } |
diff --git a/crates/ide/src/syntax_highlighting/highlights.rs b/crates/ide/src/syntax_highlighting/highlights.rs new file mode 100644 index 000000000..3e733c87c --- /dev/null +++ b/crates/ide/src/syntax_highlighting/highlights.rs | |||
@@ -0,0 +1,109 @@ | |||
1 | //! Collects a tree of highlighted ranges and flattens it. | ||
2 | use std::{cmp::Ordering, iter}; | ||
3 | |||
4 | use stdx::equal_range_by; | ||
5 | use syntax::TextRange; | ||
6 | |||
7 | use crate::{HighlightTag, HighlightedRange}; | ||
8 | |||
9 | pub(super) struct Highlights { | ||
10 | root: Node, | ||
11 | } | ||
12 | |||
13 | struct Node { | ||
14 | highlighted_range: HighlightedRange, | ||
15 | nested: Vec<Node>, | ||
16 | } | ||
17 | |||
18 | impl Highlights { | ||
19 | pub(super) fn new(range: TextRange) -> Highlights { | ||
20 | Highlights { | ||
21 | root: Node::new(HighlightedRange { | ||
22 | range, | ||
23 | highlight: HighlightTag::Dummy.into(), | ||
24 | binding_hash: None, | ||
25 | }), | ||
26 | } | ||
27 | } | ||
28 | |||
29 | pub(super) fn add(&mut self, highlighted_range: HighlightedRange) { | ||
30 | self.root.add(highlighted_range); | ||
31 | } | ||
32 | |||
33 | pub(super) fn to_vec(self) -> Vec<HighlightedRange> { | ||
34 | let mut res = Vec::new(); | ||
35 | self.root.flatten(&mut res); | ||
36 | res | ||
37 | } | ||
38 | } | ||
39 | |||
40 | impl Node { | ||
41 | fn new(highlighted_range: HighlightedRange) -> Node { | ||
42 | Node { highlighted_range, nested: Vec::new() } | ||
43 | } | ||
44 | |||
45 | fn add(&mut self, highlighted_range: HighlightedRange) { | ||
46 | assert!(self.highlighted_range.range.contains_range(highlighted_range.range)); | ||
47 | |||
48 | // Fast path | ||
49 | if let Some(last) = self.nested.last_mut() { | ||
50 | if last.highlighted_range.range.contains_range(highlighted_range.range) { | ||
51 | return last.add(highlighted_range); | ||
52 | } | ||
53 | if last.highlighted_range.range.end() <= highlighted_range.range.start() { | ||
54 | return self.nested.push(Node::new(highlighted_range)); | ||
55 | } | ||
56 | } | ||
57 | |||
58 | let (start, len) = equal_range_by(&self.nested, |n| { | ||
59 | ordering(n.highlighted_range.range, highlighted_range.range) | ||
60 | }); | ||
61 | |||
62 | if len == 1 | ||
63 | && self.nested[start].highlighted_range.range.contains_range(highlighted_range.range) | ||
64 | { | ||
65 | return self.nested[start].add(highlighted_range); | ||
66 | } | ||
67 | |||
68 | let nested = self | ||
69 | .nested | ||
70 | .splice(start..start + len, iter::once(Node::new(highlighted_range))) | ||
71 | .collect::<Vec<_>>(); | ||
72 | self.nested[start].nested = nested; | ||
73 | } | ||
74 | |||
75 | fn flatten(&self, acc: &mut Vec<HighlightedRange>) { | ||
76 | let mut start = self.highlighted_range.range.start(); | ||
77 | let mut nested = self.nested.iter(); | ||
78 | loop { | ||
79 | let next = nested.next(); | ||
80 | let end = next.map_or(self.highlighted_range.range.end(), |it| { | ||
81 | it.highlighted_range.range.start() | ||
82 | }); | ||
83 | if start < end { | ||
84 | acc.push(HighlightedRange { | ||
85 | range: TextRange::new(start, end), | ||
86 | highlight: self.highlighted_range.highlight, | ||
87 | binding_hash: self.highlighted_range.binding_hash, | ||
88 | }); | ||
89 | } | ||
90 | start = match next { | ||
91 | Some(child) => { | ||
92 | child.flatten(acc); | ||
93 | child.highlighted_range.range.end() | ||
94 | } | ||
95 | None => break, | ||
96 | } | ||
97 | } | ||
98 | } | ||
99 | } | ||
100 | |||
101 | pub(super) fn ordering(r1: TextRange, r2: TextRange) -> Ordering { | ||
102 | if r1.end() <= r2.start() { | ||
103 | Ordering::Less | ||
104 | } else if r2.end() <= r1.start() { | ||
105 | Ordering::Greater | ||
106 | } else { | ||
107 | Ordering::Equal | ||
108 | } | ||
109 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs index 99ba3a59d..44f611b25 100644 --- a/crates/ide/src/syntax_highlighting/html.rs +++ b/crates/ide/src/syntax_highlighting/html.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | use ide_db::base_db::SourceDatabase; | 3 | use ide_db::base_db::SourceDatabase; |
4 | use oorandom::Rand32; | 4 | use oorandom::Rand32; |
5 | use stdx::format_to; | 5 | use stdx::format_to; |
6 | use syntax::{AstNode, TextRange, TextSize}; | 6 | use syntax::AstNode; |
7 | 7 | ||
8 | use crate::{syntax_highlighting::highlight, FileId, RootDatabase}; | 8 | use crate::{syntax_highlighting::highlight, FileId, RootDatabase}; |
9 | 9 | ||
@@ -22,17 +22,15 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo | |||
22 | 22 | ||
23 | let ranges = highlight(db, file_id, None, false); | 23 | let ranges = highlight(db, file_id, None, false); |
24 | let text = parse.tree().syntax().to_string(); | 24 | let text = parse.tree().syntax().to_string(); |
25 | let mut prev_pos = TextSize::from(0); | ||
26 | let mut buf = String::new(); | 25 | let mut buf = String::new(); |
27 | buf.push_str(&STYLE); | 26 | buf.push_str(&STYLE); |
28 | buf.push_str("<pre><code>"); | 27 | buf.push_str("<pre><code>"); |
29 | for range in &ranges { | 28 | for range in &ranges { |
30 | if range.range.start() > prev_pos { | 29 | let curr = &text[range.range]; |
31 | let curr = &text[TextRange::new(prev_pos, range.range.start())]; | 30 | if range.highlight.is_empty() { |
32 | let text = html_escape(curr); | 31 | format_to!(buf, "{}", html_escape(curr)); |
33 | buf.push_str(&text); | 32 | continue; |
34 | } | 33 | } |
35 | let curr = &text[TextRange::new(range.range.start(), range.range.end())]; | ||
36 | 34 | ||
37 | let class = range.highlight.to_string().replace('.', " "); | 35 | let class = range.highlight.to_string().replace('.', " "); |
38 | let color = match (rainbow, range.binding_hash) { | 36 | let color = match (rainbow, range.binding_hash) { |
@@ -42,13 +40,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo | |||
42 | _ => "".into(), | 40 | _ => "".into(), |
43 | }; | 41 | }; |
44 | format_to!(buf, "<span class=\"{}\"{}>{}</span>", class, color, html_escape(curr)); | 42 | format_to!(buf, "<span class=\"{}\"{}>{}</span>", class, color, html_escape(curr)); |
45 | |||
46 | prev_pos = range.range.end(); | ||
47 | } | 43 | } |
48 | // Add the remaining (non-highlighted) text | ||
49 | let curr = &text[TextRange::new(prev_pos, TextSize::of(&text))]; | ||
50 | let text = html_escape(curr); | ||
51 | buf.push_str(&text); | ||
52 | buf.push_str("</code></pre>"); | 44 | buf.push_str("</code></pre>"); |
53 | buf | 45 | buf |
54 | } | 46 | } |
diff --git a/crates/ide/src/syntax_highlighting/injection.rs b/crates/ide/src/syntax_highlighting/injection.rs index d6be9708d..98ee03e0d 100644 --- a/crates/ide/src/syntax_highlighting/injection.rs +++ b/crates/ide/src/syntax_highlighting/injection.rs | |||
@@ -1,18 +1,18 @@ | |||
1 | //! Syntax highlighting injections such as highlighting of documentation tests. | 1 | //! Syntax highlighting injections such as highlighting of documentation tests. |
2 | 2 | ||
3 | use std::{collections::BTreeMap, convert::TryFrom}; | 3 | use std::convert::TryFrom; |
4 | 4 | ||
5 | use hir::Semantics; | 5 | use hir::Semantics; |
6 | use ide_db::call_info::ActiveParameter; | 6 | use ide_db::call_info::ActiveParameter; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; | 8 | use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; |
9 | 9 | ||
10 | use crate::{Analysis, Highlight, HighlightModifier, HighlightTag, HighlightedRange, RootDatabase}; | 10 | use crate::{Analysis, HighlightModifier, HighlightTag, HighlightedRange, RootDatabase}; |
11 | 11 | ||
12 | use super::HighlightedRangeStack; | 12 | use super::{highlights::Highlights, injector::Injector}; |
13 | 13 | ||
14 | pub(super) fn highlight_injection( | 14 | pub(super) fn highlight_injection( |
15 | acc: &mut HighlightedRangeStack, | 15 | acc: &mut Highlights, |
16 | sema: &Semantics<RootDatabase>, | 16 | sema: &Semantics<RootDatabase>, |
17 | literal: ast::String, | 17 | literal: ast::String, |
18 | expanded: SyntaxToken, | 18 | expanded: SyntaxToken, |
@@ -98,9 +98,6 @@ impl MarkerInfo { | |||
98 | } | 98 | } |
99 | } | 99 | } |
100 | 100 | ||
101 | /// Mapping from extracted documentation code to original code | ||
102 | type RangesMap = BTreeMap<TextSize, TextSize>; | ||
103 | |||
104 | const RUSTDOC_FENCE: &'static str = "```"; | 101 | const RUSTDOC_FENCE: &'static str = "```"; |
105 | const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[ | 102 | const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[ |
106 | "", | 103 | "", |
@@ -119,20 +116,20 @@ const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[ | |||
119 | /// Lastly, a vector of new comment highlight ranges (spanning only the | 116 | /// Lastly, a vector of new comment highlight ranges (spanning only the |
120 | /// comment prefix) is returned which is used in the syntax highlighting | 117 | /// comment prefix) is returned which is used in the syntax highlighting |
121 | /// injection to replace the previous (line-spanning) comment ranges. | 118 | /// injection to replace the previous (line-spanning) comment ranges. |
122 | pub(super) fn extract_doc_comments( | 119 | pub(super) fn extract_doc_comments(node: &SyntaxNode) -> Option<(Vec<HighlightedRange>, Injector)> { |
123 | node: &SyntaxNode, | 120 | let mut inj = Injector::default(); |
124 | ) -> Option<(String, RangesMap, Vec<HighlightedRange>)> { | ||
125 | // wrap the doctest into function body to get correct syntax highlighting | 121 | // wrap the doctest into function body to get correct syntax highlighting |
126 | let prefix = "fn doctest() {\n"; | 122 | let prefix = "fn doctest() {\n"; |
127 | let suffix = "}\n"; | 123 | let suffix = "}\n"; |
128 | // Mapping from extracted documentation code to original code | 124 | |
129 | let mut range_mapping: RangesMap = BTreeMap::new(); | 125 | let mut line_start = TextSize::of(prefix); |
130 | let mut line_start = TextSize::try_from(prefix.len()).unwrap(); | ||
131 | let mut is_codeblock = false; | 126 | let mut is_codeblock = false; |
132 | let mut is_doctest = false; | 127 | let mut is_doctest = false; |
133 | // Replace the original, line-spanning comment ranges by new, only comment-prefix | 128 | // Replace the original, line-spanning comment ranges by new, only comment-prefix |
134 | // spanning comment ranges. | 129 | // spanning comment ranges. |
135 | let mut new_comments = Vec::new(); | 130 | let mut new_comments = Vec::new(); |
131 | |||
132 | inj.add_unmapped(prefix); | ||
136 | let doctest = node | 133 | let doctest = node |
137 | .children_with_tokens() | 134 | .children_with_tokens() |
138 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) | 135 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) |
@@ -169,7 +166,6 @@ pub(super) fn extract_doc_comments( | |||
169 | pos | 166 | pos |
170 | }; | 167 | }; |
171 | 168 | ||
172 | range_mapping.insert(line_start, range.start() + TextSize::try_from(pos).unwrap()); | ||
173 | new_comments.push(HighlightedRange { | 169 | new_comments.push(HighlightedRange { |
174 | range: TextRange::new( | 170 | range: TextRange::new( |
175 | range.start(), | 171 | range.start(), |
@@ -179,62 +175,43 @@ pub(super) fn extract_doc_comments( | |||
179 | binding_hash: None, | 175 | binding_hash: None, |
180 | }); | 176 | }); |
181 | line_start += range.len() - TextSize::try_from(pos).unwrap(); | 177 | line_start += range.len() - TextSize::try_from(pos).unwrap(); |
182 | line_start += TextSize::try_from('\n'.len_utf8()).unwrap(); | 178 | line_start += TextSize::of("\n"); |
183 | 179 | ||
180 | inj.add( | ||
181 | &line[pos..], | ||
182 | TextRange::new(range.start() + TextSize::try_from(pos).unwrap(), range.end()), | ||
183 | ); | ||
184 | inj.add_unmapped("\n"); | ||
184 | line[pos..].to_owned() | 185 | line[pos..].to_owned() |
185 | }) | 186 | }) |
186 | .join("\n"); | 187 | .join("\n"); |
188 | inj.add_unmapped(suffix); | ||
187 | 189 | ||
188 | if doctest.is_empty() { | 190 | if doctest.is_empty() { |
189 | return None; | 191 | return None; |
190 | } | 192 | } |
191 | 193 | ||
192 | let doctest = format!("{}{}{}", prefix, doctest, suffix); | 194 | Some((new_comments, inj)) |
193 | Some((doctest, range_mapping, new_comments)) | ||
194 | } | 195 | } |
195 | 196 | ||
196 | /// Injection of syntax highlighting of doctests. | 197 | /// Injection of syntax highlighting of doctests. |
197 | pub(super) fn highlight_doc_comment( | 198 | pub(super) fn highlight_doc_comment( |
198 | text: String, | ||
199 | range_mapping: RangesMap, | ||
200 | new_comments: Vec<HighlightedRange>, | 199 | new_comments: Vec<HighlightedRange>, |
201 | stack: &mut HighlightedRangeStack, | 200 | inj: Injector, |
201 | stack: &mut Highlights, | ||
202 | ) { | 202 | ) { |
203 | let (analysis, tmp_file_id) = Analysis::from_single_file(text); | 203 | let (analysis, tmp_file_id) = Analysis::from_single_file(inj.text().to_string()); |
204 | |||
205 | stack.push(); | ||
206 | for mut h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() { | ||
207 | // Determine start offset and end offset in case of multi-line ranges | ||
208 | let mut start_offset = None; | ||
209 | let mut end_offset = None; | ||
210 | for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() { | ||
211 | // It's possible for orig_line_start - line_start to be negative. Add h.range.start() | ||
212 | // here and remove it from the end range after the loop below so that the values are | ||
213 | // always non-negative. | ||
214 | let offset = h.range.start() + orig_line_start - line_start; | ||
215 | if line_start <= &h.range.start() { | ||
216 | start_offset.get_or_insert(offset); | ||
217 | break; | ||
218 | } else { | ||
219 | end_offset.get_or_insert(offset); | ||
220 | } | ||
221 | } | ||
222 | if let Some(start_offset) = start_offset { | ||
223 | h.range = TextRange::new( | ||
224 | start_offset, | ||
225 | h.range.end() + end_offset.unwrap_or(start_offset) - h.range.start(), | ||
226 | ); | ||
227 | |||
228 | h.highlight |= HighlightModifier::Injected; | ||
229 | stack.add(h); | ||
230 | } | ||
231 | } | ||
232 | |||
233 | // Inject the comment prefix highlight ranges | ||
234 | stack.push(); | ||
235 | for comment in new_comments { | 204 | for comment in new_comments { |
236 | stack.add(comment); | 205 | stack.add(comment); |
237 | } | 206 | } |
238 | stack.pop_and_inject(None); | 207 | |
239 | stack.pop_and_inject(Some(Highlight::from(HighlightTag::Dummy) | HighlightModifier::Injected)); | 208 | for h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() { |
209 | for r in inj.map_range_up(h.range) { | ||
210 | stack.add(HighlightedRange { | ||
211 | range: r, | ||
212 | highlight: h.highlight | HighlightModifier::Injected, | ||
213 | binding_hash: h.binding_hash, | ||
214 | }); | ||
215 | } | ||
216 | } | ||
240 | } | 217 | } |
diff --git a/crates/ide/src/syntax_highlighting/injector.rs b/crates/ide/src/syntax_highlighting/injector.rs new file mode 100644 index 000000000..0513a9fd6 --- /dev/null +++ b/crates/ide/src/syntax_highlighting/injector.rs | |||
@@ -0,0 +1,83 @@ | |||
1 | //! Extracts a subsequence of a text document, remembering the mapping of ranges | ||
2 | //! between original and extracted texts. | ||
3 | use std::ops::{self, Sub}; | ||
4 | |||
5 | use stdx::equal_range_by; | ||
6 | use syntax::{TextRange, TextSize}; | ||
7 | |||
8 | use super::highlights::ordering; | ||
9 | |||
10 | #[derive(Default)] | ||
11 | pub(super) struct Injector { | ||
12 | buf: String, | ||
13 | ranges: Vec<(TextRange, Option<Delta<TextSize>>)>, | ||
14 | } | ||
15 | |||
16 | impl Injector { | ||
17 | pub(super) fn add(&mut self, text: &str, source_range: TextRange) { | ||
18 | let len = TextSize::of(text); | ||
19 | assert_eq!(len, source_range.len()); | ||
20 | |||
21 | let target_range = TextRange::at(TextSize::of(&self.buf), len); | ||
22 | self.ranges | ||
23 | .push((target_range, Some(Delta::new(target_range.start(), source_range.start())))); | ||
24 | self.buf.push_str(text); | ||
25 | } | ||
26 | pub(super) fn add_unmapped(&mut self, text: &str) { | ||
27 | let len = TextSize::of(text); | ||
28 | |||
29 | let target_range = TextRange::at(TextSize::of(&self.buf), len); | ||
30 | self.ranges.push((target_range, None)); | ||
31 | self.buf.push_str(text); | ||
32 | } | ||
33 | |||
34 | pub(super) fn text(&self) -> &str { | ||
35 | &self.buf | ||
36 | } | ||
37 | pub(super) fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ { | ||
38 | let (start, len) = equal_range_by(&self.ranges, |&(r, _)| ordering(r, range)); | ||
39 | (start..start + len).filter_map(move |i| { | ||
40 | let (target_range, delta) = self.ranges[i]; | ||
41 | let intersection = target_range.intersect(range).unwrap(); | ||
42 | Some(intersection + delta?) | ||
43 | }) | ||
44 | } | ||
45 | } | ||
46 | |||
47 | #[derive(Clone, Copy)] | ||
48 | enum Delta<T> { | ||
49 | Add(T), | ||
50 | Sub(T), | ||
51 | } | ||
52 | |||
53 | impl<T> Delta<T> { | ||
54 | fn new(from: T, to: T) -> Delta<T> | ||
55 | where | ||
56 | T: Ord + Sub<Output = T>, | ||
57 | { | ||
58 | if to >= from { | ||
59 | Delta::Add(to - from) | ||
60 | } else { | ||
61 | Delta::Sub(from - to) | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | |||
66 | impl ops::Add<Delta<TextSize>> for TextSize { | ||
67 | type Output = TextSize; | ||
68 | |||
69 | fn add(self, rhs: Delta<TextSize>) -> TextSize { | ||
70 | match rhs { | ||
71 | Delta::Add(it) => self + it, | ||
72 | Delta::Sub(it) => self - it, | ||
73 | } | ||
74 | } | ||
75 | } | ||
76 | |||
77 | impl ops::Add<Delta<TextSize>> for TextRange { | ||
78 | type Output = TextRange; | ||
79 | |||
80 | fn add(self, rhs: Delta<TextSize>) -> TextRange { | ||
81 | TextRange::at(self.start() + rhs, self.len()) | ||
82 | } | ||
83 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs index 8b8867079..a0286b72d 100644 --- a/crates/ide/src/syntax_highlighting/tags.rs +++ b/crates/ide/src/syntax_highlighting/tags.rs | |||
@@ -94,13 +94,13 @@ impl HighlightTag { | |||
94 | HighlightTag::Comment => "comment", | 94 | HighlightTag::Comment => "comment", |
95 | HighlightTag::EscapeSequence => "escape_sequence", | 95 | HighlightTag::EscapeSequence => "escape_sequence", |
96 | HighlightTag::FormatSpecifier => "format_specifier", | 96 | HighlightTag::FormatSpecifier => "format_specifier", |
97 | HighlightTag::Dummy => "dummy", | ||
98 | HighlightTag::Keyword => "keyword", | 97 | HighlightTag::Keyword => "keyword", |
99 | HighlightTag::Punctuation => "punctuation", | 98 | HighlightTag::Punctuation => "punctuation", |
100 | HighlightTag::NumericLiteral => "numeric_literal", | 99 | HighlightTag::NumericLiteral => "numeric_literal", |
101 | HighlightTag::Operator => "operator", | 100 | HighlightTag::Operator => "operator", |
102 | HighlightTag::StringLiteral => "string_literal", | 101 | HighlightTag::StringLiteral => "string_literal", |
103 | HighlightTag::UnresolvedReference => "unresolved_reference", | 102 | HighlightTag::UnresolvedReference => "unresolved_reference", |
103 | HighlightTag::Dummy => "dummy", | ||
104 | } | 104 | } |
105 | } | 105 | } |
106 | } | 106 | } |
@@ -173,6 +173,9 @@ impl Highlight { | |||
173 | pub(crate) fn new(tag: HighlightTag) -> Highlight { | 173 | pub(crate) fn new(tag: HighlightTag) -> Highlight { |
174 | Highlight { tag, modifiers: HighlightModifiers::default() } | 174 | Highlight { tag, modifiers: HighlightModifiers::default() } |
175 | } | 175 | } |
176 | pub fn is_empty(&self) -> bool { | ||
177 | self.tag == HighlightTag::Dummy && self.modifiers == HighlightModifiers::default() | ||
178 | } | ||
176 | } | 179 | } |
177 | 180 | ||
178 | impl ops::BitOr<HighlightModifier> for HighlightTag { | 181 | impl ops::BitOr<HighlightModifier> for HighlightTag { |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index 4dd7413ba..9d42b11c1 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html | |||
@@ -37,13 +37,18 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
37 | .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } | 37 | .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } |
38 | </style> | 38 | </style> |
39 | <pre><code><span class="comment documentation">/// ```</span> | 39 | <pre><code><span class="comment documentation">/// ```</span> |
40 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="punctuation injected">_</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="punctuation injected">;</span><span class="punctuation injected"> | 40 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="punctuation injected">_</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="punctuation injected">;</span> |
41 | </span><span class="comment documentation">/// ```</span> | 41 | <span class="comment documentation">/// ```</span> |
42 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span> | 42 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span> |
43 | <span class="field declaration">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span><span class="punctuation">,</span> | 43 | <span class="field declaration">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span><span class="punctuation">,</span> |
44 | <span class="punctuation">}</span> | 44 | <span class="punctuation">}</span> |
45 | 45 | ||
46 | <span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span> | 46 | <span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span> |
47 | <span class="comment documentation">/// ```</span> | ||
48 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="punctuation injected">_</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"Call me</span> | ||
49 | <span class="comment">// KILLER WHALE</span> | ||
50 | <span class="comment documentation">/// </span><span class="string_literal injected"> Ishmael."</span><span class="punctuation injected">;</span> | ||
51 | <span class="comment documentation">/// ```</span> | ||
47 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration associated">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="punctuation">;</span> | 52 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration associated">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="punctuation">;</span> |
48 | 53 | ||
49 | <span class="comment documentation">/// Constructs a new `Foo`.</span> | 54 | <span class="comment documentation">/// Constructs a new `Foo`.</span> |
@@ -52,8 +57,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
52 | <span class="comment documentation">///</span> | 57 | <span class="comment documentation">///</span> |
53 | <span class="comment documentation">/// ```</span> | 58 | <span class="comment documentation">/// ```</span> |
54 | <span class="comment documentation">/// #</span><span class="dummy injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="punctuation attribute injected">)</span><span class="attribute attribute injected">]</span> | 59 | <span class="comment documentation">/// #</span><span class="dummy injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="punctuation attribute injected">)</span><span class="attribute attribute injected">]</span> |
55 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="keyword injected">mut</span><span class="dummy injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected"> | 60 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="keyword injected">mut</span><span class="dummy injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span> |
56 | </span> <span class="comment documentation">/// ```</span> | 61 | <span class="comment documentation">/// ```</span> |
57 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static associated">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="struct">Foo</span> <span class="punctuation">{</span> | 62 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static associated">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="struct">Foo</span> <span class="punctuation">{</span> |
58 | <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">bar</span><span class="punctuation">:</span> <span class="bool_literal">true</span> <span class="punctuation">}</span> | 63 | <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">bar</span><span class="punctuation">:</span> <span class="bool_literal">true</span> <span class="punctuation">}</span> |
59 | <span class="punctuation">}</span> | 64 | <span class="punctuation">}</span> |
@@ -72,18 +77,18 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
72 | <span class="comment documentation">///</span> | 77 | <span class="comment documentation">///</span> |
73 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">bar</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="dummy injected"> </span><span class="operator injected">||</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="punctuation injected">;</span> | 78 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">bar</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="dummy injected"> </span><span class="operator injected">||</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="punctuation injected">;</span> |
74 | <span class="comment documentation">///</span> | 79 | <span class="comment documentation">///</span> |
75 | <span class="comment documentation">/// </span><span class="comment injected">/* multi-line | 80 | <span class="comment documentation">/// </span><span class="comment injected">/* multi-line</span> |
76 | </span><span class="comment documentation">/// </span><span class="comment injected"> comment */</span> | 81 | <span class="comment documentation">/// </span><span class="comment injected"> comment */</span> |
77 | <span class="comment documentation">///</span> | 82 | <span class="comment documentation">///</span> |
78 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">multi_line_string</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"Foo | 83 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">multi_line_string</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"Foo</span> |
79 | </span><span class="comment documentation">/// </span><span class="string_literal injected"> bar | 84 | <span class="comment documentation">/// </span><span class="string_literal injected"> bar</span> |
80 | </span><span class="comment documentation">/// </span><span class="string_literal injected"> "</span><span class="punctuation injected">;</span> | 85 | <span class="comment documentation">/// </span><span class="string_literal injected"> "</span><span class="punctuation injected">;</span> |
81 | <span class="comment documentation">///</span> | 86 | <span class="comment documentation">///</span> |
82 | <span class="comment documentation">/// ```</span> | 87 | <span class="comment documentation">/// ```</span> |
83 | <span class="comment documentation">///</span> | 88 | <span class="comment documentation">///</span> |
84 | <span class="comment documentation">/// ```rust,no_run</span> | 89 | <span class="comment documentation">/// ```rust,no_run</span> |
85 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">foobar</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected"> | 90 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">foobar</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span> |
86 | </span> <span class="comment documentation">/// ```</span> | 91 | <span class="comment documentation">/// ```</span> |
87 | <span class="comment documentation">///</span> | 92 | <span class="comment documentation">///</span> |
88 | <span class="comment documentation">/// ```sh</span> | 93 | <span class="comment documentation">/// ```sh</span> |
89 | <span class="comment documentation">/// echo 1</span> | 94 | <span class="comment documentation">/// echo 1</span> |
@@ -94,8 +99,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
94 | <span class="punctuation">}</span> | 99 | <span class="punctuation">}</span> |
95 | 100 | ||
96 | <span class="comment documentation">/// ```</span> | 101 | <span class="comment documentation">/// ```</span> |
97 | <span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="punctuation injected">(</span><span class="numeric_literal injected">1</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected"> | 102 | <span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="punctuation injected">(</span><span class="numeric_literal injected">1</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span> |
98 | </span><span class="comment documentation">/// ```</span> | 103 | <span class="comment documentation">/// ```</span> |
99 | <span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">noop</span> <span class="punctuation">{</span> | 104 | <span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">noop</span> <span class="punctuation">{</span> |
100 | <span class="punctuation">(</span><span class="punctuation">$</span>expr<span class="punctuation">:</span>expr<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">></span> <span class="punctuation">{</span> | 105 | <span class="punctuation">(</span><span class="punctuation">$</span>expr<span class="punctuation">:</span>expr<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">></span> <span class="punctuation">{</span> |
101 | <span class="punctuation">$</span>expr | 106 | <span class="punctuation">$</span>expr |
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 9e1a3974c..a62704c39 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs | |||
@@ -446,6 +446,11 @@ struct Foo { | |||
446 | } | 446 | } |
447 | 447 | ||
448 | impl Foo { | 448 | impl Foo { |
449 | /// ``` | ||
450 | /// let _ = "Call me | ||
451 | // KILLER WHALE | ||
452 | /// Ishmael."; | ||
453 | /// ``` | ||
449 | pub const bar: bool = true; | 454 | pub const bar: bool = true; |
450 | 455 | ||
451 | /// Constructs a new `Foo`. | 456 | /// Constructs a new `Foo`. |