aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/syntax_highlighting.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src/syntax_highlighting.rs')
-rw-r--r--crates/ra_ide_api/src/syntax_highlighting.rs87
1 files changed, 81 insertions, 6 deletions
diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs
index 7bba7a550..87e053364 100644
--- a/crates/ra_ide_api/src/syntax_highlighting.rs
+++ b/crates/ra_ide_api/src/syntax_highlighting.rs
@@ -114,10 +114,79 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
114 res 114 res
115} 115}
116 116
117pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId) -> String {
118 let source_file = db.parse(file_id);
119
120 let mut ranges = highlight(db, file_id);
121 ranges.sort_by_key(|it| it.range.start());
122 // quick non-optimal heuristic to intersect token ranges and highlighted ranges
123 let mut frontier = 0;
124 let mut could_intersect: Vec<&HighlightedRange> = Vec::new();
125
126 let mut buf = String::new();
127 buf.push_str(&STYLE);
128 buf.push_str("<pre><code>");
129 let tokens = source_file.syntax().descendants_with_tokens().filter_map(|it| it.as_token());
130 for token in tokens {
131 could_intersect.retain(|it| token.range().start() <= it.range.end());
132 while let Some(r) = ranges.get(frontier) {
133 if r.range.start() <= token.range().end() {
134 could_intersect.push(r);
135 frontier += 1;
136 } else {
137 break;
138 }
139 }
140 let text = html_escape(&token.text());
141 let classes = could_intersect
142 .iter()
143 .filter(|it| token.range().is_subrange(&it.range))
144 .map(|it| it.tag)
145 .collect::<Vec<_>>();
146 if classes.is_empty() {
147 buf.push_str(&text);
148 } else {
149 let classes = classes.join(" ");
150 buf.push_str(&format!("<span class=\"{}\">{}</span>", classes, text));
151 }
152 }
153 buf.push_str("</code></pre>");
154 buf
155}
156
157//FIXME: like, real html escaping
158fn html_escape(text: &str) -> String {
159 text.replace("<", "&lt;").replace(">", "&gt;")
160}
161
162const STYLE: &str = "
163<style>
164pre {
165 color: #DCDCCC;
166 background-color: #3F3F3F;
167 font-size: 22px;
168}
169
170.comment { color: #7F9F7F; }
171.string { color: #CC9393; }
172.function { color: #93E0E3; }
173.parameter { color: #94BFF3; }
174.builtin { color: #DD6718; }
175.text { color: #DCDCCC; }
176.attribute { color: #BFEBBF; }
177.literal { color: #DFAF8F; }
178.macro { color: #DFAF8F; }
179
180.keyword { color: #F0DFAF; }
181.keyword\\.unsafe { color: #F0DFAF; font-weight: bold; }
182.keyword\\.control { color: #DC8CC3; }
183
184</style>
185";
186
117#[cfg(test)] 187#[cfg(test)]
118mod tests { 188mod tests {
119 use insta::assert_debug_snapshot_matches; 189 use test_utils::{project_dir, read_text, assert_eq_text};
120
121 use crate::mock_analysis::single_file; 190 use crate::mock_analysis::single_file;
122 191
123 #[test] 192 #[test]
@@ -135,15 +204,21 @@ fn foo<T>() -> T {
135} 204}
136 205
137// comment 206// comment
138fn main() {} 207fn main() {
139 println!("Hello, {}!", 92); 208 println!("Hello, {}!", 92);
140 209
141 let mut vec = Vec::new(); 210 let mut vec = Vec::new();
142 vec.push(Foo { x: 0, y: 1 }); 211 if true {
212 vec.push(Foo { x: 0, y: 1 });
213 }
143 unsafe { vec.set_len(0); } 214 unsafe { vec.set_len(0); }
215}
144"#, 216"#,
145 ); 217 );
146 let result = analysis.highlight(file_id); 218 let dst_file = project_dir().join("crates/ra_ide_api/src/snapshots/highlighting.html");
147 assert_debug_snapshot_matches!("highlighting", result); 219 let actual_html = &analysis.highlight_as_html(file_id).unwrap();
220 let expected_html = &read_text(&dst_file);
221 // std::fs::write(dst_file, &actual_html).unwrap();
222 assert_eq_text!(expected_html, actual_html);
148 } 223 }
149} 224}