aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs100
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs104
2 files changed, 107 insertions, 97 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index c0f13f171..4e95b9ce5 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -1,9 +1,9 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3mod highlight; 3mod highlight;
4mod html;
4 5
5use hir::{Name, Semantics}; 6use hir::{Name, Semantics};
6use ra_db::SourceDatabase;
7use ra_ide_db::{ 7use ra_ide_db::{
8 defs::{classify_name, NameDefinition}, 8 defs::{classify_name, NameDefinition},
9 RootDatabase, 9 RootDatabase,
@@ -19,6 +19,8 @@ use crate::{references::classify_name_ref, FileId};
19 19
20pub use highlight::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; 20pub use highlight::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
21 21
22pub(crate) use html::highlight_as_html;
23
22#[derive(Debug)] 24#[derive(Debug)]
23pub struct HighlightedRange { 25pub struct HighlightedRange {
24 pub range: TextRange, 26 pub range: TextRange,
@@ -257,69 +259,6 @@ fn highlight_node(
257 } 259 }
258} 260}
259 261
260pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
261 let parse = db.parse(file_id);
262
263 fn rainbowify(seed: u64) -> String {
264 use rand::prelude::*;
265 let mut rng = SmallRng::seed_from_u64(seed);
266 format!(
267 "hsl({h},{s}%,{l}%)",
268 h = rng.gen_range::<u16, _, _>(0, 361),
269 s = rng.gen_range::<u16, _, _>(42, 99),
270 l = rng.gen_range::<u16, _, _>(40, 91),
271 )
272 }
273
274 let mut ranges = highlight(db, file_id, None);
275 ranges.sort_by_key(|it| it.range.start());
276 // quick non-optimal heuristic to intersect token ranges and highlighted ranges
277 let mut frontier = 0;
278 let mut could_intersect: Vec<&HighlightedRange> = Vec::new();
279
280 let mut buf = String::new();
281 buf.push_str(&STYLE);
282 buf.push_str("<pre><code>");
283 let tokens = parse.tree().syntax().descendants_with_tokens().filter_map(|it| it.into_token());
284 for token in tokens {
285 could_intersect.retain(|it| token.text_range().start() <= it.range.end());
286 while let Some(r) = ranges.get(frontier) {
287 if r.range.start() <= token.text_range().end() {
288 could_intersect.push(r);
289 frontier += 1;
290 } else {
291 break;
292 }
293 }
294 let text = html_escape(&token.text());
295 let ranges = could_intersect
296 .iter()
297 .filter(|it| token.text_range().is_subrange(&it.range))
298 .collect::<Vec<_>>();
299 if ranges.is_empty() {
300 buf.push_str(&text);
301 } else {
302 let classes = ranges
303 .iter()
304 .map(|it| it.highlight.to_string().replace('.', " "))
305 .collect::<Vec<_>>()
306 .join(" ");
307 let binding_hash = ranges.first().and_then(|x| x.binding_hash);
308 let color = match (rainbow, binding_hash) {
309 (true, Some(hash)) => format!(
310 " data-binding-hash=\"{}\" style=\"color: {};\"",
311 hash,
312 rainbowify(hash)
313 ),
314 _ => "".into(),
315 };
316 buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", classes, color, text));
317 }
318 }
319 buf.push_str("</code></pre>");
320 buf
321}
322
323fn highlight_name(db: &RootDatabase, def: NameDefinition) -> Highlight { 262fn highlight_name(db: &RootDatabase, def: NameDefinition) -> Highlight {
324 match def { 263 match def {
325 NameDefinition::Macro(_) => HighlightTag::Macro, 264 NameDefinition::Macro(_) => HighlightTag::Macro,
@@ -348,39 +287,6 @@ fn highlight_name(db: &RootDatabase, def: NameDefinition) -> Highlight {
348 .into() 287 .into()
349} 288}
350 289
351//FIXME: like, real html escaping
352fn html_escape(text: &str) -> String {
353 text.replace("<", "&lt;").replace(">", "&gt;")
354}
355
356const STYLE: &str = "
357<style>
358body { margin: 0; }
359pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
360
361.comment { color: #7F9F7F; }
362.string { color: #CC9393; }
363.field { color: #94BFF3; }
364.function { color: #93E0E3; }
365.parameter { color: #94BFF3; }
366.text { color: #DCDCCC; }
367.type { color: #7CB8BB; }
368.type.builtin { color: #8CD0D3; }
369.type.param { color: #20999D; }
370.attribute { color: #94BFF3; }
371.literal { color: #BFEBBF; }
372.literal.numeric { color: #6A8759; }
373.macro { color: #94BFF3; }
374.module { color: #AFD8AF; }
375.variable { color: #DCDCCC; }
376.variable.mut { color: #DCDCCC; text-decoration: underline; }
377
378.keyword { color: #F0DFAF; }
379.keyword.unsafe { color: #DFAF8F; }
380.keyword.control { color: #F0DFAF; font-weight: bold; }
381</style>
382";
383
384#[cfg(test)] 290#[cfg(test)]
385mod tests { 291mod tests {
386 use std::fs; 292 use std::fs;
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..210d9a57b
--- /dev/null
+++ b/crates/ra_ide/src/syntax_highlighting/html.rs
@@ -0,0 +1,104 @@
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.comment { color: #7F9F7F; }
84.string { color: #CC9393; }
85.field { color: #94BFF3; }
86.function { color: #93E0E3; }
87.parameter { color: #94BFF3; }
88.text { color: #DCDCCC; }
89.type { color: #7CB8BB; }
90.type.builtin { color: #8CD0D3; }
91.type.param { color: #20999D; }
92.attribute { color: #94BFF3; }
93.literal { color: #BFEBBF; }
94.literal.numeric { color: #6A8759; }
95.macro { color: #94BFF3; }
96.module { color: #AFD8AF; }
97.variable { color: #DCDCCC; }
98.variable.mut { color: #DCDCCC; text-decoration: underline; }
99
100.keyword { color: #F0DFAF; }
101.keyword.unsafe { color: #DFAF8F; }
102.keyword.control { color: #F0DFAF; font-weight: bold; }
103</style>
104";