aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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}