diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-05-27 10:56:06 +0100 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-05-27 10:56:06 +0100 |
commit | 0d1c6076073c73f57340e256dc25da9d37311ef0 (patch) | |
tree | 60ff1f4a42f8ef297c07d5716af67e3057c8e1bd | |
parent | 4b48cff022a1606bde596f01fbf44361640b10d8 (diff) | |
parent | 1e6ba1901550fb1610a1a464c48ec358cd3c339c (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.lock | 1 | ||||
-rw-r--r-- | crates/ra_cli/src/main.rs | 9 | ||||
-rw-r--r-- | crates/ra_ide_api/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 6 | ||||
-rw-r--r-- | crates/ra_ide_api/src/snapshots/highlighting.html | 20 | ||||
-rw-r--r-- | crates/ra_ide_api/src/snapshots/rainbow_highlighting.html | 27 | ||||
-rw-r--r-- | crates/ra_ide_api/src/syntax_highlighting.rs | 130 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 6 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/req.rs | 1 | ||||
-rw-r--r-- | crates/ra_syntax/src/syntax_text.rs | 8 | ||||
-rw-r--r-- | docs/user/features.md | 9 | ||||
-rw-r--r-- | editors/code/package-lock.json | 10 | ||||
-rw-r--r-- | editors/code/package.json | 7 | ||||
-rw-r--r-- | editors/code/src/config.ts | 7 | ||||
-rw-r--r-- | editors/code/src/highlighting.ts | 46 |
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" | |||
15 | parking_lot = "0.7.0" | 15 | parking_lot = "0.7.0" |
16 | unicase = "2.2.0" | 16 | unicase = "2.2.0" |
17 | superslice = "1.0.0" | 17 | superslice = "1.0.0" |
18 | rand = "0.6.5" | ||
18 | 19 | ||
19 | jemallocator = { version = "0.1.9", optional = true } | 20 | jemallocator = { version = "0.1.9", optional = true } |
20 | jemalloc-ctl = { version = "0.2.0", optional = true } | 21 | jemalloc-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> |
3 | pre { | 3 | body { margin: 0; } |
4 | color: #DCDCCC; | 4 | pre { 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> | ||
3 | body { margin: 0; } | ||
4 | pre { 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 @@ | |||
1 | use rustc_hash::FxHashSet; | 1 | use rustc_hash::{FxHashSet, FxHashMap}; |
2 | 2 | ||
3 | use ra_syntax::{ast, AstNode, TextRange, Direction, SyntaxKind, SyntaxKind::*, SyntaxElement, T}; | 3 | use ra_syntax::{ast, AstNode, TextRange, Direction, SmolStr, SyntaxKind, SyntaxKind::*, SyntaxElement, T}; |
4 | use ra_db::SourceDatabase; | 4 | use ra_db::SourceDatabase; |
5 | use ra_prof::profile; | 5 | use ra_prof::profile; |
6 | 6 | ||
@@ -10,6 +10,7 @@ use crate::{FileId, db::RootDatabase}; | |||
10 | pub struct HighlightedRange { | 10 | pub 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 | ||
15 | fn is_control_keyword(kind: SyntaxKind) -> bool { | 16 | fn is_control_keyword(kind: SyntaxKind) -> bool { |
@@ -29,22 +30,36 @@ fn is_control_keyword(kind: SyntaxKind) -> bool { | |||
29 | 30 | ||
30 | pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRange> { | 31 | pub(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 | ||
117 | pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId) -> String { | 164 | pub(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 | ||
162 | const STYLE: &str = " | 228 | const STYLE: &str = " |
163 | <style> | 229 | <style> |
164 | pre { | 230 | body { margin: 0; } |
165 | color: #DCDCCC; | 231 | pre { 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#" | ||
292 | fn 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 { | |||
129 | pub struct Decoration { | 129 | pub 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 | ||
134 | pub enum ParentModule {} | 135 | pub 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 @@ | |||
1 | use std::{fmt, ops::{self, Bound}}; | 1 | use std::{fmt, ops::{self, Bound}}; |
2 | 2 | ||
3 | use crate::{SyntaxNode, TextRange, TextUnit, SyntaxElement}; | 3 | use crate::{SmolStr, SyntaxNode, TextRange, TextUnit, SyntaxElement}; |
4 | 4 | ||
5 | #[derive(Clone)] | 5 | #[derive(Clone)] |
6 | pub struct SyntaxText<'a> { | 6 | pub 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 | |||
475 | Experimental feature to let rust-analyzer highlight Rust code instead of using the | ||
476 | default highlighter. | ||
477 | |||
478 | #### Rainbow highlighting | ||
479 | |||
480 | Experimental feature that, given code highlighting using rust-analyzer is | ||
481 | active, 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 | ||
16 | export class Config { | 16 | export 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 @@ | |||
1 | import seedrandom = require('seedrandom'); | ||
1 | import * as vscode from 'vscode'; | 2 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 3 | import * as lc from 'vscode-languageclient'; |
3 | 4 | ||
@@ -6,6 +7,20 @@ import { Server } from './server'; | |||
6 | export interface Decoration { | 7 | export 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 | ||
14 | function 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 | ||
11 | export class Highlighter { | 26 | export 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 | } |