diff options
author | Leander Tentrup <[email protected]> | 2020-04-06 22:00:09 +0100 |
---|---|---|
committer | Leander Tentrup <[email protected]> | 2020-04-06 22:00:09 +0100 |
commit | bf96d46fee1242ad701cb037a03c9575e84221b1 (patch) | |
tree | bd01242f4e504b25d07a3d193b6715443f7b2b86 /crates | |
parent | bb45aca9093ffe61cf444d538b3de737117c9a64 (diff) |
Simplify HTML highlighter and add test case for highlight_injection logic
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_ide/src/snapshots/highlight_injection.html | 39 | ||||
-rw-r--r-- | crates/ra_ide/src/snapshots/highlighting.html | 10 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting.rs | 8 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/html.rs | 66 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/tests.rs | 33 |
5 files changed, 97 insertions, 59 deletions
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html new file mode 100644 index 000000000..6ec13bd80 --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_injection.html | |||
@@ -0,0 +1,39 @@ | |||
1 | |||
2 | <style> | ||
3 | body { margin: 0; } | ||
4 | pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; } | ||
5 | |||
6 | .lifetime { color: #DFAF8F; font-style: italic; } | ||
7 | .comment { color: #7F9F7F; } | ||
8 | .struct, .enum { color: #7CB8BB; } | ||
9 | .enum_variant { color: #BDE0F3; } | ||
10 | .string_literal { color: #CC9393; } | ||
11 | .field { color: #94BFF3; } | ||
12 | .function { color: #93E0E3; } | ||
13 | .parameter { color: #94BFF3; } | ||
14 | .text { color: #DCDCCC; } | ||
15 | .type { color: #7CB8BB; } | ||
16 | .builtin_type { color: #8CD0D3; } | ||
17 | .type_param { color: #DFAF8F; } | ||
18 | .attribute { color: #94BFF3; } | ||
19 | .numeric_literal { color: #BFEBBF; } | ||
20 | .macro { color: #94BFF3; } | ||
21 | .module { color: #AFD8AF; } | ||
22 | .variable { color: #DCDCCC; } | ||
23 | .mutable { text-decoration: underline; } | ||
24 | |||
25 | .keyword { color: #F0DFAF; font-weight: bold; } | ||
26 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | ||
27 | .control { font-style: italic; } | ||
28 | </style> | ||
29 | <pre><code><span class="keyword">fn</span> <span class="function declaration">fixture</span>(<span class="variable declaration">ra_fixture</span>: &<span class="builtin_type">str</span>) {} | ||
30 | |||
31 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { | ||
32 | <span class="function">fixture</span>(<span class="string_literal">r#"</span> | ||
33 | <span class="keyword">trait</span> <span class="trait declaration">Foo</span> { | ||
34 | <span class="keyword">fn</span> <span class="function declaration">foo</span>() { | ||
35 | <span class="macro">println!</span>(<span class="string_literal">"2 + 2 = {}"</span>, <span class="numeric_literal">4</span>); | ||
36 | } | ||
37 | }<span class="string_literal">"#</span> | ||
38 | ); | ||
39 | }</code></pre> \ No newline at end of file | ||
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html index 495b07f69..214dcbb62 100644 --- a/crates/ra_ide/src/snapshots/highlighting.html +++ b/crates/ra_ide/src/snapshots/highlighting.html | |||
@@ -26,7 +26,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
26 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 26 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
27 | .control { font-style: italic; } | 27 | .control { font-style: italic; } |
28 | </style> | 28 | </style> |
29 | <pre><code><span class="attribute">#</span><span class="attribute">[</span><span class="attribute">derive</span><span class="attribute">(</span><span class="attribute">Clone</span><span class="attribute">,</span><span class="attribute"> </span><span class="attribute">Debug</span><span class="attribute">)</span><span class="attribute">]</span> | 29 | <pre><code><span class="attribute">#[derive(Clone, Debug)]</span> |
30 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> { | 30 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> { |
31 | <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>, | 31 | <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>, |
32 | <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, | 32 | <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, |
@@ -36,11 +36,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
36 | <span class="function">foo</span>::<<span class="lifetime">'a</span>, <span class="builtin_type">i32</span>>() | 36 | <span class="function">foo</span>::<<span class="lifetime">'a</span>, <span class="builtin_type">i32</span>>() |
37 | } | 37 | } |
38 | 38 | ||
39 | <span class="macro">macro_rules</span><span class="macro">!</span> def_fn { | 39 | <span class="macro">macro_rules!</span> def_fn { |
40 | ($($tt:tt)*) => {$($tt)*} | 40 | ($($tt:tt)*) => {$($tt)*} |
41 | } | 41 | } |
42 | 42 | ||
43 | <span class="macro">def_fn</span><span class="macro">!</span> { | 43 | <span class="macro">def_fn!</span> { |
44 | <span class="keyword">fn</span> <span class="function declaration">bar</span>() -> <span class="builtin_type">u32</span> { | 44 | <span class="keyword">fn</span> <span class="function declaration">bar</span>() -> <span class="builtin_type">u32</span> { |
45 | <span class="numeric_literal">100</span> | 45 | <span class="numeric_literal">100</span> |
46 | } | 46 | } |
@@ -48,7 +48,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
48 | 48 | ||
49 | <span class="comment">// comment</span> | 49 | <span class="comment">// comment</span> |
50 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { | 50 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { |
51 | <span class="macro">println</span><span class="macro">!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); | 51 | <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); |
52 | 52 | ||
53 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> = Vec::new(); | 53 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> = Vec::new(); |
54 | <span class="keyword control">if</span> <span class="keyword">true</span> { | 54 | <span class="keyword control">if</span> <span class="keyword">true</span> { |
@@ -73,7 +73,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
73 | <span class="keyword">impl</span><<span class="type_param declaration">T</span>> <span class="enum">Option</span><<span class="type_param">T</span>> { | 73 | <span class="keyword">impl</span><<span class="type_param declaration">T</span>> <span class="enum">Option</span><<span class="type_param">T</span>> { |
74 | <span class="keyword">fn</span> <span class="function declaration">and</span><<span class="type_param declaration">U</span>>(<span class="keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span><<span class="type_param">U</span>>) -> <span class="enum">Option</span><(<span class="type_param">T</span>, <span class="type_param">U</span>)> { | 74 | <span class="keyword">fn</span> <span class="function declaration">and</span><<span class="type_param declaration">U</span>>(<span class="keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span><<span class="type_param">U</span>>) -> <span class="enum">Option</span><(<span class="type_param">T</span>, <span class="type_param">U</span>)> { |
75 | <span class="keyword control">match</span> <span class="variable">other</span> { | 75 | <span class="keyword control">match</span> <span class="variable">other</span> { |
76 | <span class="enum_variant">None</span> => <span class="macro">unimplemented</span><span class="macro">!</span>(), | 76 | <span class="enum_variant">None</span> => <span class="macro">unimplemented!</span>(), |
77 | <span class="variable declaration">Nope</span> => <span class="variable">Nope</span>, | 77 | <span class="variable declaration">Nope</span> => <span class="variable">Nope</span>, |
78 | } | 78 | } |
79 | } | 79 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index eb1c54639..7d908f987 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -174,7 +174,13 @@ pub(crate) fn highlight( | |||
174 | } | 174 | } |
175 | 175 | ||
176 | assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element"); | 176 | assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element"); |
177 | res.pop().unwrap() | 177 | let res = res.pop().unwrap(); |
178 | // Check that ranges are sorted and disjoint | ||
179 | assert!(res | ||
180 | .iter() | ||
181 | .zip(res.iter().skip(1)) | ||
182 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
183 | res | ||
178 | } | 184 | } |
179 | 185 | ||
180 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 186 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { |
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs index e13766c9d..4496529a1 100644 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ b/crates/ra_ide/src/syntax_highlighting/html.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | //! Renders a bit of code as HTML. | 1 | //! Renders a bit of code as HTML. |
2 | 2 | ||
3 | use ra_db::SourceDatabase; | 3 | use ra_db::SourceDatabase; |
4 | use ra_syntax::AstNode; | 4 | use ra_syntax::{AstNode, TextUnit}; |
5 | 5 | ||
6 | use crate::{FileId, HighlightedRange, RootDatabase}; | 6 | use crate::{FileId, RootDatabase}; |
7 | 7 | ||
8 | use super::highlight; | 8 | use super::highlight; |
9 | 9 | ||
@@ -21,51 +21,35 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo | |||
21 | ) | 21 | ) |
22 | } | 22 | } |
23 | 23 | ||
24 | let mut ranges = highlight(db, file_id, None); | 24 | let ranges = highlight(db, file_id, None); |
25 | ranges.sort_by_key(|it| it.range.start()); | 25 | let text = parse.tree().syntax().to_string(); |
26 | // quick non-optimal heuristic to intersect token ranges and highlighted ranges | 26 | let mut prev_pos = TextUnit::from(0); |
27 | let mut frontier = 0; | ||
28 | let mut could_intersect: Vec<&HighlightedRange> = Vec::new(); | ||
29 | |||
30 | let mut buf = String::new(); | 27 | let mut buf = String::new(); |
31 | buf.push_str(&STYLE); | 28 | buf.push_str(&STYLE); |
32 | buf.push_str("<pre><code>"); | 29 | buf.push_str("<pre><code>"); |
33 | let tokens = parse.tree().syntax().descendants_with_tokens().filter_map(|it| it.into_token()); | 30 | for range in &ranges { |
34 | for token in tokens { | 31 | if range.range.start() > prev_pos { |
35 | could_intersect.retain(|it| token.text_range().start() <= it.range.end()); | 32 | let curr = &text[prev_pos.to_usize()..range.range.start().to_usize()]; |
36 | while let Some(r) = ranges.get(frontier) { | 33 | let text = html_escape(curr); |
37 | if r.range.start() <= token.text_range().end() { | ||
38 | could_intersect.push(r); | ||
39 | frontier += 1; | ||
40 | } else { | ||
41 | break; | ||
42 | } | ||
43 | } | ||
44 | let text = html_escape(&token.text()); | ||
45 | let ranges = could_intersect | ||
46 | .iter() | ||
47 | .filter(|it| token.text_range().is_subrange(&it.range)) | ||
48 | .collect::<Vec<_>>(); | ||
49 | if ranges.is_empty() { | ||
50 | buf.push_str(&text); | 34 | buf.push_str(&text); |
51 | } else { | ||
52 | let classes = ranges | ||
53 | .iter() | ||
54 | .map(|it| it.highlight.to_string().replace('.', " ")) | ||
55 | .collect::<Vec<_>>() | ||
56 | .join(" "); | ||
57 | let binding_hash = ranges.first().and_then(|x| x.binding_hash); | ||
58 | let color = match (rainbow, binding_hash) { | ||
59 | (true, Some(hash)) => format!( | ||
60 | " data-binding-hash=\"{}\" style=\"color: {};\"", | ||
61 | hash, | ||
62 | rainbowify(hash) | ||
63 | ), | ||
64 | _ => "".into(), | ||
65 | }; | ||
66 | buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", classes, color, text)); | ||
67 | } | 35 | } |
36 | let curr = &text[range.range.start().to_usize()..range.range.end().to_usize()]; | ||
37 | |||
38 | let class = range.highlight.to_string().replace('.', " "); | ||
39 | let color = match (rainbow, range.binding_hash) { | ||
40 | (true, Some(hash)) => { | ||
41 | format!(" data-binding-hash=\"{}\" style=\"color: {};\"", hash, rainbowify(hash)) | ||
42 | } | ||
43 | _ => "".into(), | ||
44 | }; | ||
45 | buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", class, color, html_escape(curr))); | ||
46 | |||
47 | prev_pos = range.range.end(); | ||
68 | } | 48 | } |
49 | // Add the remaining (non-highlighted) text | ||
50 | let curr = &text[prev_pos.to_usize()..]; | ||
51 | let text = html_escape(curr); | ||
52 | buf.push_str(&text); | ||
69 | buf.push_str("</code></pre>"); | 53 | buf.push_str("</code></pre>"); |
70 | buf | 54 | buf |
71 | } | 55 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index 7f442bd0f..110887c2a 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -134,16 +134,25 @@ fn test_ranges() { | |||
134 | 134 | ||
135 | #[test] | 135 | #[test] |
136 | fn test_flattening() { | 136 | fn test_flattening() { |
137 | let (analysis, file_id) = single_file(r#"#[cfg(feature = "foo")]"#); | 137 | let (analysis, file_id) = single_file( |
138 | 138 | r##" | |
139 | let highlights = analysis.highlight(file_id).unwrap(); | 139 | fn fixture(ra_fixture: &str) {} |
140 | 140 | ||
141 | // The source code snippet contains 2 nested highlights: | 141 | fn main() { |
142 | // 1) Attribute spanning the whole string | 142 | fixture(r#" |
143 | // 2) The string "foo" | 143 | trait Foo { |
144 | // The resulting flattening splits the attribute range: | 144 | fn foo() { |
145 | assert_eq!(highlights.len(), 3); | 145 | println!("2 + 2 = {}", 4); |
146 | assert_eq!(&highlights[0].highlight.to_string(), "attribute"); | 146 | } |
147 | assert_eq!(&highlights[1].highlight.to_string(), "string_literal"); | 147 | }"# |
148 | assert_eq!(&highlights[2].highlight.to_string(), "attribute"); | 148 | ); |
149 | }"## | ||
150 | .trim(), | ||
151 | ); | ||
152 | |||
153 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_injection.html"); | ||
154 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
155 | let expected_html = &read_text(&dst_file); | ||
156 | fs::write(dst_file, &actual_html).unwrap(); | ||
157 | assert_eq_text!(expected_html, actual_html); | ||
149 | } | 158 | } |