mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-16 01:38:13 +00:00
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 <pascal@technocreatives.com>
This commit is contained in:
commit
0d1c607607
15 changed files with 242 additions and 46 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1155,6 +1155,7 @@ dependencies = [
|
|||
"ra_prof 0.1.0",
|
||||
"ra_syntax 0.1.0",
|
||||
"ra_text_edit 0.1.0",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -16,7 +16,10 @@ fn main() -> Result<()> {
|
|||
.setting(clap::AppSettings::SubcommandRequiredElseHelp)
|
||||
.subcommand(SubCommand::with_name("parse").arg(Arg::with_name("no-dump").long("--no-dump")))
|
||||
.subcommand(SubCommand::with_name("symbols"))
|
||||
.subcommand(SubCommand::with_name("highlight"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("highlight")
|
||||
.arg(Arg::with_name("rainbow").short("r").long("rainbow")),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("analysis-stats")
|
||||
.arg(Arg::with_name("verbose").short("v").long("verbose"))
|
||||
|
@ -39,9 +42,9 @@ fn main() -> Result<()> {
|
|||
println!("{:?}", s);
|
||||
}
|
||||
}
|
||||
("highlight", _) => {
|
||||
("highlight", Some(matches)) => {
|
||||
let (analysis, file_id) = Analysis::from_single_file(read_stdin()?);
|
||||
let html = analysis.highlight_as_html(file_id).unwrap();
|
||||
let html = analysis.highlight_as_html(file_id, matches.is_present("rainbow")).unwrap();
|
||||
println!("{}", html);
|
||||
}
|
||||
("analysis-stats", Some(matches)) => {
|
||||
|
|
|
@ -15,6 +15,7 @@ rustc-hash = "1.0"
|
|||
parking_lot = "0.7.0"
|
||||
unicase = "2.2.0"
|
||||
superslice = "1.0.0"
|
||||
rand = "0.6.5"
|
||||
|
||||
jemallocator = { version = "0.1.9", optional = true }
|
||||
jemalloc-ctl = { version = "0.2.0", optional = true }
|
||||
|
|
|
@ -463,8 +463,8 @@ impl Analysis {
|
|||
}
|
||||
|
||||
/// Computes syntax highlighting for the given file.
|
||||
pub fn highlight_as_html(&self, file_id: FileId) -> Cancelable<String> {
|
||||
self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id))
|
||||
pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancelable<String> {
|
||||
self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow))
|
||||
}
|
||||
|
||||
/// Computes completions at the given position.
|
||||
|
@ -472,7 +472,7 @@ impl Analysis {
|
|||
self.with_db(|db| completion::completions(db, position).map(Into::into))
|
||||
}
|
||||
|
||||
/// Computes assists (aks code actons aka intentions) for the given
|
||||
/// Computes assists (aka code actions aka intentions) for the given
|
||||
/// position.
|
||||
pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> {
|
||||
self.with_db(|db| assists::assists(db, frange))
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
|
||||
<style>
|
||||
pre {
|
||||
color: #DCDCCC;
|
||||
background-color: #3F3F3F;
|
||||
font-size: 22px;
|
||||
}
|
||||
body { margin: 0; }
|
||||
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
|
||||
|
||||
.comment { color: #7F9F7F; }
|
||||
.string { color: #CC9393; }
|
||||
|
@ -19,10 +16,8 @@ pre {
|
|||
.keyword { color: #F0DFAF; }
|
||||
.keyword\.unsafe { color: #F0DFAF; font-weight: bold; }
|
||||
.keyword\.control { color: #DC8CC3; }
|
||||
|
||||
</style>
|
||||
<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>
|
||||
<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>
|
||||
<span class="keyword">struct</span> <span class="function">Foo</span> {
|
||||
<span class="keyword">pub</span> <span class="function">x</span>: <span class="text">i32</span>,
|
||||
<span class="keyword">pub</span> <span class="function">y</span>: <span class="text">i32</span>,
|
||||
|
@ -36,10 +31,9 @@ pre {
|
|||
<span class="keyword">fn</span> <span class="function">main</span>() {
|
||||
<span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal">92</span>);
|
||||
|
||||
<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>();
|
||||
<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>();
|
||||
<span class="keyword.control">if</span> <span class="keyword">true</span> {
|
||||
<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> });
|
||||
<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> });
|
||||
}
|
||||
<span class="keyword.unsafe">unsafe</span> { <span class="text">vec</span>.<span class="text">set_len</span>(<span class="literal">0</span>); }
|
||||
}
|
||||
</code></pre>
|
||||
<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>); }
|
||||
}</code></pre>
|
27
crates/ra_ide_api/src/snapshots/rainbow_highlighting.html
Normal file
27
crates/ra_ide_api/src/snapshots/rainbow_highlighting.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
|
||||
|
||||
.comment { color: #7F9F7F; }
|
||||
.string { color: #CC9393; }
|
||||
.function { color: #93E0E3; }
|
||||
.parameter { color: #94BFF3; }
|
||||
.builtin { color: #DD6718; }
|
||||
.text { color: #DCDCCC; }
|
||||
.attribute { color: #BFEBBF; }
|
||||
.literal { color: #DFAF8F; }
|
||||
.macro { color: #DFAF8F; }
|
||||
|
||||
.keyword { color: #F0DFAF; }
|
||||
.keyword\.unsafe { color: #F0DFAF; font-weight: bold; }
|
||||
.keyword\.control { color: #DC8CC3; }
|
||||
</style>
|
||||
<pre><code><span class="keyword">fn</span> <span class="function">main</span>() {
|
||||
<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>;
|
||||
<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>();
|
||||
<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>();
|
||||
|
||||
<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>;
|
||||
<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>();
|
||||
}</code></pre>
|
|
@ -1,6 +1,6 @@
|
|||
use rustc_hash::FxHashSet;
|
||||
use rustc_hash::{FxHashSet, FxHashMap};
|
||||
|
||||
use ra_syntax::{ast, AstNode, TextRange, Direction, SyntaxKind, SyntaxKind::*, SyntaxElement, T};
|
||||
use ra_syntax::{ast, AstNode, TextRange, Direction, SmolStr, SyntaxKind, SyntaxKind::*, SyntaxElement, T};
|
||||
use ra_db::SourceDatabase;
|
||||
use ra_prof::profile;
|
||||
|
||||
|
@ -10,6 +10,7 @@ use crate::{FileId, db::RootDatabase};
|
|||
pub struct HighlightedRange {
|
||||
pub range: TextRange,
|
||||
pub tag: &'static str,
|
||||
pub binding_hash: Option<u64>,
|
||||
}
|
||||
|
||||
fn is_control_keyword(kind: SyntaxKind) -> bool {
|
||||
|
@ -29,22 +30,36 @@ fn is_control_keyword(kind: SyntaxKind) -> bool {
|
|||
|
||||
pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRange> {
|
||||
let _p = profile("highlight");
|
||||
|
||||
let source_file = db.parse(file_id);
|
||||
|
||||
fn calc_binding_hash(file_id: FileId, text: &SmolStr, shadow_count: u32) -> u64 {
|
||||
fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
|
||||
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
x.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
hash((file_id, text, shadow_count))
|
||||
}
|
||||
|
||||
// Visited nodes to handle highlighting priorities
|
||||
let mut highlighted: FxHashSet<SyntaxElement> = FxHashSet::default();
|
||||
let mut bindings_shadow_count: FxHashMap<SmolStr, u32> = FxHashMap::default();
|
||||
|
||||
let mut res = Vec::new();
|
||||
for node in source_file.syntax().descendants_with_tokens() {
|
||||
if highlighted.contains(&node) {
|
||||
continue;
|
||||
}
|
||||
let mut binding_hash = None;
|
||||
let tag = match node.kind() {
|
||||
COMMENT => "comment",
|
||||
STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string",
|
||||
ATTR => "attribute",
|
||||
NAME_REF => {
|
||||
if let Some(name_ref) = node.as_node().and_then(|n| ast::NameRef::cast(n)) {
|
||||
if let Some(name_ref) = node.as_node().and_then(ast::NameRef::cast) {
|
||||
use crate::name_ref_kind::{classify_name_ref, NameRefKind::*};
|
||||
use hir::{ModuleDef, ImplItem};
|
||||
|
||||
|
@ -68,7 +83,20 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
|
|||
Some(Def(ModuleDef::Trait(_))) => "type",
|
||||
Some(Def(ModuleDef::TypeAlias(_))) => "type",
|
||||
Some(SelfType(_)) => "type",
|
||||
Some(Pat(_)) => "text",
|
||||
Some(Pat(ptr)) => {
|
||||
binding_hash = Some({
|
||||
let text = ptr
|
||||
.syntax_node_ptr()
|
||||
.to_node(&source_file.syntax())
|
||||
.text()
|
||||
.to_smol_string();
|
||||
let shadow_count =
|
||||
bindings_shadow_count.entry(text.clone()).or_default();
|
||||
calc_binding_hash(file_id, &text, *shadow_count)
|
||||
});
|
||||
|
||||
"variable"
|
||||
}
|
||||
Some(SelfParam(_)) => "type",
|
||||
Some(GenericParam(_)) => "type",
|
||||
None => "text",
|
||||
|
@ -77,7 +105,24 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
|
|||
"text"
|
||||
}
|
||||
}
|
||||
NAME => "function",
|
||||
NAME => {
|
||||
if let Some(name) = node.as_node().and_then(ast::Name::cast) {
|
||||
if name.syntax().ancestors().any(|x| ast::BindPat::cast(x).is_some()) {
|
||||
binding_hash = Some({
|
||||
let text = name.syntax().text().to_smol_string();
|
||||
let shadow_count =
|
||||
bindings_shadow_count.entry(text.clone()).or_insert(0);
|
||||
*shadow_count += 1;
|
||||
calc_binding_hash(file_id, &text, *shadow_count)
|
||||
});
|
||||
"variable"
|
||||
} else {
|
||||
"function"
|
||||
}
|
||||
} else {
|
||||
"text"
|
||||
}
|
||||
}
|
||||
TYPE_ALIAS_DEF | TYPE_ARG | TYPE_PARAM => "type",
|
||||
INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal",
|
||||
LIFETIME => "parameter",
|
||||
|
@ -85,6 +130,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
|
|||
k if is_control_keyword(k) => "keyword.control",
|
||||
k if k.is_keyword() => "keyword",
|
||||
_ => {
|
||||
// let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None);
|
||||
if let Some(macro_call) = node.as_node().and_then(ast::MacroCall::cast) {
|
||||
if let Some(path) = macro_call.path() {
|
||||
if let Some(segment) = path.segment() {
|
||||
|
@ -101,6 +147,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
|
|||
res.push(HighlightedRange {
|
||||
range: TextRange::from_to(range_start, range_end),
|
||||
tag: "macro",
|
||||
binding_hash: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -109,14 +156,25 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
|
|||
continue;
|
||||
}
|
||||
};
|
||||
res.push(HighlightedRange { range: node.range(), tag })
|
||||
res.push(HighlightedRange { range: node.range(), tag, binding_hash })
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId) -> String {
|
||||
pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
|
||||
let source_file = db.parse(file_id);
|
||||
|
||||
fn rainbowify(seed: u64) -> String {
|
||||
use rand::prelude::*;
|
||||
let mut rng = SmallRng::seed_from_u64(seed);
|
||||
format!(
|
||||
"hsl({h},{s}%,{l}%)",
|
||||
h = rng.gen_range::<u16, _, _>(0, 361),
|
||||
s = rng.gen_range::<u16, _, _>(42, 99),
|
||||
l = rng.gen_range::<u16, _, _>(40, 91),
|
||||
)
|
||||
}
|
||||
|
||||
let mut ranges = highlight(db, file_id);
|
||||
ranges.sort_by_key(|it| it.range.start());
|
||||
// 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 {
|
|||
}
|
||||
}
|
||||
let text = html_escape(&token.text());
|
||||
let classes = could_intersect
|
||||
let ranges = could_intersect
|
||||
.iter()
|
||||
.filter(|it| token.range().is_subrange(&it.range))
|
||||
.map(|it| it.tag)
|
||||
.collect::<Vec<_>>();
|
||||
if classes.is_empty() {
|
||||
if ranges.is_empty() {
|
||||
buf.push_str(&text);
|
||||
} else {
|
||||
let classes = classes.join(" ");
|
||||
buf.push_str(&format!("<span class=\"{}\">{}</span>", classes, text));
|
||||
let classes = ranges.iter().map(|x| x.tag).collect::<Vec<_>>().join(" ");
|
||||
let binding_hash = ranges.first().and_then(|x| x.binding_hash);
|
||||
let color = match (rainbow, binding_hash) {
|
||||
(true, Some(hash)) => format!(
|
||||
" data-binding-hash=\"{}\" style=\"color: {};\"",
|
||||
hash,
|
||||
rainbowify(hash)
|
||||
),
|
||||
_ => "".into(),
|
||||
};
|
||||
buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", classes, color, text));
|
||||
}
|
||||
}
|
||||
buf.push_str("</code></pre>");
|
||||
|
@ -161,11 +227,8 @@ fn html_escape(text: &str) -> String {
|
|||
|
||||
const STYLE: &str = "
|
||||
<style>
|
||||
pre {
|
||||
color: #DCDCCC;
|
||||
background-color: #3F3F3F;
|
||||
font-size: 22px;
|
||||
}
|
||||
body { margin: 0; }
|
||||
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
|
||||
|
||||
.comment { color: #7F9F7F; }
|
||||
.string { color: #CC9393; }
|
||||
|
@ -180,7 +243,6 @@ pre {
|
|||
.keyword { color: #F0DFAF; }
|
||||
.keyword\\.unsafe { color: #F0DFAF; font-weight: bold; }
|
||||
.keyword\\.control { color: #DC8CC3; }
|
||||
|
||||
</style>
|
||||
";
|
||||
|
||||
|
@ -213,12 +275,36 @@ fn main() {
|
|||
}
|
||||
unsafe { vec.set_len(0); }
|
||||
}
|
||||
"#,
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
let dst_file = project_dir().join("crates/ra_ide_api/src/snapshots/highlighting.html");
|
||||
let actual_html = &analysis.highlight_as_html(file_id).unwrap();
|
||||
let actual_html = &analysis.highlight_as_html(file_id, true).unwrap();
|
||||
let expected_html = &read_text(&dst_file);
|
||||
// std::fs::write(dst_file, &actual_html).unwrap();
|
||||
std::fs::write(dst_file, &actual_html).unwrap();
|
||||
assert_eq_text!(expected_html, actual_html);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rainbow_highlighting() {
|
||||
let (analysis, file_id) = single_file(
|
||||
r#"
|
||||
fn main() {
|
||||
let hello = "hello";
|
||||
let x = hello.to_string();
|
||||
let y = hello.to_string();
|
||||
|
||||
let x = "other color please!";
|
||||
let y = x.to_string();
|
||||
}
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
let dst_file =
|
||||
project_dir().join("crates/ra_ide_api/src/snapshots/rainbow_highlighting.html");
|
||||
let actual_html = &analysis.highlight_as_html(file_id, true).unwrap();
|
||||
let expected_html = &read_text(&dst_file);
|
||||
std::fs::write(dst_file, &actual_html).unwrap();
|
||||
assert_eq_text!(expected_html, actual_html);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -872,7 +872,11 @@ fn highlight(world: &ServerWorld, file_id: FileId) -> Result<Vec<Decoration>> {
|
|||
.analysis()
|
||||
.highlight(file_id)?
|
||||
.into_iter()
|
||||
.map(|h| Decoration { range: h.range.conv_with(&line_index), tag: h.tag })
|
||||
.map(|h| Decoration {
|
||||
range: h.range.conv_with(&line_index),
|
||||
tag: h.tag,
|
||||
binding_hash: h.binding_hash.map(|x| x.to_string()),
|
||||
})
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
|
|
|
@ -129,6 +129,7 @@ pub struct PublishDecorationsParams {
|
|||
pub struct Decoration {
|
||||
pub range: Range,
|
||||
pub tag: &'static str,
|
||||
pub binding_hash: Option<String>,
|
||||
}
|
||||
|
||||
pub enum ParentModule {}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{fmt, ops::{self, Bound}};
|
||||
|
||||
use crate::{SyntaxNode, TextRange, TextUnit, SyntaxElement};
|
||||
use crate::{SmolStr, SyntaxNode, TextRange, TextUnit, SyntaxElement};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SyntaxText<'a> {
|
||||
|
@ -34,6 +34,12 @@ impl<'a> SyntaxText<'a> {
|
|||
self.chunks().collect()
|
||||
}
|
||||
|
||||
pub fn to_smol_string(&self) -> SmolStr {
|
||||
// FIXME: use `self.chunks().collect()` here too once
|
||||
// https://github.com/matklad/smol_str/pull/12 is merged and published
|
||||
self.to_string().into()
|
||||
}
|
||||
|
||||
pub fn contains(&self, c: char) -> bool {
|
||||
self.chunks().any(|it| it.contains(c))
|
||||
}
|
||||
|
|
|
@ -470,3 +470,12 @@ There also snippet completions:
|
|||
|
||||
- `tfn` -> `#[test] fn f(){}`
|
||||
|
||||
### Code highlighting
|
||||
|
||||
Experimental feature to let rust-analyzer highlight Rust code instead of using the
|
||||
default highlighter.
|
||||
|
||||
#### Rainbow highlighting
|
||||
|
||||
Experimental feature that, given code highlighting using rust-analyzer is
|
||||
active, will pick unique colors for identifiers.
|
||||
|
|
10
editors/code/package-lock.json
generated
10
editors/code/package-lock.json
generated
|
@ -36,6 +36,11 @@
|
|||
"integrity": "sha512-Ja7d4s0qyGFxjGeDq5S7Si25OFibSAHUi6i17UWnwNnpitADN7hah9q0Tl25gxuV5R1u2Bx+np6w4LHXfHyj/g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/seedrandom": {
|
||||
"version": "2.4.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.28.tgz",
|
||||
"integrity": "sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA=="
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
|
||||
|
@ -984,6 +989,11 @@
|
|||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"seedrandom": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.1.tgz",
|
||||
"integrity": "sha512-1/02Y/rUeU1CJBAGLebiC5Lbo5FnB22gQbIFFYTLkwvp1xdABZJH1sn4ZT1MzXmPpzv+Rf/Lu2NcsLJiK4rcDg=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
|
||||
|
|
|
@ -31,11 +31,13 @@
|
|||
"singleQuote": true
|
||||
},
|
||||
"dependencies": {
|
||||
"seedrandom": "^3.0.1",
|
||||
"vscode-languageclient": "^5.3.0-next.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/node": "^10.14.5",
|
||||
"@types/seedrandom": "^2.4.28",
|
||||
"prettier": "^1.17.0",
|
||||
"shx": "^0.3.1",
|
||||
"tslint": "^5.16.0",
|
||||
|
@ -162,6 +164,11 @@
|
|||
"default": false,
|
||||
"description": "Highlight Rust code (overrides built-in syntax highlighting)"
|
||||
},
|
||||
"rust-analyzer.rainbowHighlightingOn": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When highlighting Rust code, use a unique color per identifier"
|
||||
},
|
||||
"rust-analyzer.showWorkspaceLoadedNotification": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
|
|
|
@ -15,6 +15,7 @@ export interface CargoWatchOptions {
|
|||
|
||||
export class Config {
|
||||
public highlightingOn = true;
|
||||
public rainbowHighlightingOn = false;
|
||||
public enableEnhancedTyping = true;
|
||||
public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
|
||||
public showWorkspaceLoadedNotification = true;
|
||||
|
@ -39,6 +40,12 @@ export class Config {
|
|||
this.highlightingOn = config.get('highlightingOn') as boolean;
|
||||
}
|
||||
|
||||
if (config.has('rainbowHighlightingOn')) {
|
||||
this.rainbowHighlightingOn = config.get(
|
||||
'rainbowHighlightingOn'
|
||||
) as boolean;
|
||||
}
|
||||
|
||||
if (config.has('showWorkspaceLoadedNotification')) {
|
||||
this.showWorkspaceLoadedNotification = config.get(
|
||||
'showWorkspaceLoadedNotification'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import seedrandom = require('seedrandom');
|
||||
import * as vscode from 'vscode';
|
||||
import * as lc from 'vscode-languageclient';
|
||||
|
||||
|
@ -6,6 +7,20 @@ import { Server } from './server';
|
|||
export interface Decoration {
|
||||
range: lc.Range;
|
||||
tag: string;
|
||||
bindingHash?: string;
|
||||
}
|
||||
|
||||
// Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76
|
||||
function fancify(seed: string, shade: 'light' | 'dark') {
|
||||
const random = seedrandom(seed);
|
||||
const randomInt = (min: number, max: number) => {
|
||||
return Math.floor(random() * (max - min + 1)) + min;
|
||||
};
|
||||
|
||||
const h = randomInt(0, 360);
|
||||
const s = randomInt(42, 98);
|
||||
const l = shade === 'light' ? randomInt(15, 40) : randomInt(40, 90);
|
||||
return `hsl(${h},${s}%,${l}%)`;
|
||||
}
|
||||
|
||||
export class Highlighter {
|
||||
|
@ -76,6 +91,9 @@ export class Highlighter {
|
|||
}
|
||||
|
||||
const byTag: Map<string, vscode.Range[]> = new Map();
|
||||
const colorfulIdents: Map<string, vscode.Range[]> = new Map();
|
||||
const rainbowTime = Server.config.rainbowHighlightingOn;
|
||||
|
||||
for (const tag of this.decorations.keys()) {
|
||||
byTag.set(tag, []);
|
||||
}
|
||||
|
@ -84,9 +102,23 @@ export class Highlighter {
|
|||
if (!byTag.get(d.tag)) {
|
||||
continue;
|
||||
}
|
||||
byTag
|
||||
.get(d.tag)!
|
||||
.push(Server.client.protocol2CodeConverter.asRange(d.range));
|
||||
|
||||
if (rainbowTime && d.bindingHash) {
|
||||
if (!colorfulIdents.has(d.bindingHash)) {
|
||||
colorfulIdents.set(d.bindingHash, []);
|
||||
}
|
||||
colorfulIdents
|
||||
.get(d.bindingHash)!
|
||||
.push(
|
||||
Server.client.protocol2CodeConverter.asRange(d.range)
|
||||
);
|
||||
} else {
|
||||
byTag
|
||||
.get(d.tag)!
|
||||
.push(
|
||||
Server.client.protocol2CodeConverter.asRange(d.range)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const tag of byTag.keys()) {
|
||||
|
@ -96,5 +128,13 @@ export class Highlighter {
|
|||
const ranges = byTag.get(tag)!;
|
||||
editor.setDecorations(dec, ranges);
|
||||
}
|
||||
|
||||
for (const [hash, ranges] of colorfulIdents.entries()) {
|
||||
const dec = vscode.window.createTextEditorDecorationType({
|
||||
light: { color: fancify(hash, 'light') },
|
||||
dark: { color: fancify(hash, 'dark') }
|
||||
});
|
||||
editor.setDecorations(dec, ranges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue