From 5bf3e949e8470a138a61c806769e1a329761cab6 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Thu, 23 May 2019 19:42:42 +0200 Subject: [PATCH 1/7] Semantic highlighting spike 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? --- .../src/snapshots/tests__highlighting.snap | 192 ++++++++++++++++++ .../tests__sematic_highlighting.snap | 87 ++++++++ crates/ra_ide_api/src/syntax_highlighting.rs | 101 +++++---- .../ra_lsp_server/src/main_loop/handlers.rs | 6 +- crates/ra_lsp_server/src/req.rs | 1 + crates/ra_syntax/src/syntax_node.rs | 4 + editors/code/package-lock.json | 10 + editors/code/package.json | 2 + editors/code/src/highlighting.ts | 45 +++- 9 files changed, 409 insertions(+), 39 deletions(-) create mode 100644 crates/ra_ide_api/src/snapshots/tests__highlighting.snap create mode 100644 crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap diff --git a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap new file mode 100644 index 0000000000..208681f103 --- /dev/null +++ b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap @@ -0,0 +1,192 @@ +--- +created: "2019-05-25T10:53:54.439877Z" +creator: insta@0.8.1 +source: crates/ra_ide_api/src/syntax_highlighting.rs +expression: result +--- +Ok( + [ + HighlightedRange { + range: [1; 24), + tag: "attribute", + id: None, + }, + HighlightedRange { + range: [25; 31), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [32; 35), + tag: "variable", + id: Some( + 461893210254723387, + ), + }, + HighlightedRange { + range: [42; 45), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [46; 47), + tag: "variable", + id: Some( + 8312289520117458465, + ), + }, + HighlightedRange { + range: [49; 52), + tag: "text", + id: None, + }, + HighlightedRange { + range: [58; 61), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [62; 63), + tag: "variable", + id: Some( + 4497542318236667727, + ), + }, + HighlightedRange { + range: [65; 68), + tag: "text", + id: None, + }, + HighlightedRange { + range: [73; 75), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [76; 79), + tag: "variable", + id: Some( + 4506850079084802999, + ), + }, + HighlightedRange { + range: [80; 81), + tag: "type", + id: None, + }, + HighlightedRange { + range: [80; 81), + tag: "variable", + id: Some( + 16968185728268100018, + ), + }, + HighlightedRange { + range: [88; 89), + tag: "type", + id: None, + }, + HighlightedRange { + range: [96; 110), + tag: "macro", + id: None, + }, + HighlightedRange { + range: [117; 127), + tag: "comment", + id: None, + }, + HighlightedRange { + range: [128; 130), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [131; 135), + tag: "variable", + id: Some( + 14467718814232352107, + ), + }, + HighlightedRange { + range: [145; 153), + tag: "macro", + id: None, + }, + HighlightedRange { + range: [154; 166), + tag: "string", + id: None, + }, + HighlightedRange { + range: [168; 170), + tag: "literal", + id: None, + }, + HighlightedRange { + range: [178; 181), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [182; 185), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [186; 189), + tag: "macro", + id: None, + }, + HighlightedRange { + range: [197; 200), + tag: "macro", + id: None, + }, + HighlightedRange { + range: [192; 195), + tag: "text", + id: None, + }, + HighlightedRange { + range: [208; 211), + tag: "macro", + id: None, + }, + HighlightedRange { + range: [212; 216), + tag: "macro", + id: None, + }, + HighlightedRange { + range: [226; 227), + tag: "literal", + id: None, + }, + HighlightedRange { + range: [232; 233), + tag: "literal", + id: None, + }, + HighlightedRange { + range: [242; 248), + tag: "keyword.unsafe", + id: None, + }, + HighlightedRange { + range: [251; 254), + tag: "text", + id: None, + }, + HighlightedRange { + range: [255; 262), + tag: "text", + id: None, + }, + HighlightedRange { + range: [263; 264), + tag: "literal", + id: None, + }, + ], +) diff --git a/crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap new file mode 100644 index 0000000000..3b3fe32e97 --- /dev/null +++ b/crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap @@ -0,0 +1,87 @@ +--- +created: "2019-05-25T10:25:13.898113Z" +creator: insta@0.8.1 +source: crates/ra_ide_api/src/syntax_highlighting.rs +expression: result +--- +Ok( + [ + HighlightedRange { + range: [1; 3), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [4; 8), + tag: "variable", + id: Some( + 17119830160611610240, + ), + }, + HighlightedRange { + range: [17; 20), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [21; 26), + tag: "variable", + id: Some( + 2744494144922727377, + ), + }, + HighlightedRange { + range: [29; 36), + tag: "string", + id: None, + }, + HighlightedRange { + range: [42; 45), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [46; 47), + tag: "variable", + id: Some( + 10375904121795371996, + ), + }, + HighlightedRange { + range: [50; 55), + tag: "variable", + id: Some( + 2744494144922727377, + ), + }, + HighlightedRange { + range: [56; 65), + tag: "text", + id: None, + }, + HighlightedRange { + range: [73; 76), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [77; 78), + tag: "variable", + id: Some( + 8228548264153724449, + ), + }, + HighlightedRange { + range: [81; 86), + tag: "variable", + id: Some( + 2744494144922727377, + ), + }, + HighlightedRange { + range: [87; 96), + tag: "text", + id: None, + }, + ], +) diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs index 87e053364c..da000c0c30 100644 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ b/crates/ra_ide_api/src/syntax_highlighting.rs @@ -10,6 +10,7 @@ use crate::{FileId, db::RootDatabase}; pub struct HighlightedRange { pub range: TextRange, pub tag: &'static str, + pub id: Option, } fn is_control_keyword(kind: SyntaxKind) -> bool { @@ -32,6 +33,14 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec(x: T) -> u64 { + use std::{collections::hash_map::DefaultHasher, hash::Hasher}; + + let mut hasher = DefaultHasher::new(); + x.hash(&mut hasher); + hasher.finish() + } + // Visited nodes to handle highlighting priorities let mut highlighted: FxHashSet = FxHashSet::default(); let mut res = Vec::new(); @@ -39,52 +48,59 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec "comment", - STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", - ATTR => "attribute", + let (tag, id) = match node.kind() { + COMMENT => ("comment", None), + STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => ("string", None), + ATTR => ("attribute", None), NAME_REF => { - if let Some(name_ref) = node.as_node().and_then(|n| ast::NameRef::cast(n)) { + if let Some(name_ref) = node.as_ast_node::() { use crate::name_ref_kind::{classify_name_ref, NameRefKind::*}; use hir::{ModuleDef, ImplItem}; // FIXME: try to reuse the SourceAnalyzers let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); match classify_name_ref(db, &analyzer, name_ref) { - Some(Method(_)) => "function", - Some(Macro(_)) => "macro", - Some(FieldAccess(_)) => "field", - Some(AssocItem(ImplItem::Method(_))) => "function", - Some(AssocItem(ImplItem::Const(_))) => "constant", - Some(AssocItem(ImplItem::TypeAlias(_))) => "type", - Some(Def(ModuleDef::Module(_))) => "module", - Some(Def(ModuleDef::Function(_))) => "function", - Some(Def(ModuleDef::Struct(_))) => "type", - Some(Def(ModuleDef::Union(_))) => "type", - Some(Def(ModuleDef::Enum(_))) => "type", - Some(Def(ModuleDef::EnumVariant(_))) => "constant", - Some(Def(ModuleDef::Const(_))) => "constant", - Some(Def(ModuleDef::Static(_))) => "constant", - Some(Def(ModuleDef::Trait(_))) => "type", - Some(Def(ModuleDef::TypeAlias(_))) => "type", - Some(SelfType(_)) => "type", - Some(Pat(_)) => "text", - Some(SelfParam(_)) => "type", - Some(GenericParam(_)) => "type", - None => "text", + Some(Method(_)) => ("function", None), + Some(Macro(_)) => ("macro", None), + Some(FieldAccess(_)) => ("field", None), + Some(AssocItem(ImplItem::Method(_))) => ("function", None), + Some(AssocItem(ImplItem::Const(_))) => ("constant", None), + Some(AssocItem(ImplItem::TypeAlias(_))) => ("type", None), + Some(Def(ModuleDef::Module(_))) => ("module", None), + Some(Def(ModuleDef::Function(_))) => ("function", None), + Some(Def(ModuleDef::Struct(_))) => ("type", None), + Some(Def(ModuleDef::Union(_))) => ("type", None), + Some(Def(ModuleDef::Enum(_))) => ("type", None), + Some(Def(ModuleDef::EnumVariant(_))) => ("constant", None), + Some(Def(ModuleDef::Const(_))) => ("constant", None), + Some(Def(ModuleDef::Static(_))) => ("constant", None), + Some(Def(ModuleDef::Trait(_))) => ("type", None), + Some(Def(ModuleDef::TypeAlias(_))) => ("type", None), + Some(SelfType(_)) => ("type", None), + Some(Pat(ptr)) => ("variable", Some(hash(ptr.syntax_node_ptr().range()))), + Some(SelfParam(_)) => ("type", None), + Some(GenericParam(_)) => ("type", None), + None => ("text", None), } } else { - "text" + ("text", None) } } - NAME => "function", - TYPE_ALIAS_DEF | TYPE_ARG | TYPE_PARAM => "type", - INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", - LIFETIME => "parameter", - T![unsafe] => "keyword.unsafe", - k if is_control_keyword(k) => "keyword.control", - k if k.is_keyword() => "keyword", + NAME => { + if let Some(name) = node.as_ast_node::() { + ("variable", Some(hash(name.syntax().range()))) + } else { + ("text", None) + } + } + TYPE_ALIAS_DEF | TYPE_ARG | TYPE_PARAM => ("type", None), + INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => ("literal", None), + LIFETIME => ("parameter", None), + T![unsafe] => ("keyword.unsafe", None), + k if is_control_keyword(k) => ("keyword.control", None), + k if k.is_keyword() => ("keyword", None), _ => { + // 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 +117,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec Vec Result> { .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, + id: h.id.map(|x| x.to_string()), + }) .collect(); Ok(res) } diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index 6090eb7b92..cea0e6ce74 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs @@ -129,6 +129,7 @@ pub struct PublishDecorationsParams { pub struct Decoration { pub range: Range, pub tag: &'static str, + pub id: Option, } pub enum ParentModule {} diff --git a/crates/ra_syntax/src/syntax_node.rs b/crates/ra_syntax/src/syntax_node.rs index 80054f529a..89f92e0b7b 100644 --- a/crates/ra_syntax/src/syntax_node.rs +++ b/crates/ra_syntax/src/syntax_node.rs @@ -523,6 +523,10 @@ impl<'a> SyntaxElement<'a> { } } + pub fn as_ast_node(&self) -> Option<&T> { + self.as_node().and_then(|x| ::cast(x)) + } + pub fn as_token(&self) -> Option> { match self { SyntaxElement::Node(_) => None, diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 29cd260a4c..6b3a12f91a 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -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", diff --git a/editors/code/package.json b/editors/code/package.json index cde5fbcb8f..d8ba914f56 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -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", diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index 8389d94b8c..4597db08fe 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -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; + id?: 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,8 @@ export class Highlighter { } const byTag: Map = new Map(); + const colorfulIdents: Map = new Map(); + for (const tag of this.decorations.keys()) { byTag.set(tag, []); } @@ -84,9 +101,23 @@ export class Highlighter { if (!byTag.get(d.tag)) { continue; } - byTag - .get(d.tag)! - .push(Server.client.protocol2CodeConverter.asRange(d.range)); + + if (d.id) { + if (!colorfulIdents.has(d.id)) { + colorfulIdents.set(d.id, []); + } + colorfulIdents + .get(d.id)! + .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 +127,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); + } } } From ed89b0638b1dbf8f9a33d9a95e829e602142bb05 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sat, 25 May 2019 12:56:52 +0200 Subject: [PATCH 2/7] Hash based on binding name and shadow counter --- .../src/snapshots/tests__highlighting.snap | 14 ++--- ....snap => tests__rainbow_highlighting.snap} | 55 ++++++++++++++++--- crates/ra_ide_api/src/syntax_highlighting.rs | 26 +++++++-- crates/ra_syntax/src/syntax_text.rs | 7 ++- 4 files changed, 81 insertions(+), 21 deletions(-) rename crates/ra_ide_api/src/snapshots/{tests__sematic_highlighting.snap => tests__rainbow_highlighting.snap} (57%) diff --git a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap index 208681f103..e50003b3c0 100644 --- a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap +++ b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap @@ -1,5 +1,5 @@ --- -created: "2019-05-25T10:53:54.439877Z" +created: "2019-05-25T11:24:53.486036Z" creator: insta@0.8.1 source: crates/ra_ide_api/src/syntax_highlighting.rs expression: result @@ -20,7 +20,7 @@ Ok( range: [32; 35), tag: "variable", id: Some( - 461893210254723387, + 8465336196764640996, ), }, HighlightedRange { @@ -32,7 +32,7 @@ Ok( range: [46; 47), tag: "variable", id: Some( - 8312289520117458465, + 176272420896316891, ), }, HighlightedRange { @@ -49,7 +49,7 @@ Ok( range: [62; 63), tag: "variable", id: Some( - 4497542318236667727, + 15061637676198917049, ), }, HighlightedRange { @@ -66,7 +66,7 @@ Ok( range: [76; 79), tag: "variable", id: Some( - 4506850079084802999, + 14077410872302487760, ), }, HighlightedRange { @@ -78,7 +78,7 @@ Ok( range: [80; 81), tag: "variable", id: Some( - 16968185728268100018, + 8379786015941272633, ), }, HighlightedRange { @@ -105,7 +105,7 @@ Ok( range: [131; 135), tag: "variable", id: Some( - 14467718814232352107, + 5766414492220109266, ), }, HighlightedRange { diff --git a/crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__rainbow_highlighting.snap similarity index 57% rename from crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap rename to crates/ra_ide_api/src/snapshots/tests__rainbow_highlighting.snap index 3b3fe32e97..84cd521a24 100644 --- a/crates/ra_ide_api/src/snapshots/tests__sematic_highlighting.snap +++ b/crates/ra_ide_api/src/snapshots/tests__rainbow_highlighting.snap @@ -1,5 +1,5 @@ --- -created: "2019-05-25T10:25:13.898113Z" +created: "2019-05-25T11:21:56.117898Z" creator: insta@0.8.1 source: crates/ra_ide_api/src/syntax_highlighting.rs expression: result @@ -15,7 +15,7 @@ Ok( range: [4; 8), tag: "variable", id: Some( - 17119830160611610240, + 5766414492220109266, ), }, HighlightedRange { @@ -27,7 +27,7 @@ Ok( range: [21; 26), tag: "variable", id: Some( - 2744494144922727377, + 15975256018338854530, ), }, HighlightedRange { @@ -44,14 +44,14 @@ Ok( range: [46; 47), tag: "variable", id: Some( - 10375904121795371996, + 176272420896316891, ), }, HighlightedRange { range: [50; 55), tag: "variable", id: Some( - 2744494144922727377, + 15975256018338854530, ), }, HighlightedRange { @@ -68,14 +68,14 @@ Ok( range: [77; 78), tag: "variable", id: Some( - 8228548264153724449, + 15061637676198917049, ), }, HighlightedRange { range: [81; 86), tag: "variable", id: Some( - 2744494144922727377, + 15975256018338854530, ), }, HighlightedRange { @@ -83,5 +83,46 @@ Ok( tag: "text", id: None, }, + HighlightedRange { + range: [105; 108), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [109; 110), + tag: "variable", + id: Some( + 1714508680417729339, + ), + }, + HighlightedRange { + range: [113; 134), + tag: "string", + id: None, + }, + HighlightedRange { + range: [140; 143), + tag: "keyword", + id: None, + }, + HighlightedRange { + range: [144; 145), + tag: "variable", + id: Some( + 15953336624848413466, + ), + }, + HighlightedRange { + range: [148; 149), + tag: "variable", + id: Some( + 1714508680417729339, + ), + }, + HighlightedRange { + range: [150; 159), + tag: "text", + id: None, + }, ], ) diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs index da000c0c30..407fcda4a7 100644 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ b/crates/ra_ide_api/src/syntax_highlighting.rs @@ -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; @@ -43,6 +43,8 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec = FxHashSet::default(); + let mut bindings_shadow_count: FxHashMap = FxHashMap::default(); + let mut res = Vec::new(); for node in source_file.syntax().descendants_with_tokens() { if highlighted.contains(&node) { @@ -77,7 +79,11 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec ("type", None), Some(Def(ModuleDef::TypeAlias(_))) => ("type", None), Some(SelfType(_)) => ("type", None), - Some(Pat(ptr)) => ("variable", Some(hash(ptr.syntax_node_ptr().range()))), + Some(Pat(ptr)) => ("variable", Some(hash({ + 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(); + (text, shadow_count) + }))), Some(SelfParam(_)) => ("type", None), Some(GenericParam(_)) => ("type", None), None => ("text", None), @@ -88,7 +94,12 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec { if let Some(name) = node.as_ast_node::() { - ("variable", Some(hash(name.syntax().range()))) + ("variable", Some(hash({ + let text = name.syntax().text().to_smol_string(); + let shadow_count = bindings_shadow_count.entry(text.clone()).or_insert(1); + *shadow_count += 1; + (text, shadow_count) + }))) } else { ("text", None) } @@ -240,16 +251,19 @@ fn main() { } #[test] - fn test_sematic_highlighting() { + 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(); }"#, ); let result = analysis.highlight(file_id); - assert_debug_snapshot_matches!("sematic_highlighting", result); + assert_debug_snapshot_matches!("rainbow_highlighting", result); } } diff --git a/crates/ra_syntax/src/syntax_text.rs b/crates/ra_syntax/src/syntax_text.rs index b013164c49..bff1ed5a0d 100644 --- a/crates/ra_syntax/src/syntax_text.rs +++ b/crates/ra_syntax/src/syntax_text.rs @@ -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,11 @@ impl<'a> SyntaxText<'a> { self.chunks().collect() } + pub fn to_smol_string(&self) -> SmolStr { + // TODO: `impl iter::FromIterator<&str> for SmolStr` + self.to_string().into() + } + pub fn contains(&self, c: char) -> bool { self.chunks().any(|it| it.contains(c)) } From 43d5a4965308ec4b594725c0bd02cb046bdb730c Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sat, 25 May 2019 16:23:58 +0200 Subject: [PATCH 3/7] More clever highlighting, incl draft for structs --- Cargo.lock | 1 + crates/ra_cli/src/main.rs | 9 +- crates/ra_ide_api/Cargo.toml | 1 + crates/ra_ide_api/src/lib.rs | 2 +- .../src/snapshots/highlighting.html | 24 +-- .../src/snapshots/rainbow_highlighting.html | 27 +++ .../src/snapshots/tests__highlighting.snap | 192 ------------------ .../tests__rainbow_highlighting.snap | 128 ------------ crates/ra_ide_api/src/syntax_highlighting.rs | 185 ++++++++++------- crates/ra_syntax/src/syntax_node.rs | 4 - 10 files changed, 159 insertions(+), 414 deletions(-) create mode 100644 crates/ra_ide_api/src/snapshots/rainbow_highlighting.html delete mode 100644 crates/ra_ide_api/src/snapshots/tests__highlighting.snap delete mode 100644 crates/ra_ide_api/src/snapshots/tests__rainbow_highlighting.snap diff --git a/Cargo.lock b/Cargo.lock index 79e6329076..68c5f78747 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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)", diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index 93aba4c704..bdc7a76c6e 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs @@ -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)) => { diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml index d399d5e2e0..8939e9d79d 100644 --- a/crates/ra_ide_api/Cargo.toml +++ b/crates/ra_ide_api/Cargo.toml @@ -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 } diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index d3456d5b25..65a3b591ae 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -464,7 +464,7 @@ impl Analysis { /// Computes syntax highlighting for the given file. pub fn highlight_as_html(&self, file_id: FileId) -> Cancelable { - self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id)) + self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, true)) } /// Computes completions at the given position. diff --git a/crates/ra_ide_api/src/snapshots/highlighting.html b/crates/ra_ide_api/src/snapshots/highlighting.html index bfc0a67b11..4f4ed62a1b 100644 --- a/crates/ra_ide_api/src/snapshots/highlighting.html +++ b/crates/ra_ide_api/src/snapshots/highlighting.html @@ -1,10 +1,7 @@ -

-#[derive(Clone, Debug)]
+
#[derive(Clone, Debug)]
 struct Foo {
-    pub x: i32,
-    pub y: i32,
+    pub x: i32,
+    pub y: i32,
 }
 
 fn foo<T>() -> T {
@@ -36,10 +31,9 @@ pre {
 fn main() {
     println!("Hello, {}!", 92);
 
-    let mut vec = Vec::new();
+    let mut vec = Vec::new();
     if true {
-        vec.push(Foo { x: 0, y: 1 });
+        vec.push(Foo { x: 0, y: 1 });
     }
-    unsafe { vec.set_len(0); }
-}
-
\ No newline at end of file + unsafe { vec.set_len(0); } +}
\ 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 0000000000..729d129d07 --- /dev/null +++ b/crates/ra_ide_api/src/snapshots/rainbow_highlighting.html @@ -0,0 +1,27 @@ + + +
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();
+}
\ No newline at end of file diff --git a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap deleted file mode 100644 index e50003b3c0..0000000000 --- a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap +++ /dev/null @@ -1,192 +0,0 @@ ---- -created: "2019-05-25T11:24:53.486036Z" -creator: insta@0.8.1 -source: crates/ra_ide_api/src/syntax_highlighting.rs -expression: result ---- -Ok( - [ - HighlightedRange { - range: [1; 24), - tag: "attribute", - id: None, - }, - HighlightedRange { - range: [25; 31), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [32; 35), - tag: "variable", - id: Some( - 8465336196764640996, - ), - }, - HighlightedRange { - range: [42; 45), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [46; 47), - tag: "variable", - id: Some( - 176272420896316891, - ), - }, - HighlightedRange { - range: [49; 52), - tag: "text", - id: None, - }, - HighlightedRange { - range: [58; 61), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [62; 63), - tag: "variable", - id: Some( - 15061637676198917049, - ), - }, - HighlightedRange { - range: [65; 68), - tag: "text", - id: None, - }, - HighlightedRange { - range: [73; 75), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [76; 79), - tag: "variable", - id: Some( - 14077410872302487760, - ), - }, - HighlightedRange { - range: [80; 81), - tag: "type", - id: None, - }, - HighlightedRange { - range: [80; 81), - tag: "variable", - id: Some( - 8379786015941272633, - ), - }, - HighlightedRange { - range: [88; 89), - tag: "type", - id: None, - }, - HighlightedRange { - range: [96; 110), - tag: "macro", - id: None, - }, - HighlightedRange { - range: [117; 127), - tag: "comment", - id: None, - }, - HighlightedRange { - range: [128; 130), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [131; 135), - tag: "variable", - id: Some( - 5766414492220109266, - ), - }, - HighlightedRange { - range: [145; 153), - tag: "macro", - id: None, - }, - HighlightedRange { - range: [154; 166), - tag: "string", - id: None, - }, - HighlightedRange { - range: [168; 170), - tag: "literal", - id: None, - }, - HighlightedRange { - range: [178; 181), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [182; 185), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [186; 189), - tag: "macro", - id: None, - }, - HighlightedRange { - range: [197; 200), - tag: "macro", - id: None, - }, - HighlightedRange { - range: [192; 195), - tag: "text", - id: None, - }, - HighlightedRange { - range: [208; 211), - tag: "macro", - id: None, - }, - HighlightedRange { - range: [212; 216), - tag: "macro", - id: None, - }, - HighlightedRange { - range: [226; 227), - tag: "literal", - id: None, - }, - HighlightedRange { - range: [232; 233), - tag: "literal", - id: None, - }, - HighlightedRange { - range: [242; 248), - tag: "keyword.unsafe", - id: None, - }, - HighlightedRange { - range: [251; 254), - tag: "text", - id: None, - }, - HighlightedRange { - range: [255; 262), - tag: "text", - id: None, - }, - HighlightedRange { - range: [263; 264), - tag: "literal", - id: None, - }, - ], -) diff --git a/crates/ra_ide_api/src/snapshots/tests__rainbow_highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__rainbow_highlighting.snap deleted file mode 100644 index 84cd521a24..0000000000 --- a/crates/ra_ide_api/src/snapshots/tests__rainbow_highlighting.snap +++ /dev/null @@ -1,128 +0,0 @@ ---- -created: "2019-05-25T11:21:56.117898Z" -creator: insta@0.8.1 -source: crates/ra_ide_api/src/syntax_highlighting.rs -expression: result ---- -Ok( - [ - HighlightedRange { - range: [1; 3), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [4; 8), - tag: "variable", - id: Some( - 5766414492220109266, - ), - }, - HighlightedRange { - range: [17; 20), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [21; 26), - tag: "variable", - id: Some( - 15975256018338854530, - ), - }, - HighlightedRange { - range: [29; 36), - tag: "string", - id: None, - }, - HighlightedRange { - range: [42; 45), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [46; 47), - tag: "variable", - id: Some( - 176272420896316891, - ), - }, - HighlightedRange { - range: [50; 55), - tag: "variable", - id: Some( - 15975256018338854530, - ), - }, - HighlightedRange { - range: [56; 65), - tag: "text", - id: None, - }, - HighlightedRange { - range: [73; 76), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [77; 78), - tag: "variable", - id: Some( - 15061637676198917049, - ), - }, - HighlightedRange { - range: [81; 86), - tag: "variable", - id: Some( - 15975256018338854530, - ), - }, - HighlightedRange { - range: [87; 96), - tag: "text", - id: None, - }, - HighlightedRange { - range: [105; 108), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [109; 110), - tag: "variable", - id: Some( - 1714508680417729339, - ), - }, - HighlightedRange { - range: [113; 134), - tag: "string", - id: None, - }, - HighlightedRange { - range: [140; 143), - tag: "keyword", - id: None, - }, - HighlightedRange { - range: [144; 145), - tag: "variable", - id: Some( - 15953336624848413466, - ), - }, - HighlightedRange { - range: [148; 149), - tag: "variable", - id: Some( - 1714508680417729339, - ), - }, - HighlightedRange { - range: [150; 159), - tag: "text", - id: None, - }, - ], -) diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs index 407fcda4a7..8981c85e65 100644 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ b/crates/ra_ide_api/src/syntax_highlighting.rs @@ -10,7 +10,7 @@ use crate::{FileId, db::RootDatabase}; pub struct HighlightedRange { pub range: TextRange, pub tag: &'static str, - pub id: Option, + pub binding_hash: Option, } fn is_control_keyword(kind: SyntaxKind) -> bool { @@ -30,15 +30,18 @@ fn is_control_keyword(kind: SyntaxKind) -> bool { pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec { let _p = profile("highlight"); - let source_file = db.parse(file_id); - fn hash(x: T) -> u64 { - use std::{collections::hash_map::DefaultHasher, hash::Hasher}; + fn calc_binding_hash(file_id: FileId, text: &SmolStr, shadow_count: u32) -> u64 { + fn hash(x: T) -> u64 { + use std::{collections::hash_map::DefaultHasher, hash::Hasher}; - let mut hasher = DefaultHasher::new(); - x.hash(&mut hasher); - hasher.finish() + let mut hasher = DefaultHasher::new(); + x.hash(&mut hasher); + hasher.finish() + } + + hash((file_id, text, shadow_count)) } // Visited nodes to handle highlighting priorities @@ -50,66 +53,92 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec ("comment", None), - STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => ("string", None), - ATTR => ("attribute", None), + 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_ast_node::() { + 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}; // FIXME: try to reuse the SourceAnalyzers let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); match classify_name_ref(db, &analyzer, name_ref) { - Some(Method(_)) => ("function", None), - Some(Macro(_)) => ("macro", None), - Some(FieldAccess(_)) => ("field", None), - Some(AssocItem(ImplItem::Method(_))) => ("function", None), - Some(AssocItem(ImplItem::Const(_))) => ("constant", None), - Some(AssocItem(ImplItem::TypeAlias(_))) => ("type", None), - Some(Def(ModuleDef::Module(_))) => ("module", None), - Some(Def(ModuleDef::Function(_))) => ("function", None), - Some(Def(ModuleDef::Struct(_))) => ("type", None), - Some(Def(ModuleDef::Union(_))) => ("type", None), - Some(Def(ModuleDef::Enum(_))) => ("type", None), - Some(Def(ModuleDef::EnumVariant(_))) => ("constant", None), - Some(Def(ModuleDef::Const(_))) => ("constant", None), - Some(Def(ModuleDef::Static(_))) => ("constant", None), - Some(Def(ModuleDef::Trait(_))) => ("type", None), - Some(Def(ModuleDef::TypeAlias(_))) => ("type", None), - Some(SelfType(_)) => ("type", None), - Some(Pat(ptr)) => ("variable", Some(hash({ - 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(); - (text, shadow_count) - }))), - Some(SelfParam(_)) => ("type", None), - Some(GenericParam(_)) => ("type", None), - None => ("text", None), + Some(Method(_)) => "function", + Some(Macro(_)) => "macro", + Some(FieldAccess(field)) => { + let (hir_file_id, src) = field.source(db); + if let hir::FieldSource::Named(name) = src { + let text = name.syntax().text().to_smol_string(); + let shadow_count = 0; // potentially even from different file + binding_hash = Some(calc_binding_hash(hir_file_id.original_file(db), &text, shadow_count)); + } + + "field" + }, + Some(AssocItem(ImplItem::Method(_))) => "function", + Some(AssocItem(ImplItem::Const(_))) => "constant", + Some(AssocItem(ImplItem::TypeAlias(_))) => "type", + Some(Def(ModuleDef::Module(_))) => "module", + Some(Def(ModuleDef::Function(_))) => "function", + Some(Def(ModuleDef::Struct(_))) => "type", + Some(Def(ModuleDef::Union(_))) => "type", + Some(Def(ModuleDef::Enum(_))) => "type", + Some(Def(ModuleDef::EnumVariant(_))) => "constant", + Some(Def(ModuleDef::Const(_))) => "constant", + Some(Def(ModuleDef::Static(_))) => "constant", + Some(Def(ModuleDef::Trait(_))) => "type", + Some(Def(ModuleDef::TypeAlias(_))) => "type", + Some(SelfType(_)) => "type", + 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", } } else { - ("text", None) + "text" } } NAME => { - if let Some(name) = node.as_ast_node::() { - ("variable", Some(hash({ - let text = name.syntax().text().to_smol_string(); - let shadow_count = bindings_shadow_count.entry(text.clone()).or_insert(1); - *shadow_count += 1; - (text, shadow_count) - }))) + 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 if name.syntax().ancestors().any(|x| ast::NamedFieldDef::cast(x).is_some()) { + binding_hash = Some({ + let text = name.syntax().text().to_smol_string(); + let shadow_count = 0; + calc_binding_hash(file_id, &text, shadow_count) + }); + "variable" + } else { + "function" + } } else { - ("text", None) + "text" } } - TYPE_ALIAS_DEF | TYPE_ARG | TYPE_PARAM => ("type", None), - INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => ("literal", None), - LIFETIME => ("parameter", None), - T![unsafe] => ("keyword.unsafe", None), - k if is_control_keyword(k) => ("keyword.control", None), - k if k.is_keyword() => ("keyword", None), + TYPE_ALIAS_DEF | TYPE_ARG | TYPE_PARAM => "type", + INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", + LIFETIME => "parameter", + T![unsafe] => "keyword.unsafe", + 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) { @@ -128,7 +157,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec Vec 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::(0, 361), + s = rng.gen_range::(42, 99), + l = rng.gen_range::(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 @@ -166,16 +205,20 @@ 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::>(); - if classes.is_empty() { + if ranges.is_empty() { buf.push_str(&text); } else { - let classes = classes.join(" "); - buf.push_str(&format!("{}", classes, text)); + let classes = ranges.iter().map(|x| x.tag).collect::>().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!("{}", classes, color, text)); } } buf.push_str(""); @@ -189,11 +232,8 @@ fn html_escape(text: &str) -> String { const STYLE: &str = " "; @@ -241,12 +280,12 @@ 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 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); } @@ -261,9 +300,13 @@ fn main() { let x = "other color please!"; let y = x.to_string(); -}"#, +} +"#.trim(), ); - let result = analysis.highlight(file_id); - assert_debug_snapshot_matches!("rainbow_highlighting", result); + let dst_file = project_dir().join("crates/ra_ide_api/src/snapshots/rainbow_highlighting.html"); + let actual_html = &analysis.highlight_as_html(file_id).unwrap(); + let expected_html = &read_text(&dst_file); + std::fs::write(dst_file, &actual_html).unwrap(); + assert_eq_text!(expected_html, actual_html); } } diff --git a/crates/ra_syntax/src/syntax_node.rs b/crates/ra_syntax/src/syntax_node.rs index 89f92e0b7b..80054f529a 100644 --- a/crates/ra_syntax/src/syntax_node.rs +++ b/crates/ra_syntax/src/syntax_node.rs @@ -523,10 +523,6 @@ impl<'a> SyntaxElement<'a> { } } - pub fn as_ast_node(&self) -> Option<&T> { - self.as_node().and_then(|x| ::cast(x)) - } - pub fn as_token(&self) -> Option> { match self { SyntaxElement::Node(_) => None, From 2b200f6e1a600e263f314efba5d8c3743095071b Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sat, 25 May 2019 16:29:39 +0200 Subject: [PATCH 4/7] Disable broken struct field rainbowing --- crates/ra_ide_api/src/lib.rs | 6 +++--- .../ra_ide_api/src/snapshots/highlighting.html | 6 +++--- crates/ra_ide_api/src/syntax_highlighting.rs | 18 +----------------- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 65a3b591ae..452407e8e3 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -463,8 +463,8 @@ impl Analysis { } /// Computes syntax highlighting for the given file. - pub fn highlight_as_html(&self, file_id: FileId) -> Cancelable { - self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, true)) + pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancelable { + 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> { 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 4f4ed62a1b..ebd187a35d 100644 --- a/crates/ra_ide_api/src/snapshots/highlighting.html +++ b/crates/ra_ide_api/src/snapshots/highlighting.html @@ -19,8 +19,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4e
#[derive(Clone, Debug)]
 struct Foo {
-    pub x: i32,
-    pub y: i32,
+    pub x: i32,
+    pub y: i32,
 }
 
 fn foo<T>() -> T {
@@ -33,7 +33,7 @@ pre        { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4e
 
     let mut vec = Vec::new();
     if true {
-        vec.push(Foo { x: 0, y: 1 });
+        vec.push(Foo { x: 0, y: 1 });
     }
     unsafe { vec.set_len(0); }
 }
\ 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 8981c85e65..e46686ab94 100644 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ b/crates/ra_ide_api/src/syntax_highlighting.rs @@ -68,16 +68,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec "function", Some(Macro(_)) => "macro", - Some(FieldAccess(field)) => { - let (hir_file_id, src) = field.source(db); - if let hir::FieldSource::Named(name) = src { - let text = name.syntax().text().to_smol_string(); - let shadow_count = 0; // potentially even from different file - binding_hash = Some(calc_binding_hash(hir_file_id.original_file(db), &text, shadow_count)); - } - - "field" - }, + Some(FieldAccess(_)) => "field", Some(AssocItem(ImplItem::Method(_))) => "function", Some(AssocItem(ImplItem::Const(_))) => "constant", Some(AssocItem(ImplItem::TypeAlias(_))) => "type", @@ -119,13 +110,6 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec Date: Sun, 26 May 2019 11:56:31 +0200 Subject: [PATCH 5/7] make it build again --- crates/ra_cli/src/main.rs | 2 +- crates/ra_ide_api/src/syntax_highlighting.rs | 38 +++++++++++++------ .../ra_lsp_server/src/main_loop/handlers.rs | 2 +- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index bdc7a76c6e..84a1564ceb 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs @@ -18,7 +18,7 @@ fn main() -> Result<()> { .subcommand(SubCommand::with_name("symbols")) .subcommand( SubCommand::with_name("highlight") - .arg(Arg::with_name("rainbow").short("r").long("rainbow")) + .arg(Arg::with_name("rainbow").short("r").long("rainbow")), ) .subcommand( SubCommand::with_name("analysis-stats") diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs index e46686ab94..dcefb05134 100644 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ b/crates/ra_ide_api/src/syntax_highlighting.rs @@ -85,13 +85,18 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec "type", 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(); + 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", @@ -105,7 +110,8 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec String { use rand::prelude::*; let mut rng = SmallRng::seed_from_u64(seed); - format!("hsl({h},{s}%,{l}%)", + format!( + "hsl({h},{s}%,{l}%)", h = rng.gen_range::(0, 361), s = rng.gen_range::(42, 99), l = rng.gen_range::(40, 91), @@ -199,8 +206,12 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo let classes = ranges.iter().map(|x| x.tag).collect::>().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() + (true, Some(hash)) => format!( + " data-binding-hash=\"{}\" style=\"color: {};\"", + hash, + rainbowify(hash) + ), + _ => "".into(), }; buf.push_str(&format!("{}", classes, color, text)); } @@ -264,10 +275,11 @@ fn main() { } unsafe { vec.set_len(0); } } -"#.trim(), +"# + .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(); assert_eq_text!(expected_html, actual_html); @@ -285,10 +297,12 @@ fn main() { let x = "other color please!"; let y = x.to_string(); } -"#.trim(), +"# + .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).unwrap(); + 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); diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 5dfd64ed41..1df8224ba6 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -875,7 +875,7 @@ fn highlight(world: &ServerWorld, file_id: FileId) -> Result> { .map(|h| Decoration { range: h.range.conv_with(&line_index), tag: h.tag, - id: h.id.map(|x| x.to_string()), + id: h.binding_hash.map(|x| x.to_string()), }) .collect(); Ok(res) From 4ac338b608bb40c5126d019db63232e7834914c2 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sun, 26 May 2019 15:39:57 +0200 Subject: [PATCH 6/7] rename stray id field --- crates/ra_lsp_server/src/main_loop/handlers.rs | 2 +- crates/ra_lsp_server/src/req.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 1df8224ba6..e36db12b32 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -875,7 +875,7 @@ fn highlight(world: &ServerWorld, file_id: FileId) -> Result> { .map(|h| Decoration { range: h.range.conv_with(&line_index), tag: h.tag, - id: h.binding_hash.map(|x| x.to_string()), + binding_hash: h.binding_hash.map(|x| x.to_string()), }) .collect(); Ok(res) diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index cea0e6ce74..992c24eac3 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs @@ -129,7 +129,7 @@ pub struct PublishDecorationsParams { pub struct Decoration { pub range: Range, pub tag: &'static str, - pub id: Option, + pub binding_hash: Option, } pub enum ParentModule {} From 1e6ba1901550fb1610a1a464c48ec358cd3c339c Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Mon, 27 May 2019 11:26:15 +0200 Subject: [PATCH 7/7] Make rainbows optional --- crates/ra_syntax/src/syntax_text.rs | 3 ++- docs/user/features.md | 9 +++++++++ editors/code/package.json | 5 +++++ editors/code/src/config.ts | 7 +++++++ editors/code/src/highlighting.ts | 11 ++++++----- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/crates/ra_syntax/src/syntax_text.rs b/crates/ra_syntax/src/syntax_text.rs index bff1ed5a0d..c9038cd5c0 100644 --- a/crates/ra_syntax/src/syntax_text.rs +++ b/crates/ra_syntax/src/syntax_text.rs @@ -35,7 +35,8 @@ impl<'a> SyntaxText<'a> { } pub fn to_smol_string(&self) -> SmolStr { - // TODO: `impl iter::FromIterator<&str> for 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() } diff --git a/docs/user/features.md b/docs/user/features.md index 22470bc563..b6e6008c42 100644 --- a/docs/user/features.md +++ b/docs/user/features.md @@ -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. diff --git a/editors/code/package.json b/editors/code/package.json index d8ba914f56..05c8083949 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -164,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, diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 481a5e5f18..8d73a6b340 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -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' diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index 4597db08fe..52a0bd4bb1 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -7,7 +7,7 @@ import { Server } from './server'; export interface Decoration { range: lc.Range; tag: string; - id?: string; + bindingHash?: string; } // Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76 @@ -92,6 +92,7 @@ export class Highlighter { const byTag: Map = new Map(); const colorfulIdents: Map = new Map(); + const rainbowTime = Server.config.rainbowHighlightingOn; for (const tag of this.decorations.keys()) { byTag.set(tag, []); @@ -102,12 +103,12 @@ export class Highlighter { continue; } - if (d.id) { - if (!colorfulIdents.has(d.id)) { - colorfulIdents.set(d.id, []); + if (rainbowTime && d.bindingHash) { + if (!colorfulIdents.has(d.bindingHash)) { + colorfulIdents.set(d.bindingHash, []); } colorfulIdents - .get(d.id)! + .get(d.bindingHash)! .push( Server.client.protocol2CodeConverter.asRange(d.range) );