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/html.rs106
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs175
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs133
3 files changed, 414 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";
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
new file mode 100644
index 000000000..8835a5de2
--- /dev/null
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -0,0 +1,175 @@
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 Attribute,
18 BuiltinType,
19 ByteLiteral,
20 CharLiteral,
21 Comment,
22 Constant,
23 Enum,
24 EnumVariant,
25 Field,
26 Function,
27 Keyword,
28 Lifetime,
29 Macro,
30 Module,
31 NumericLiteral,
32 SelfType,
33 Static,
34 StringLiteral,
35 Struct,
36 Trait,
37 TypeAlias,
38 TypeParam,
39 Union,
40 Local,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
44#[repr(u8)]
45pub enum HighlightModifier {
46 /// Used with keywords like `if` and `break`.
47 Control = 0,
48 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
49 /// not.
50 Definition,
51 Mutable,
52 Unsafe,
53}
54
55impl HighlightTag {
56 fn as_str(self) -> &'static str {
57 match self {
58 HighlightTag::Attribute => "attribute",
59 HighlightTag::BuiltinType => "builtin_type",
60 HighlightTag::ByteLiteral => "byte_literal",
61 HighlightTag::CharLiteral => "char_literal",
62 HighlightTag::Comment => "comment",
63 HighlightTag::Constant => "constant",
64 HighlightTag::Enum => "enum",
65 HighlightTag::EnumVariant => "enum_variant",
66 HighlightTag::Field => "field",
67 HighlightTag::Function => "function",
68 HighlightTag::Keyword => "keyword",
69 HighlightTag::Lifetime => "lifetime",
70 HighlightTag::Macro => "macro",
71 HighlightTag::Module => "module",
72 HighlightTag::NumericLiteral => "numeric_literal",
73 HighlightTag::SelfType => "self_type",
74 HighlightTag::Static => "static",
75 HighlightTag::StringLiteral => "string_literal",
76 HighlightTag::Struct => "struct",
77 HighlightTag::Trait => "trait",
78 HighlightTag::TypeAlias => "type_alias",
79 HighlightTag::TypeParam => "type_param",
80 HighlightTag::Union => "union",
81 HighlightTag::Local => "variable",
82 }
83 }
84}
85
86impl fmt::Display for HighlightTag {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 fmt::Display::fmt(self.as_str(), f)
89 }
90}
91
92impl HighlightModifier {
93 const ALL: &'static [HighlightModifier] = &[
94 HighlightModifier::Control,
95 HighlightModifier::Definition,
96 HighlightModifier::Mutable,
97 HighlightModifier::Unsafe,
98 ];
99
100 fn as_str(self) -> &'static str {
101 match self {
102 HighlightModifier::Control => "control",
103 HighlightModifier::Definition => "declaration",
104 HighlightModifier::Mutable => "mutable",
105 HighlightModifier::Unsafe => "unsafe",
106 }
107 }
108
109 fn mask(self) -> u32 {
110 1 << (self as u32)
111 }
112}
113
114impl fmt::Display for HighlightModifier {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 fmt::Display::fmt(self.as_str(), f)
117 }
118}
119
120impl fmt::Display for Highlight {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 write!(f, "{}", self.tag)?;
123 for modifier in self.modifiers.iter() {
124 write!(f, ".{}", modifier)?
125 }
126 Ok(())
127 }
128}
129
130impl From<HighlightTag> for Highlight {
131 fn from(tag: HighlightTag) -> Highlight {
132 Highlight::new(tag)
133 }
134}
135
136impl Highlight {
137 pub(crate) fn new(tag: HighlightTag) -> Highlight {
138 Highlight { tag, modifiers: HighlightModifiers::default() }
139 }
140}
141
142impl ops::BitOr<HighlightModifier> for HighlightTag {
143 type Output = Highlight;
144
145 fn bitor(self, rhs: HighlightModifier) -> Highlight {
146 Highlight::new(self) | rhs
147 }
148}
149
150impl ops::BitOrAssign<HighlightModifier> for HighlightModifiers {
151 fn bitor_assign(&mut self, rhs: HighlightModifier) {
152 self.0 |= rhs.mask();
153 }
154}
155
156impl ops::BitOrAssign<HighlightModifier> for Highlight {
157 fn bitor_assign(&mut self, rhs: HighlightModifier) {
158 self.modifiers |= rhs;
159 }
160}
161
162impl ops::BitOr<HighlightModifier> for Highlight {
163 type Output = Highlight;
164
165 fn bitor(mut self, rhs: HighlightModifier) -> Highlight {
166 self |= rhs;
167 self
168 }
169}
170
171impl HighlightModifiers {
172 pub fn iter(self) -> impl Iterator<Item = HighlightModifier> {
173 HighlightModifier::ALL.iter().copied().filter(move |it| self.0 & it.mask() == it.mask())
174 }
175}
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
new file mode 100644
index 000000000..98c030791
--- /dev/null
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -0,0 +1,133 @@
1use std::fs;
2
3use test_utils::{assert_eq_text, project_dir, read_text};
4
5use crate::{
6 mock_analysis::{single_file, MockAnalysis},
7 FileRange, TextRange,
8};
9
10#[test]
11fn test_highlighting() {
12 let (analysis, file_id) = single_file(
13 r#"
14#[derive(Clone, Debug)]
15struct Foo {
16 pub x: i32,
17 pub y: i32,
18}
19
20fn foo<'a, T>() -> T {
21 foo::<'a, i32>()
22}
23
24macro_rules! def_fn {
25 ($($tt:tt)*) => {$($tt)*}
26}
27
28def_fn! {
29 fn bar() -> u32 {
30 100
31 }
32}
33
34// comment
35fn main() {
36 println!("Hello, {}!", 92);
37
38 let mut vec = Vec::new();
39 if true {
40 let x = 92;
41 vec.push(Foo { x, y: 1 });
42 }
43 unsafe { vec.set_len(0); }
44
45 let mut x = 42;
46 let y = &mut x;
47 let z = &y;
48
49 y;
50}
51
52enum Option<T> {
53 Some(T),
54 None,
55}
56use Option::*;
57
58impl<T> Option<T> {
59 fn and<U>(self, other: Option<U>) -> Option<(T, U)> {
60 match other {
61 None => unimplemented!(),
62 Nope => Nope,
63 }
64 }
65}
66"#
67 .trim(),
68 );
69 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlighting.html");
70 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
71 let expected_html = &read_text(&dst_file);
72 fs::write(dst_file, &actual_html).unwrap();
73 assert_eq_text!(expected_html, actual_html);
74}
75
76#[test]
77fn test_rainbow_highlighting() {
78 let (analysis, file_id) = single_file(
79 r#"
80fn main() {
81 let hello = "hello";
82 let x = hello.to_string();
83 let y = hello.to_string();
84
85 let x = "other color please!";
86 let y = x.to_string();
87}
88
89fn bar() {
90 let mut hello = "hello";
91}
92"#
93 .trim(),
94 );
95 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html");
96 let actual_html = &analysis.highlight_as_html(file_id, true).unwrap();
97 let expected_html = &read_text(&dst_file);
98 fs::write(dst_file, &actual_html).unwrap();
99 assert_eq_text!(expected_html, actual_html);
100}
101
102#[test]
103fn accidentally_quadratic() {
104 let file = project_dir().join("crates/ra_syntax/test_data/accidentally_quadratic");
105 let src = fs::read_to_string(file).unwrap();
106
107 let mut mock = MockAnalysis::new();
108 let file_id = mock.add_file("/main.rs", &src);
109 let host = mock.analysis_host();
110
111 // let t = std::time::Instant::now();
112 let _ = host.analysis().highlight(file_id).unwrap();
113 // eprintln!("elapsed: {:?}", t.elapsed());
114}
115
116#[test]
117fn test_ranges() {
118 let (analysis, file_id) = single_file(
119 r#"
120 #[derive(Clone, Debug)]
121 struct Foo {
122 pub x: i32,
123 pub y: i32,
124 }"#,
125 );
126
127 // The "x"
128 let highlights = &analysis
129 .highlight_range(FileRange { file_id, range: TextRange::offset_len(82.into(), 1.into()) })
130 .unwrap();
131
132 assert_eq!(&highlights[0].highlight.to_string(), "field.declaration");
133}