aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r--crates/ra_ide_api/Cargo.toml1
-rw-r--r--crates/ra_ide_api/src/lib.rs6
-rw-r--r--crates/ra_ide_api/src/snapshots/highlighting.html20
-rw-r--r--crates/ra_ide_api/src/snapshots/rainbow_highlighting.html27
-rw-r--r--crates/ra_ide_api/src/syntax_highlighting.rs130
5 files changed, 146 insertions, 38 deletions
diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml
index d399d5e2e..8939e9d79 100644
--- a/crates/ra_ide_api/Cargo.toml
+++ b/crates/ra_ide_api/Cargo.toml
@@ -15,6 +15,7 @@ rustc-hash = "1.0"
15parking_lot = "0.7.0" 15parking_lot = "0.7.0"
16unicase = "2.2.0" 16unicase = "2.2.0"
17superslice = "1.0.0" 17superslice = "1.0.0"
18rand = "0.6.5"
18 19
19jemallocator = { version = "0.1.9", optional = true } 20jemallocator = { version = "0.1.9", optional = true }
20jemalloc-ctl = { version = "0.2.0", optional = true } 21jemalloc-ctl = { version = "0.2.0", optional = true }
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index d3456d5b2..452407e8e 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -463,8 +463,8 @@ impl Analysis {
463 } 463 }
464 464
465 /// Computes syntax highlighting for the given file. 465 /// Computes syntax highlighting for the given file.
466 pub fn highlight_as_html(&self, file_id: FileId) -> Cancelable<String> { 466 pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancelable<String> {
467 self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id)) 467 self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow))
468 } 468 }
469 469
470 /// Computes completions at the given position. 470 /// Computes completions at the given position.
@@ -472,7 +472,7 @@ impl Analysis {
472 self.with_db(|db| completion::completions(db, position).map(Into::into)) 472 self.with_db(|db| completion::completions(db, position).map(Into::into))
473 } 473 }
474 474
475 /// Computes assists (aks code actons aka intentions) for the given 475 /// Computes assists (aka code actions aka intentions) for the given
476 /// position. 476 /// position.
477 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { 477 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> {
478 self.with_db(|db| assists::assists(db, frange)) 478 self.with_db(|db| assists::assists(db, frange))
diff --git a/crates/ra_ide_api/src/snapshots/highlighting.html b/crates/ra_ide_api/src/snapshots/highlighting.html
index bfc0a67b1..ebd187a35 100644
--- a/crates/ra_ide_api/src/snapshots/highlighting.html
+++ b/crates/ra_ide_api/src/snapshots/highlighting.html
@@ -1,10 +1,7 @@
1 1
2<style> 2<style>
3pre { 3body { margin: 0; }
4 color: #DCDCCC; 4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5 background-color: #3F3F3F;
6 font-size: 22px;
7}
8 5
9.comment { color: #7F9F7F; } 6.comment { color: #7F9F7F; }
10.string { color: #CC9393; } 7.string { color: #CC9393; }
@@ -19,10 +16,8 @@ pre {
19.keyword { color: #F0DFAF; } 16.keyword { color: #F0DFAF; }
20.keyword\.unsafe { color: #F0DFAF; font-weight: bold; } 17.keyword\.unsafe { color: #F0DFAF; font-weight: bold; }
21.keyword\.control { color: #DC8CC3; } 18.keyword\.control { color: #DC8CC3; }
22
23</style> 19</style>
24<pre><code> 20<pre><code><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>
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> { 21<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>, 22 <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>, 23 <span class="keyword">pub</span> <span class="function">y</span>: <span class="text">i32</span>,
@@ -36,10 +31,9 @@ pre {
36<span class="keyword">fn</span> <span class="function">main</span>() { 31<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>); 32 <span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal">92</span>);
38 33
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>(); 34 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable" data-binding-hash="9636295041291189729" style="color: hsl(51,57%,74%);">vec</span> = <span class="text">Vec</span>::<span class="text">new</span>();
40 <span class="keyword.control">if</span> <span class="keyword">true</span> { 35 <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> }); 36 <span class="variable" data-binding-hash="8496027264380925433" style="color: hsl(18,48%,55%);">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 } 37 }
43 <span class="keyword.unsafe">unsafe</span> { <span class="text">vec</span>.<span class="text">set_len</span>(<span class="literal">0</span>); } 38 <span class="keyword.unsafe">unsafe</span> { <span class="variable" data-binding-hash="8496027264380925433" style="color: hsl(18,48%,55%);">vec</span>.<span class="text">set_len</span>(<span class="literal">0</span>); }
44} 39}</code></pre> \ No newline at end of file
45</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide_api/src/snapshots/rainbow_highlighting.html b/crates/ra_ide_api/src/snapshots/rainbow_highlighting.html
new file mode 100644
index 000000000..729d129d0
--- /dev/null
+++ b/crates/ra_ide_api/src/snapshots/rainbow_highlighting.html
@@ -0,0 +1,27 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.comment { color: #7F9F7F; }
7.string { color: #CC9393; }
8.function { color: #93E0E3; }
9.parameter { color: #94BFF3; }
10.builtin { color: #DD6718; }
11.text { color: #DCDCCC; }
12.attribute { color: #BFEBBF; }
13.literal { color: #DFAF8F; }
14.macro { color: #DFAF8F; }
15
16.keyword { color: #F0DFAF; }
17.keyword\.unsafe { color: #F0DFAF; font-weight: bold; }
18.keyword\.control { color: #DC8CC3; }
19</style>
20<pre><code><span class="keyword">fn</span> <span class="function">main</span>() {
21 <span class="keyword">let</span> <span class="variable" data-binding-hash="3888301305669440875" style="color: hsl(242,59%,59%);">hello</span> = <span class="string">"hello"</span>;
22 <span class="keyword">let</span> <span class="variable" data-binding-hash="5695551762718493399" style="color: hsl(272,48%,45%);">x</span> = <span class="variable" data-binding-hash="3888301305669440875" style="color: hsl(242,59%,59%);">hello</span>.<span class="text">to_string</span>();
23 <span class="keyword">let</span> <span class="variable" data-binding-hash="5435401749617022797" style="color: hsl(353,77%,74%);">y</span> = <span class="variable" data-binding-hash="3888301305669440875" style="color: hsl(242,59%,59%);">hello</span>.<span class="text">to_string</span>();
24
25 <span class="keyword">let</span> <span class="variable" data-binding-hash="1903207544374197704" style="color: hsl(58,61%,61%);">x</span> = <span class="string">"other color please!"</span>;
26 <span class="keyword">let</span> <span class="variable" data-binding-hash="14878783531007968800" style="color: hsl(265,73%,83%);">y</span> = <span class="variable" data-binding-hash="1903207544374197704" style="color: hsl(58,61%,61%);">x</span>.<span class="text">to_string</span>();
27}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs
index 87e053364..dcefb0513 100644
--- a/crates/ra_ide_api/src/syntax_highlighting.rs
+++ b/crates/ra_ide_api/src/syntax_highlighting.rs
@@ -1,6 +1,6 @@
1use rustc_hash::FxHashSet; 1use rustc_hash::{FxHashSet, FxHashMap};
2 2
3use ra_syntax::{ast, AstNode, TextRange, Direction, SyntaxKind, SyntaxKind::*, SyntaxElement, T}; 3use ra_syntax::{ast, AstNode, TextRange, Direction, SmolStr, SyntaxKind, SyntaxKind::*, SyntaxElement, T};
4use ra_db::SourceDatabase; 4use ra_db::SourceDatabase;
5use ra_prof::profile; 5use ra_prof::profile;
6 6
@@ -10,6 +10,7 @@ use crate::{FileId, db::RootDatabase};
10pub struct HighlightedRange { 10pub struct HighlightedRange {
11 pub range: TextRange, 11 pub range: TextRange,
12 pub tag: &'static str, 12 pub tag: &'static str,
13 pub binding_hash: Option<u64>,
13} 14}
14 15
15fn is_control_keyword(kind: SyntaxKind) -> bool { 16fn is_control_keyword(kind: SyntaxKind) -> bool {
@@ -29,22 +30,36 @@ fn is_control_keyword(kind: SyntaxKind) -> bool {
29 30
30pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRange> { 31pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRange> {
31 let _p = profile("highlight"); 32 let _p = profile("highlight");
32
33 let source_file = db.parse(file_id); 33 let source_file = db.parse(file_id);
34 34
35 fn calc_binding_hash(file_id: FileId, text: &SmolStr, shadow_count: u32) -> u64 {
36 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
37 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
38
39 let mut hasher = DefaultHasher::new();
40 x.hash(&mut hasher);
41 hasher.finish()
42 }
43
44 hash((file_id, text, shadow_count))
45 }
46
35 // Visited nodes to handle highlighting priorities 47 // Visited nodes to handle highlighting priorities
36 let mut highlighted: FxHashSet<SyntaxElement> = FxHashSet::default(); 48 let mut highlighted: FxHashSet<SyntaxElement> = FxHashSet::default();
49 let mut bindings_shadow_count: FxHashMap<SmolStr, u32> = FxHashMap::default();
50
37 let mut res = Vec::new(); 51 let mut res = Vec::new();
38 for node in source_file.syntax().descendants_with_tokens() { 52 for node in source_file.syntax().descendants_with_tokens() {
39 if highlighted.contains(&node) { 53 if highlighted.contains(&node) {
40 continue; 54 continue;
41 } 55 }
56 let mut binding_hash = None;
42 let tag = match node.kind() { 57 let tag = match node.kind() {
43 COMMENT => "comment", 58 COMMENT => "comment",
44 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", 59 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string",
45 ATTR => "attribute", 60 ATTR => "attribute",
46 NAME_REF => { 61 NAME_REF => {
47 if let Some(name_ref) = node.as_node().and_then(|n| ast::NameRef::cast(n)) { 62 if let Some(name_ref) = node.as_node().and_then(ast::NameRef::cast) {
48 use crate::name_ref_kind::{classify_name_ref, NameRefKind::*}; 63 use crate::name_ref_kind::{classify_name_ref, NameRefKind::*};
49 use hir::{ModuleDef, ImplItem}; 64 use hir::{ModuleDef, ImplItem};
50 65
@@ -68,7 +83,20 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
68 Some(Def(ModuleDef::Trait(_))) => "type", 83 Some(Def(ModuleDef::Trait(_))) => "type",
69 Some(Def(ModuleDef::TypeAlias(_))) => "type", 84 Some(Def(ModuleDef::TypeAlias(_))) => "type",
70 Some(SelfType(_)) => "type", 85 Some(SelfType(_)) => "type",
71 Some(Pat(_)) => "text", 86 Some(Pat(ptr)) => {
87 binding_hash = Some({
88 let text = ptr
89 .syntax_node_ptr()
90 .to_node(&source_file.syntax())
91 .text()
92 .to_smol_string();
93 let shadow_count =
94 bindings_shadow_count.entry(text.clone()).or_default();
95 calc_binding_hash(file_id, &text, *shadow_count)
96 });
97
98 "variable"
99 }
72 Some(SelfParam(_)) => "type", 100 Some(SelfParam(_)) => "type",
73 Some(GenericParam(_)) => "type", 101 Some(GenericParam(_)) => "type",
74 None => "text", 102 None => "text",
@@ -77,7 +105,24 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
77 "text" 105 "text"
78 } 106 }
79 } 107 }
80 NAME => "function", 108 NAME => {
109 if let Some(name) = node.as_node().and_then(ast::Name::cast) {
110 if name.syntax().ancestors().any(|x| ast::BindPat::cast(x).is_some()) {
111 binding_hash = Some({
112 let text = name.syntax().text().to_smol_string();
113 let shadow_count =
114 bindings_shadow_count.entry(text.clone()).or_insert(0);
115 *shadow_count += 1;
116 calc_binding_hash(file_id, &text, *shadow_count)
117 });
118 "variable"
119 } else {
120 "function"
121 }
122 } else {
123 "text"
124 }
125 }
81 TYPE_ALIAS_DEF | TYPE_ARG | TYPE_PARAM => "type", 126 TYPE_ALIAS_DEF | TYPE_ARG | TYPE_PARAM => "type",
82 INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", 127 INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal",
83 LIFETIME => "parameter", 128 LIFETIME => "parameter",
@@ -85,6 +130,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
85 k if is_control_keyword(k) => "keyword.control", 130 k if is_control_keyword(k) => "keyword.control",
86 k if k.is_keyword() => "keyword", 131 k if k.is_keyword() => "keyword",
87 _ => { 132 _ => {
133 // let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None);
88 if let Some(macro_call) = node.as_node().and_then(ast::MacroCall::cast) { 134 if let Some(macro_call) = node.as_node().and_then(ast::MacroCall::cast) {
89 if let Some(path) = macro_call.path() { 135 if let Some(path) = macro_call.path() {
90 if let Some(segment) = path.segment() { 136 if let Some(segment) = path.segment() {
@@ -101,6 +147,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
101 res.push(HighlightedRange { 147 res.push(HighlightedRange {
102 range: TextRange::from_to(range_start, range_end), 148 range: TextRange::from_to(range_start, range_end),
103 tag: "macro", 149 tag: "macro",
150 binding_hash: None,
104 }) 151 })
105 } 152 }
106 } 153 }
@@ -109,14 +156,25 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
109 continue; 156 continue;
110 } 157 }
111 }; 158 };
112 res.push(HighlightedRange { range: node.range(), tag }) 159 res.push(HighlightedRange { range: node.range(), tag, binding_hash })
113 } 160 }
114 res 161 res
115} 162}
116 163
117pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId) -> String { 164pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
118 let source_file = db.parse(file_id); 165 let source_file = db.parse(file_id);
119 166
167 fn rainbowify(seed: u64) -> String {
168 use rand::prelude::*;
169 let mut rng = SmallRng::seed_from_u64(seed);
170 format!(
171 "hsl({h},{s}%,{l}%)",
172 h = rng.gen_range::<u16, _, _>(0, 361),
173 s = rng.gen_range::<u16, _, _>(42, 99),
174 l = rng.gen_range::<u16, _, _>(40, 91),
175 )
176 }
177
120 let mut ranges = highlight(db, file_id); 178 let mut ranges = highlight(db, file_id);
121 ranges.sort_by_key(|it| it.range.start()); 179 ranges.sort_by_key(|it| it.range.start());
122 // quick non-optimal heuristic to intersect token ranges and highlighted ranges 180 // quick non-optimal heuristic to intersect token ranges and highlighted ranges
@@ -138,16 +196,24 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId) -> String {
138 } 196 }
139 } 197 }
140 let text = html_escape(&token.text()); 198 let text = html_escape(&token.text());
141 let classes = could_intersect 199 let ranges = could_intersect
142 .iter() 200 .iter()
143 .filter(|it| token.range().is_subrange(&it.range)) 201 .filter(|it| token.range().is_subrange(&it.range))
144 .map(|it| it.tag)
145 .collect::<Vec<_>>(); 202 .collect::<Vec<_>>();
146 if classes.is_empty() { 203 if ranges.is_empty() {
147 buf.push_str(&text); 204 buf.push_str(&text);
148 } else { 205 } else {
149 let classes = classes.join(" "); 206 let classes = ranges.iter().map(|x| x.tag).collect::<Vec<_>>().join(" ");
150 buf.push_str(&format!("<span class=\"{}\">{}</span>", classes, text)); 207 let binding_hash = ranges.first().and_then(|x| x.binding_hash);
208 let color = match (rainbow, binding_hash) {
209 (true, Some(hash)) => format!(
210 " data-binding-hash=\"{}\" style=\"color: {};\"",
211 hash,
212 rainbowify(hash)
213 ),
214 _ => "".into(),
215 };
216 buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", classes, color, text));
151 } 217 }
152 } 218 }
153 buf.push_str("</code></pre>"); 219 buf.push_str("</code></pre>");
@@ -161,11 +227,8 @@ fn html_escape(text: &str) -> String {
161 227
162const STYLE: &str = " 228const STYLE: &str = "
163<style> 229<style>
164pre { 230body { margin: 0; }
165 color: #DCDCCC; 231pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
166 background-color: #3F3F3F;
167 font-size: 22px;
168}
169 232
170.comment { color: #7F9F7F; } 233.comment { color: #7F9F7F; }
171.string { color: #CC9393; } 234.string { color: #CC9393; }
@@ -180,7 +243,6 @@ pre {
180.keyword { color: #F0DFAF; } 243.keyword { color: #F0DFAF; }
181.keyword\\.unsafe { color: #F0DFAF; font-weight: bold; } 244.keyword\\.unsafe { color: #F0DFAF; font-weight: bold; }
182.keyword\\.control { color: #DC8CC3; } 245.keyword\\.control { color: #DC8CC3; }
183
184</style> 246</style>
185"; 247";
186 248
@@ -213,12 +275,36 @@ fn main() {
213 } 275 }
214 unsafe { vec.set_len(0); } 276 unsafe { vec.set_len(0); }
215} 277}
216"#, 278"#
279 .trim(),
217 ); 280 );
218 let dst_file = project_dir().join("crates/ra_ide_api/src/snapshots/highlighting.html"); 281 let dst_file = project_dir().join("crates/ra_ide_api/src/snapshots/highlighting.html");
219 let actual_html = &analysis.highlight_as_html(file_id).unwrap(); 282 let actual_html = &analysis.highlight_as_html(file_id, true).unwrap();
283 let expected_html = &read_text(&dst_file);
284 std::fs::write(dst_file, &actual_html).unwrap();
285 assert_eq_text!(expected_html, actual_html);
286 }
287
288 #[test]
289 fn test_rainbow_highlighting() {
290 let (analysis, file_id) = single_file(
291 r#"
292fn main() {
293 let hello = "hello";
294 let x = hello.to_string();
295 let y = hello.to_string();
296
297 let x = "other color please!";
298 let y = x.to_string();
299}
300"#
301 .trim(),
302 );
303 let dst_file =
304 project_dir().join("crates/ra_ide_api/src/snapshots/rainbow_highlighting.html");
305 let actual_html = &analysis.highlight_as_html(file_id, true).unwrap();
220 let expected_html = &read_text(&dst_file); 306 let expected_html = &read_text(&dst_file);
221 // std::fs::write(dst_file, &actual_html).unwrap(); 307 std::fs::write(dst_file, &actual_html).unwrap();
222 assert_eq_text!(expected_html, actual_html); 308 assert_eq_text!(expected_html, actual_html);
223 } 309 }
224} 310}