mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-28 09:44:08 +00:00
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?
This commit is contained in:
parent
4b48cff022
commit
5bf3e949e8
192
crates/ra_ide_api/src/snapshots/tests__highlighting.snap
Normal file
192
crates/ra_ide_api/src/snapshots/tests__highlighting.snap
Normal file
@ -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,
|
||||
},
|
||||
],
|
||||
)
|
@ -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,
|
||||
},
|
||||
],
|
||||
)
|
@ -10,6 +10,7 @@ use crate::{FileId, db::RootDatabase};
|
||||
pub struct HighlightedRange {
|
||||
pub range: TextRange,
|
||||
pub tag: &'static str,
|
||||
pub id: Option<u64>,
|
||||
}
|
||||
|
||||
fn is_control_keyword(kind: SyntaxKind) -> bool {
|
||||
@ -32,6 +33,14 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
|
||||
|
||||
let source_file = db.parse(file_id);
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// Visited nodes to handle highlighting priorities
|
||||
let mut highlighted: FxHashSet<SyntaxElement> = FxHashSet::default();
|
||||
let mut res = Vec::new();
|
||||
@ -39,52 +48,59 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
|
||||
if highlighted.contains(&node) {
|
||||
continue;
|
||||
}
|
||||
let tag = match node.kind() {
|
||||
COMMENT => "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::<ast::NameRef>() {
|
||||
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::<ast::Name>() {
|
||||
("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<HighlightedRa
|
||||
res.push(HighlightedRange {
|
||||
range: TextRange::from_to(range_start, range_end),
|
||||
tag: "macro",
|
||||
id: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -109,7 +126,7 @@ 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, id })
|
||||
}
|
||||
res
|
||||
}
|
||||
@ -221,4 +238,18 @@ fn main() {
|
||||
// std::fs::write(dst_file, &actual_html).unwrap();
|
||||
assert_eq_text!(expected_html, actual_html);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sematic_highlighting() {
|
||||
let (analysis, file_id) = single_file(
|
||||
r#"
|
||||
fn main() {
|
||||
let hello = "hello";
|
||||
let x = hello.to_string();
|
||||
let y = hello.to_string();
|
||||
}"#,
|
||||
);
|
||||
let result = analysis.highlight(file_id);
|
||||
assert_debug_snapshot_matches!("sematic_highlighting", result);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
id: h.id.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 id: Option<String>,
|
||||
}
|
||||
|
||||
pub enum ParentModule {}
|
||||
|
@ -523,6 +523,10 @@ impl<'a> SyntaxElement<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ast_node<T: AstNode>(&self) -> Option<&T> {
|
||||
self.as_node().and_then(|x| <T as AstNode>::cast(x))
|
||||
}
|
||||
|
||||
pub fn as_token(&self) -> Option<SyntaxToken<'a>> {
|
||||
match self {
|
||||
SyntaxElement::Node(_) => None,
|
||||
|
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",
|
||||
|
@ -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<string, vscode.Range[]> = new Map();
|
||||
const colorfulIdents: Map<string, vscode.Range[]> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user