aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-05-27 10:56:06 +0100
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-05-27 10:56:06 +0100
commit0d1c6076073c73f57340e256dc25da9d37311ef0 (patch)
tree60ff1f4a42f8ef297c07d5716af67e3057c8e1bd
parent4b48cff022a1606bde596f01fbf44361640b10d8 (diff)
parent1e6ba1901550fb1610a1a464c48ec358cd3c339c (diff)
Merge #1319
1319: Rainbow highlighting spike 🌈 r=killercup a=killercup Very simple approach: For each identifier, set the hash of the range where it's defined as its 'id' and use it in the VSCode extension to generate unique colors. Thus, the generated colors are per-file. They are also quite fragile, and I'm not entirely sure why. Looks like we need to make sure the same ranges aren't overwritten by a later request? Co-authored-by: Pascal Hertleif <[email protected]>
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_cli/src/main.rs9
-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
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs6
-rw-r--r--crates/ra_lsp_server/src/req.rs1
-rw-r--r--crates/ra_syntax/src/syntax_text.rs8
-rw-r--r--docs/user/features.md9
-rw-r--r--editors/code/package-lock.json10
-rw-r--r--editors/code/package.json7
-rw-r--r--editors/code/src/config.ts7
-rw-r--r--editors/code/src/highlighting.ts46
15 files changed, 242 insertions, 46 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 79e632907..68c5f7874 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1155,6 +1155,7 @@ dependencies = [
1155 "ra_prof 0.1.0", 1155 "ra_prof 0.1.0",
1156 "ra_syntax 0.1.0", 1156 "ra_syntax 0.1.0",
1157 "ra_text_edit 0.1.0", 1157 "ra_text_edit 0.1.0",
1158 "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
1158 "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 1159 "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
1159 "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1160 "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
1160 "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 1161 "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs
index 93aba4c70..84a1564ce 100644
--- a/crates/ra_cli/src/main.rs
+++ b/crates/ra_cli/src/main.rs
@@ -16,7 +16,10 @@ 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::with_name("highlight")
21 .arg(Arg::with_name("rainbow").short("r").long("rainbow")),
22 )
20 .subcommand( 23 .subcommand(
21 SubCommand::with_name("analysis-stats") 24 SubCommand::with_name("analysis-stats")
22 .arg(Arg::with_name("verbose").short("v").long("verbose")) 25 .arg(Arg::with_name("verbose").short("v").long("verbose"))
@@ -39,9 +42,9 @@ fn main() -> Result<()> {
39 println!("{:?}", s); 42 println!("{:?}", s);
40 } 43 }
41 } 44 }
42 ("highlight", _) => { 45 ("highlight", Some(matches)) => {
43 let (analysis, file_id) = Analysis::from_single_file(read_stdin()?); 46 let (analysis, file_id) = Analysis::from_single_file(read_stdin()?);
44 let html = analysis.highlight_as_html(file_id).unwrap(); 47 let html = analysis.highlight_as_html(file_id, matches.is_present("rainbow")).unwrap();
45 println!("{}", html); 48 println!("{}", html);
46 } 49 }
47 ("analysis-stats", Some(matches)) => { 50 ("analysis-stats", Some(matches)) => {
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}
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index a82ae696b..e36db12b3 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -872,7 +872,11 @@ fn highlight(world: &ServerWorld, file_id: FileId) -> Result<Vec<Decoration>> {
872 .analysis() 872 .analysis()
873 .highlight(file_id)? 873 .highlight(file_id)?
874 .into_iter() 874 .into_iter()
875 .map(|h| Decoration { range: h.range.conv_with(&line_index), tag: h.tag }) 875 .map(|h| Decoration {
876 range: h.range.conv_with(&line_index),
877 tag: h.tag,
878 binding_hash: h.binding_hash.map(|x| x.to_string()),
879 })
876 .collect(); 880 .collect();
877 Ok(res) 881 Ok(res)
878} 882}
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index 6090eb7b9..992c24eac 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -129,6 +129,7 @@ pub struct PublishDecorationsParams {
129pub struct Decoration { 129pub struct Decoration {
130 pub range: Range, 130 pub range: Range,
131 pub tag: &'static str, 131 pub tag: &'static str,
132 pub binding_hash: Option<String>,
132} 133}
133 134
134pub enum ParentModule {} 135pub enum ParentModule {}
diff --git a/crates/ra_syntax/src/syntax_text.rs b/crates/ra_syntax/src/syntax_text.rs
index b013164c4..c9038cd5c 100644
--- a/crates/ra_syntax/src/syntax_text.rs
+++ b/crates/ra_syntax/src/syntax_text.rs
@@ -1,6 +1,6 @@
1use std::{fmt, ops::{self, Bound}}; 1use std::{fmt, ops::{self, Bound}};
2 2
3use crate::{SyntaxNode, TextRange, TextUnit, SyntaxElement}; 3use crate::{SmolStr, SyntaxNode, TextRange, TextUnit, SyntaxElement};
4 4
5#[derive(Clone)] 5#[derive(Clone)]
6pub struct SyntaxText<'a> { 6pub struct SyntaxText<'a> {
@@ -34,6 +34,12 @@ impl<'a> SyntaxText<'a> {
34 self.chunks().collect() 34 self.chunks().collect()
35 } 35 }
36 36
37 pub fn to_smol_string(&self) -> SmolStr {
38 // FIXME: use `self.chunks().collect()` here too once
39 // https://github.com/matklad/smol_str/pull/12 is merged and published
40 self.to_string().into()
41 }
42
37 pub fn contains(&self, c: char) -> bool { 43 pub fn contains(&self, c: char) -> bool {
38 self.chunks().any(|it| it.contains(c)) 44 self.chunks().any(|it| it.contains(c))
39 } 45 }
diff --git a/docs/user/features.md b/docs/user/features.md
index 22470bc56..b6e6008c4 100644
--- a/docs/user/features.md
+++ b/docs/user/features.md
@@ -470,3 +470,12 @@ There also snippet completions:
470 470
471- `tfn` -> `#[test] fn f(){}` 471- `tfn` -> `#[test] fn f(){}`
472 472
473### Code highlighting
474
475Experimental feature to let rust-analyzer highlight Rust code instead of using the
476default highlighter.
477
478#### Rainbow highlighting
479
480Experimental feature that, given code highlighting using rust-analyzer is
481active, will pick unique colors for identifiers.
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 29cd260a4..6b3a12f91 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -36,6 +36,11 @@
36 "integrity": "sha512-Ja7d4s0qyGFxjGeDq5S7Si25OFibSAHUi6i17UWnwNnpitADN7hah9q0Tl25gxuV5R1u2Bx+np6w4LHXfHyj/g==", 36 "integrity": "sha512-Ja7d4s0qyGFxjGeDq5S7Si25OFibSAHUi6i17UWnwNnpitADN7hah9q0Tl25gxuV5R1u2Bx+np6w4LHXfHyj/g==",
37 "dev": true 37 "dev": true
38 }, 38 },
39 "@types/seedrandom": {
40 "version": "2.4.28",
41 "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.28.tgz",
42 "integrity": "sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA=="
43 },
39 "agent-base": { 44 "agent-base": {
40 "version": "4.2.1", 45 "version": "4.2.1",
41 "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", 46 "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
@@ -984,6 +989,11 @@
984 "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 989 "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
985 "dev": true 990 "dev": true
986 }, 991 },
992 "seedrandom": {
993 "version": "3.0.1",
994 "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.1.tgz",
995 "integrity": "sha512-1/02Y/rUeU1CJBAGLebiC5Lbo5FnB22gQbIFFYTLkwvp1xdABZJH1sn4ZT1MzXmPpzv+Rf/Lu2NcsLJiK4rcDg=="
996 },
987 "semver": { 997 "semver": {
988 "version": "5.7.0", 998 "version": "5.7.0",
989 "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", 999 "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
diff --git a/editors/code/package.json b/editors/code/package.json
index cde5fbcb8..05c808394 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -31,11 +31,13 @@
31 "singleQuote": true 31 "singleQuote": true
32 }, 32 },
33 "dependencies": { 33 "dependencies": {
34 "seedrandom": "^3.0.1",
34 "vscode-languageclient": "^5.3.0-next.4" 35 "vscode-languageclient": "^5.3.0-next.4"
35 }, 36 },
36 "devDependencies": { 37 "devDependencies": {
37 "@types/mocha": "^5.2.6", 38 "@types/mocha": "^5.2.6",
38 "@types/node": "^10.14.5", 39 "@types/node": "^10.14.5",
40 "@types/seedrandom": "^2.4.28",
39 "prettier": "^1.17.0", 41 "prettier": "^1.17.0",
40 "shx": "^0.3.1", 42 "shx": "^0.3.1",
41 "tslint": "^5.16.0", 43 "tslint": "^5.16.0",
@@ -162,6 +164,11 @@
162 "default": false, 164 "default": false,
163 "description": "Highlight Rust code (overrides built-in syntax highlighting)" 165 "description": "Highlight Rust code (overrides built-in syntax highlighting)"
164 }, 166 },
167 "rust-analyzer.rainbowHighlightingOn": {
168 "type": "boolean",
169 "default": false,
170 "description": "When highlighting Rust code, use a unique color per identifier"
171 },
165 "rust-analyzer.showWorkspaceLoadedNotification": { 172 "rust-analyzer.showWorkspaceLoadedNotification": {
166 "type": "boolean", 173 "type": "boolean",
167 "default": true, 174 "default": true,
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 481a5e5f1..8d73a6b34 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -15,6 +15,7 @@ export interface CargoWatchOptions {
15 15
16export class Config { 16export class Config {
17 public highlightingOn = true; 17 public highlightingOn = true;
18 public rainbowHighlightingOn = false;
18 public enableEnhancedTyping = true; 19 public enableEnhancedTyping = true;
19 public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; 20 public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
20 public showWorkspaceLoadedNotification = true; 21 public showWorkspaceLoadedNotification = true;
@@ -39,6 +40,12 @@ export class Config {
39 this.highlightingOn = config.get('highlightingOn') as boolean; 40 this.highlightingOn = config.get('highlightingOn') as boolean;
40 } 41 }
41 42
43 if (config.has('rainbowHighlightingOn')) {
44 this.rainbowHighlightingOn = config.get(
45 'rainbowHighlightingOn'
46 ) as boolean;
47 }
48
42 if (config.has('showWorkspaceLoadedNotification')) { 49 if (config.has('showWorkspaceLoadedNotification')) {
43 this.showWorkspaceLoadedNotification = config.get( 50 this.showWorkspaceLoadedNotification = config.get(
44 'showWorkspaceLoadedNotification' 51 'showWorkspaceLoadedNotification'
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts
index 8389d94b8..52a0bd4bb 100644
--- a/editors/code/src/highlighting.ts
+++ b/editors/code/src/highlighting.ts
@@ -1,3 +1,4 @@
1import seedrandom = require('seedrandom');
1import * as vscode from 'vscode'; 2import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 3import * as lc from 'vscode-languageclient';
3 4
@@ -6,6 +7,20 @@ import { Server } from './server';
6export interface Decoration { 7export interface Decoration {
7 range: lc.Range; 8 range: lc.Range;
8 tag: string; 9 tag: string;
10 bindingHash?: string;
11}
12
13// Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76
14function fancify(seed: string, shade: 'light' | 'dark') {
15 const random = seedrandom(seed);
16 const randomInt = (min: number, max: number) => {
17 return Math.floor(random() * (max - min + 1)) + min;
18 };
19
20 const h = randomInt(0, 360);
21 const s = randomInt(42, 98);
22 const l = shade === 'light' ? randomInt(15, 40) : randomInt(40, 90);
23 return `hsl(${h},${s}%,${l}%)`;
9} 24}
10 25
11export class Highlighter { 26export class Highlighter {
@@ -76,6 +91,9 @@ export class Highlighter {
76 } 91 }
77 92
78 const byTag: Map<string, vscode.Range[]> = new Map(); 93 const byTag: Map<string, vscode.Range[]> = new Map();
94 const colorfulIdents: Map<string, vscode.Range[]> = new Map();
95 const rainbowTime = Server.config.rainbowHighlightingOn;
96
79 for (const tag of this.decorations.keys()) { 97 for (const tag of this.decorations.keys()) {
80 byTag.set(tag, []); 98 byTag.set(tag, []);
81 } 99 }
@@ -84,9 +102,23 @@ export class Highlighter {
84 if (!byTag.get(d.tag)) { 102 if (!byTag.get(d.tag)) {
85 continue; 103 continue;
86 } 104 }
87 byTag 105
88 .get(d.tag)! 106 if (rainbowTime && d.bindingHash) {
89 .push(Server.client.protocol2CodeConverter.asRange(d.range)); 107 if (!colorfulIdents.has(d.bindingHash)) {
108 colorfulIdents.set(d.bindingHash, []);
109 }
110 colorfulIdents
111 .get(d.bindingHash)!
112 .push(
113 Server.client.protocol2CodeConverter.asRange(d.range)
114 );
115 } else {
116 byTag
117 .get(d.tag)!
118 .push(
119 Server.client.protocol2CodeConverter.asRange(d.range)
120 );
121 }
90 } 122 }
91 123
92 for (const tag of byTag.keys()) { 124 for (const tag of byTag.keys()) {
@@ -96,5 +128,13 @@ export class Highlighter {
96 const ranges = byTag.get(tag)!; 128 const ranges = byTag.get(tag)!;
97 editor.setDecorations(dec, ranges); 129 editor.setDecorations(dec, ranges);
98 } 130 }
131
132 for (const [hash, ranges] of colorfulIdents.entries()) {
133 const dec = vscode.window.createTextEditorDecorationType({
134 light: { color: fancify(hash, 'light') },
135 dark: { color: fancify(hash, 'dark') }
136 });
137 editor.setDecorations(dec, ranges);
138 }
99 } 139 }
100} 140}