diff options
-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 | } |