aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/syntax_highlighting.rs194
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs12
-rw-r--r--crates/ide/src/syntax_highlighting/highlights.rs109
-rw-r--r--crates/ide/src/syntax_highlighting/html.rs18
-rw-r--r--crates/ide/src/syntax_highlighting/injection.rs85
-rw-r--r--crates/ide/src/syntax_highlighting/injector.rs83
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs5
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html31
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs5
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 @@
1mod highlights;
2mod injector;
3
1mod format; 4mod format;
2mod html; 5mod html;
3mod injection; 6mod 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)]
240struct HighlightedRangeStack {
241 stack: Vec<Vec<HighlightedRange>>,
242}
243
244/// We use a stack to implement the flattening logic for the highlighted
245/// syntax ranges.
246impl 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
403fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { 225fn 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
7use crate::{ 7use crate::{HighlightTag, HighlightedRange, SymbolKind};
8 syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange, SymbolKind, 8
9}; 9use super::highlights::Highlights;
10 10
11#[derive(Default)] 11#[derive(Default)]
12pub(super) struct FormatStringHighlighter { 12pub(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.
2use std::{cmp::Ordering, iter};
3
4use stdx::equal_range_by;
5use syntax::TextRange;
6
7use crate::{HighlightTag, HighlightedRange};
8
9pub(super) struct Highlights {
10 root: Node,
11}
12
13struct Node {
14 highlighted_range: HighlightedRange,
15 nested: Vec<Node>,
16}
17
18impl 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
40impl 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
101pub(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 @@
3use ide_db::base_db::SourceDatabase; 3use ide_db::base_db::SourceDatabase;
4use oorandom::Rand32; 4use oorandom::Rand32;
5use stdx::format_to; 5use stdx::format_to;
6use syntax::{AstNode, TextRange, TextSize}; 6use syntax::AstNode;
7 7
8use crate::{syntax_highlighting::highlight, FileId, RootDatabase}; 8use 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
3use std::{collections::BTreeMap, convert::TryFrom}; 3use std::convert::TryFrom;
4 4
5use hir::Semantics; 5use hir::Semantics;
6use ide_db::call_info::ActiveParameter; 6use ide_db::call_info::ActiveParameter;
7use itertools::Itertools; 7use itertools::Itertools;
8use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; 8use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
9 9
10use crate::{Analysis, Highlight, HighlightModifier, HighlightTag, HighlightedRange, RootDatabase}; 10use crate::{Analysis, HighlightModifier, HighlightTag, HighlightedRange, RootDatabase};
11 11
12use super::HighlightedRangeStack; 12use super::{highlights::Highlights, injector::Injector};
13 13
14pub(super) fn highlight_injection( 14pub(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
102type RangesMap = BTreeMap<TextSize, TextSize>;
103
104const RUSTDOC_FENCE: &'static str = "```"; 101const RUSTDOC_FENCE: &'static str = "```";
105const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[ 102const 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.
122pub(super) fn extract_doc_comments( 119pub(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.
197pub(super) fn highlight_doc_comment( 198pub(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.
3use std::ops::{self, Sub};
4
5use stdx::equal_range_by;
6use syntax::{TextRange, TextSize};
7
8use super::highlights::ordering;
9
10#[derive(Default)]
11pub(super) struct Injector {
12 buf: String,
13 ranges: Vec<(TextRange, Option<Delta<TextSize>>)>,
14}
15
16impl 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)]
48enum Delta<T> {
49 Add(T),
50 Sub(T),
51}
52
53impl<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
66impl 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
77impl 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
178impl ops::BitOr<HighlightModifier> for HighlightTag { 181impl 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">-&gt;</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">-&gt;</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">&gt;</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">&gt;</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
448impl Foo { 448impl 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`.