aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/syntax_highlighting
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting')
-rw-r--r--crates/ra_ide/src/syntax_highlighting/highlight.rs163
-rw-r--r--crates/ra_ide/src/syntax_highlighting/highlight_tag.rs43
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs104
3 files changed, 267 insertions, 43 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting/highlight.rs b/crates/ra_ide/src/syntax_highlighting/highlight.rs
new file mode 100644
index 000000000..383c74c98
--- /dev/null
+++ b/crates/ra_ide/src/syntax_highlighting/highlight.rs
@@ -0,0 +1,163 @@
1//! Defines token tags we use for syntax highlighting.
2//! A tag is not unlike a CSS class.
3
4use std::{fmt, ops};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub struct Highlight {
8 pub tag: HighlightTag,
9 pub modifiers: HighlightModifiers,
10}
11
12#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
13pub struct HighlightModifiers(u32);
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub enum HighlightTag {
17 Field,
18 Function,
19 Module,
20 Constant,
21 Macro,
22 Variable,
23
24 Type,
25 TypeSelf,
26 TypeParam,
27 TypeLifetime,
28
29 LiteralByte,
30 LiteralNumeric,
31 LiteralChar,
32
33 Comment,
34 LiteralString,
35 Attribute,
36
37 Keyword,
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
41#[repr(u8)]
42pub enum HighlightModifier {
43 Mutable = 0,
44 Unsafe,
45 /// Used with keywords like `if` and `break`.
46 Control,
47 Builtin,
48}
49
50impl HighlightTag {
51 fn as_str(self) -> &'static str {
52 match self {
53 HighlightTag::Field => "field",
54 HighlightTag::Function => "function",
55 HighlightTag::Module => "module",
56 HighlightTag::Constant => "constant",
57 HighlightTag::Macro => "macro",
58 HighlightTag::Variable => "variable",
59 HighlightTag::Type => "type",
60 HighlightTag::TypeSelf => "type.self",
61 HighlightTag::TypeParam => "type.param",
62 HighlightTag::TypeLifetime => "type.lifetime",
63 HighlightTag::LiteralByte => "literal.byte",
64 HighlightTag::LiteralNumeric => "literal.numeric",
65 HighlightTag::LiteralChar => "literal.char",
66 HighlightTag::Comment => "comment",
67 HighlightTag::LiteralString => "string",
68 HighlightTag::Attribute => "attribute",
69 HighlightTag::Keyword => "keyword",
70 }
71 }
72}
73
74impl fmt::Display for HighlightTag {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 fmt::Display::fmt(self.as_str(), f)
77 }
78}
79
80impl HighlightModifier {
81 const ALL: &'static [HighlightModifier] = &[
82 HighlightModifier::Mutable,
83 HighlightModifier::Unsafe,
84 HighlightModifier::Control,
85 HighlightModifier::Builtin,
86 ];
87
88 fn as_str(self) -> &'static str {
89 match self {
90 HighlightModifier::Mutable => "mutable",
91 HighlightModifier::Unsafe => "unsafe",
92 HighlightModifier::Control => "control",
93 HighlightModifier::Builtin => "builtin",
94 }
95 }
96
97 fn mask(self) -> u32 {
98 1 << (self as u32)
99 }
100}
101
102impl fmt::Display for HighlightModifier {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 fmt::Display::fmt(self.as_str(), f)
105 }
106}
107
108impl fmt::Display for Highlight {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 write!(f, "{}", self.tag)?;
111 for modifier in self.modifiers.iter() {
112 write!(f, ".{}", modifier)?
113 }
114 Ok(())
115 }
116}
117
118impl From<HighlightTag> for Highlight {
119 fn from(tag: HighlightTag) -> Highlight {
120 Highlight::new(tag)
121 }
122}
123
124impl Highlight {
125 pub(crate) fn new(tag: HighlightTag) -> Highlight {
126 Highlight { tag, modifiers: HighlightModifiers::default() }
127 }
128}
129
130impl ops::BitOr<HighlightModifier> for HighlightTag {
131 type Output = Highlight;
132
133 fn bitor(self, rhs: HighlightModifier) -> Highlight {
134 Highlight::new(self) | rhs
135 }
136}
137
138impl ops::BitOrAssign<HighlightModifier> for HighlightModifiers {
139 fn bitor_assign(&mut self, rhs: HighlightModifier) {
140 self.0 |= rhs.mask();
141 }
142}
143
144impl ops::BitOrAssign<HighlightModifier> for Highlight {
145 fn bitor_assign(&mut self, rhs: HighlightModifier) {
146 self.modifiers |= rhs;
147 }
148}
149
150impl ops::BitOr<HighlightModifier> for Highlight {
151 type Output = Highlight;
152
153 fn bitor(mut self, rhs: HighlightModifier) -> Highlight {
154 self |= rhs;
155 self
156 }
157}
158
159impl HighlightModifiers {
160 pub fn iter(self) -> impl Iterator<Item = HighlightModifier> {
161 HighlightModifier::ALL.iter().copied().filter(move |it| self.0 & it.mask() == it.mask())
162 }
163}
diff --git a/crates/ra_ide/src/syntax_highlighting/highlight_tag.rs b/crates/ra_ide/src/syntax_highlighting/highlight_tag.rs
deleted file mode 100644
index af1ac07b3..000000000
--- a/crates/ra_ide/src/syntax_highlighting/highlight_tag.rs
+++ /dev/null
@@ -1,43 +0,0 @@
1//! Defines token tags we use for syntax highlighting.
2//! A tag is not unlike a CSS class.
3
4use std::fmt;
5
6#[derive(Debug, PartialEq, Eq, Clone, Copy)]
7pub struct HighlightTag(&'static str);
8
9impl fmt::Display for HighlightTag {
10 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11 fmt::Display::fmt(self.0, f)
12 }
13}
14
15#[rustfmt::skip]
16impl HighlightTag {
17 pub const FIELD: HighlightTag = HighlightTag("field");
18 pub const FUNCTION: HighlightTag = HighlightTag("function");
19 pub const MODULE: HighlightTag = HighlightTag("module");
20 pub const CONSTANT: HighlightTag = HighlightTag("constant");
21 pub const MACRO: HighlightTag = HighlightTag("macro");
22
23 pub const VARIABLE: HighlightTag = HighlightTag("variable");
24 pub const VARIABLE_MUT: HighlightTag = HighlightTag("variable.mut");
25
26 pub const TYPE: HighlightTag = HighlightTag("type");
27 pub const TYPE_BUILTIN: HighlightTag = HighlightTag("type.builtin");
28 pub const TYPE_SELF: HighlightTag = HighlightTag("type.self");
29 pub const TYPE_PARAM: HighlightTag = HighlightTag("type.param");
30 pub const TYPE_LIFETIME: HighlightTag = HighlightTag("type.lifetime");
31
32 pub const LITERAL_BYTE: HighlightTag = HighlightTag("literal.byte");
33 pub const LITERAL_NUMERIC: HighlightTag = HighlightTag("literal.numeric");
34 pub const LITERAL_CHAR: HighlightTag = HighlightTag("literal.char");
35
36 pub const LITERAL_COMMENT: HighlightTag = HighlightTag("comment");
37 pub const LITERAL_STRING: HighlightTag = HighlightTag("string");
38 pub const LITERAL_ATTRIBUTE: HighlightTag = HighlightTag("attribute");
39
40 pub const KEYWORD: HighlightTag = HighlightTag("keyword");
41 pub const KEYWORD_UNSAFE: HighlightTag = HighlightTag("keyword.unsafe");
42 pub const KEYWORD_CONTROL: HighlightTag = HighlightTag("keyword.control");
43}
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";