diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-05-25 11:48:47 +0100 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-05-25 11:48:47 +0100 |
commit | 91bd783273477d5709ea4bb891355e9aa9a7cdfe (patch) | |
tree | 606a17d688e931e35fe45eefd6c13c0baf7bd5bb /crates | |
parent | 9800699bab04e97996f0aebec528714165a2619b (diff) | |
parent | c6e905a79f7ba083b3f97728aa3a74fb0e03661b (diff) |
Merge #1327
1327: Colorize Rust code as HTML r=matklad a=matklad
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_cli/src/main.rs | 8 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 5 | ||||
-rw-r--r-- | crates/ra_ide_api/src/snapshots/highlighting.html | 45 | ||||
-rw-r--r-- | crates/ra_ide_api/src/snapshots/tests__highlighting.snap | 146 | ||||
-rw-r--r-- | crates/ra_ide_api/src/syntax_highlighting.rs | 87 |
5 files changed, 138 insertions, 153 deletions
diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index f11d0e6bd..93aba4c70 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs | |||
@@ -3,7 +3,7 @@ mod analysis_stats; | |||
3 | use std::io::Read; | 3 | use std::io::Read; |
4 | 4 | ||
5 | use clap::{App, Arg, SubCommand}; | 5 | use clap::{App, Arg, SubCommand}; |
6 | use ra_ide_api::file_structure; | 6 | use ra_ide_api::{file_structure, Analysis}; |
7 | use ra_syntax::{SourceFile, TreeArc, AstNode}; | 7 | use ra_syntax::{SourceFile, TreeArc, AstNode}; |
8 | use flexi_logger::Logger; | 8 | use flexi_logger::Logger; |
9 | use ra_prof::profile; | 9 | use ra_prof::profile; |
@@ -16,6 +16,7 @@ fn main() -> Result<()> { | |||
16 | .setting(clap::AppSettings::SubcommandRequiredElseHelp) | 16 | .setting(clap::AppSettings::SubcommandRequiredElseHelp) |
17 | .subcommand(SubCommand::with_name("parse").arg(Arg::with_name("no-dump").long("--no-dump"))) | 17 | .subcommand(SubCommand::with_name("parse").arg(Arg::with_name("no-dump").long("--no-dump"))) |
18 | .subcommand(SubCommand::with_name("symbols")) | 18 | .subcommand(SubCommand::with_name("symbols")) |
19 | .subcommand(SubCommand::with_name("highlight")) | ||
19 | .subcommand( | 20 | .subcommand( |
20 | SubCommand::with_name("analysis-stats") | 21 | SubCommand::with_name("analysis-stats") |
21 | .arg(Arg::with_name("verbose").short("v").long("verbose")) | 22 | .arg(Arg::with_name("verbose").short("v").long("verbose")) |
@@ -38,6 +39,11 @@ fn main() -> Result<()> { | |||
38 | println!("{:?}", s); | 39 | println!("{:?}", s); |
39 | } | 40 | } |
40 | } | 41 | } |
42 | ("highlight", _) => { | ||
43 | let (analysis, file_id) = Analysis::from_single_file(read_stdin()?); | ||
44 | let html = analysis.highlight_as_html(file_id).unwrap(); | ||
45 | println!("{}", html); | ||
46 | } | ||
41 | ("analysis-stats", Some(matches)) => { | 47 | ("analysis-stats", Some(matches)) => { |
42 | let verbose = matches.is_present("verbose"); | 48 | let verbose = matches.is_present("verbose"); |
43 | let path = matches.value_of("path").unwrap_or(""); | 49 | let path = matches.value_of("path").unwrap_or(""); |
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index f78348f74..d3456d5b2 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs | |||
@@ -462,6 +462,11 @@ impl Analysis { | |||
462 | self.with_db(|db| syntax_highlighting::highlight(db, file_id)) | 462 | self.with_db(|db| syntax_highlighting::highlight(db, file_id)) |
463 | } | 463 | } |
464 | 464 | ||
465 | /// Computes syntax highlighting for the given file. | ||
466 | pub fn highlight_as_html(&self, file_id: FileId) -> Cancelable<String> { | ||
467 | self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id)) | ||
468 | } | ||
469 | |||
465 | /// Computes completions at the given position. | 470 | /// Computes completions at the given position. |
466 | pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { | 471 | pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { |
467 | self.with_db(|db| completion::completions(db, position).map(Into::into)) | 472 | self.with_db(|db| completion::completions(db, position).map(Into::into)) |
diff --git a/crates/ra_ide_api/src/snapshots/highlighting.html b/crates/ra_ide_api/src/snapshots/highlighting.html new file mode 100644 index 000000000..bfc0a67b1 --- /dev/null +++ b/crates/ra_ide_api/src/snapshots/highlighting.html | |||
@@ -0,0 +1,45 @@ | |||
1 | |||
2 | <style> | ||
3 | pre { | ||
4 | color: #DCDCCC; | ||
5 | background-color: #3F3F3F; | ||
6 | font-size: 22px; | ||
7 | } | ||
8 | |||
9 | .comment { color: #7F9F7F; } | ||
10 | .string { color: #CC9393; } | ||
11 | .function { color: #93E0E3; } | ||
12 | .parameter { color: #94BFF3; } | ||
13 | .builtin { color: #DD6718; } | ||
14 | .text { color: #DCDCCC; } | ||
15 | .attribute { color: #BFEBBF; } | ||
16 | .literal { color: #DFAF8F; } | ||
17 | .macro { color: #DFAF8F; } | ||
18 | |||
19 | .keyword { color: #F0DFAF; } | ||
20 | .keyword\.unsafe { color: #F0DFAF; font-weight: bold; } | ||
21 | .keyword\.control { color: #DC8CC3; } | ||
22 | |||
23 | </style> | ||
24 | <pre><code> | ||
25 | <span class="attribute">#</span><span class="attribute">[</span><span class="attribute">derive</span><span class="attribute">(</span><span class="attribute">Clone</span><span class="attribute">,</span><span class="attribute"> </span><span class="attribute">Debug</span><span class="attribute">)</span><span class="attribute">]</span> | ||
26 | <span class="keyword">struct</span> <span class="function">Foo</span> { | ||
27 | <span class="keyword">pub</span> <span class="function">x</span>: <span class="text">i32</span>, | ||
28 | <span class="keyword">pub</span> <span class="function">y</span>: <span class="text">i32</span>, | ||
29 | } | ||
30 | |||
31 | <span class="keyword">fn</span> <span class="function">foo</span><<span class="type function">T</span>>() -> <span class="type">T</span> { | ||
32 | <span class="macro">unimplemented</span><span class="macro">!</span>(); | ||
33 | } | ||
34 | |||
35 | <span class="comment">// comment</span> | ||
36 | <span class="keyword">fn</span> <span class="function">main</span>() { | ||
37 | <span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal">92</span>); | ||
38 | |||
39 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="function">vec</span> = <span class="text">Vec</span>::<span class="text">new</span>(); | ||
40 | <span class="keyword.control">if</span> <span class="keyword">true</span> { | ||
41 | <span class="text">vec</span>.<span class="text">push</span>(<span class="type">Foo</span> { <span class="field">x</span>: <span class="literal">0</span>, <span class="field">y</span>: <span class="literal">1</span> }); | ||
42 | } | ||
43 | <span class="keyword.unsafe">unsafe</span> { <span class="text">vec</span>.<span class="text">set_len</span>(<span class="literal">0</span>); } | ||
44 | } | ||
45 | </code></pre> \ No newline at end of file | ||
diff --git a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap deleted file mode 100644 index 9c60aed2a..000000000 --- a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap +++ /dev/null | |||
@@ -1,146 +0,0 @@ | |||
1 | --- | ||
2 | created: "2019-05-23T22:23:35.242742395Z" | ||
3 | creator: [email protected] | ||
4 | source: crates/ra_ide_api/src/syntax_highlighting.rs | ||
5 | expression: result | ||
6 | --- | ||
7 | Ok( | ||
8 | [ | ||
9 | HighlightedRange { | ||
10 | range: [1; 24), | ||
11 | tag: "attribute", | ||
12 | }, | ||
13 | HighlightedRange { | ||
14 | range: [25; 31), | ||
15 | tag: "keyword", | ||
16 | }, | ||
17 | HighlightedRange { | ||
18 | range: [32; 35), | ||
19 | tag: "function", | ||
20 | }, | ||
21 | HighlightedRange { | ||
22 | range: [42; 45), | ||
23 | tag: "keyword", | ||
24 | }, | ||
25 | HighlightedRange { | ||
26 | range: [46; 47), | ||
27 | tag: "function", | ||
28 | }, | ||
29 | HighlightedRange { | ||
30 | range: [49; 52), | ||
31 | tag: "text", | ||
32 | }, | ||
33 | HighlightedRange { | ||
34 | range: [58; 61), | ||
35 | tag: "keyword", | ||
36 | }, | ||
37 | HighlightedRange { | ||
38 | range: [62; 63), | ||
39 | tag: "function", | ||
40 | }, | ||
41 | HighlightedRange { | ||
42 | range: [65; 68), | ||
43 | tag: "text", | ||
44 | }, | ||
45 | HighlightedRange { | ||
46 | range: [73; 75), | ||
47 | tag: "keyword", | ||
48 | }, | ||
49 | HighlightedRange { | ||
50 | range: [76; 79), | ||
51 | tag: "function", | ||
52 | }, | ||
53 | HighlightedRange { | ||
54 | range: [80; 81), | ||
55 | tag: "type", | ||
56 | }, | ||
57 | HighlightedRange { | ||
58 | range: [80; 81), | ||
59 | tag: "function", | ||
60 | }, | ||
61 | HighlightedRange { | ||
62 | range: [88; 89), | ||
63 | tag: "type", | ||
64 | }, | ||
65 | HighlightedRange { | ||
66 | range: [96; 110), | ||
67 | tag: "macro", | ||
68 | }, | ||
69 | HighlightedRange { | ||
70 | range: [117; 127), | ||
71 | tag: "comment", | ||
72 | }, | ||
73 | HighlightedRange { | ||
74 | range: [128; 130), | ||
75 | tag: "keyword", | ||
76 | }, | ||
77 | HighlightedRange { | ||
78 | range: [131; 135), | ||
79 | tag: "function", | ||
80 | }, | ||
81 | HighlightedRange { | ||
82 | range: [145; 153), | ||
83 | tag: "macro", | ||
84 | }, | ||
85 | HighlightedRange { | ||
86 | range: [154; 166), | ||
87 | tag: "string", | ||
88 | }, | ||
89 | HighlightedRange { | ||
90 | range: [168; 170), | ||
91 | tag: "literal", | ||
92 | }, | ||
93 | HighlightedRange { | ||
94 | range: [178; 181), | ||
95 | tag: "keyword", | ||
96 | }, | ||
97 | HighlightedRange { | ||
98 | range: [182; 185), | ||
99 | tag: "keyword", | ||
100 | }, | ||
101 | HighlightedRange { | ||
102 | range: [186; 189), | ||
103 | tag: "macro", | ||
104 | }, | ||
105 | HighlightedRange { | ||
106 | range: [197; 200), | ||
107 | tag: "macro", | ||
108 | }, | ||
109 | HighlightedRange { | ||
110 | range: [192; 195), | ||
111 | tag: "text", | ||
112 | }, | ||
113 | HighlightedRange { | ||
114 | range: [208; 211), | ||
115 | tag: "macro", | ||
116 | }, | ||
117 | HighlightedRange { | ||
118 | range: [212; 216), | ||
119 | tag: "macro", | ||
120 | }, | ||
121 | HighlightedRange { | ||
122 | range: [226; 227), | ||
123 | tag: "literal", | ||
124 | }, | ||
125 | HighlightedRange { | ||
126 | range: [232; 233), | ||
127 | tag: "literal", | ||
128 | }, | ||
129 | HighlightedRange { | ||
130 | range: [242; 248), | ||
131 | tag: "keyword.unsafe", | ||
132 | }, | ||
133 | HighlightedRange { | ||
134 | range: [251; 254), | ||
135 | tag: "text", | ||
136 | }, | ||
137 | HighlightedRange { | ||
138 | range: [255; 262), | ||
139 | tag: "text", | ||
140 | }, | ||
141 | HighlightedRange { | ||
142 | range: [263; 264), | ||
143 | tag: "literal", | ||
144 | }, | ||
145 | ], | ||
146 | ) | ||
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 | ||
117 | pub(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 | ||
158 | fn html_escape(text: &str) -> String { | ||
159 | text.replace("<", "<").replace(">", ">") | ||
160 | } | ||
161 | |||
162 | const STYLE: &str = " | ||
163 | <style> | ||
164 | pre { | ||
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)] |
118 | mod tests { | 188 | mod 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 |
138 | fn main() {} | 207 | fn 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 | } |