aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/syntax_highlighting/html.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting/html.rs')
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs106
1 files changed, 106 insertions, 0 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs
new file mode 100644
index 000000000..e13766c9d
--- /dev/null
+++ b/crates/ra_ide/src/syntax_highlighting/html.rs
@@ -0,0 +1,106 @@
1//! Renders a bit of code as HTML.
2
3use ra_db::SourceDatabase;
4use ra_syntax::AstNode;
5
6use crate::{FileId, HighlightedRange, RootDatabase};
7
8use super::highlight;
9
10pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
11 let parse = db.parse(file_id);
12
13 fn rainbowify(seed: u64) -> String {
14 use rand::prelude::*;
15 let mut rng = SmallRng::seed_from_u64(seed);
16 format!(
17 "hsl({h},{s}%,{l}%)",
18 h = rng.gen_range::<u16, _, _>(0, 361),
19 s = rng.gen_range::<u16, _, _>(42, 99),
20 l = rng.gen_range::<u16, _, _>(40, 91),
21 )
22 }
23
24 let mut ranges = highlight(db, file_id, None);
25 ranges.sort_by_key(|it| it.range.start());
26 // quick non-optimal heuristic to intersect token ranges and highlighted ranges
27 let mut frontier = 0;
28 let mut could_intersect: Vec<&HighlightedRange> = Vec::new();
29
30 let mut buf = String::new();
31 buf.push_str(&STYLE);
32 buf.push_str("<pre><code>");
33 let tokens = parse.tree().syntax().descendants_with_tokens().filter_map(|it| it.into_token());
34 for token in tokens {
35 could_intersect.retain(|it| token.text_range().start() <= it.range.end());
36 while let Some(r) = ranges.get(frontier) {
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);
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 }
68 }
69 buf.push_str("</code></pre>");
70 buf
71}
72
73//FIXME: like, real html escaping
74fn html_escape(text: &str) -> String {
75 text.replace("<", "&lt;").replace(">", "&gt;")
76}
77
78const STYLE: &str = "
79<style>
80body { margin: 0; }
81pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
82
83.lifetime { color: #DFAF8F; font-style: italic; }
84.comment { color: #7F9F7F; }
85.struct, .enum { color: #7CB8BB; }
86.enum_variant { color: #BDE0F3; }
87.string_literal { color: #CC9393; }
88.field { color: #94BFF3; }
89.function { color: #93E0E3; }
90.parameter { color: #94BFF3; }
91.text { color: #DCDCCC; }
92.type { color: #7CB8BB; }
93.builtin_type { color: #8CD0D3; }
94.type_param { color: #DFAF8F; }
95.attribute { color: #94BFF3; }
96.numeric_literal { color: #BFEBBF; }
97.macro { color: #94BFF3; }
98.module { color: #AFD8AF; }
99.variable { color: #DCDCCC; }
100.mutable { text-decoration: underline; }
101
102.keyword { color: #F0DFAF; font-weight: bold; }
103.keyword.unsafe { color: #BC8383; font-weight: bold; }
104.control { font-style: italic; }
105</style>
106";