rust/editors/code/src/highlighting.ts

270 lines
8.3 KiB
TypeScript
Raw Normal View History

2018-10-07 20:44:25 +00:00
import * as vscode from 'vscode';
2018-10-07 20:59:02 +00:00
import * as lc from 'vscode-languageclient';
2019-12-30 19:29:21 +00:00
2019-12-31 10:06:50 +00:00
import { ColorTheme, TextMateRuleSettings } from './color_theme';
2018-10-07 20:44:25 +00:00
2019-12-31 17:14:00 +00:00
import { Ctx, sendRequestWithRetry } from './ctx';
2019-12-30 19:29:21 +00:00
export function activateHighlighting(ctx: Ctx) {
2019-12-30 22:40:48 +00:00
const highlighter = new Highlighter(ctx);
2019-12-31 17:14:00 +00:00
ctx.onDidRestart(client => {
client.onNotification(
'rust-analyzer/publishDecorations',
(params: PublishDecorationsParams) => {
if (!ctx.config.highlightingOn) return;
const targetEditor = vscode.window.visibleTextEditors.find(
editor => {
const unescapedUri = unescape(
editor.document.uri.toString(),
);
// Unescaped URI looks like:
// file:///c:/Workspace/ra-test/src/main.rs
return unescapedUri === params.uri;
},
);
if (!targetEditor) return;
highlighter.setHighlights(targetEditor, params.decorations);
},
);
2019-12-31 17:55:34 +00:00
});
2019-12-30 22:12:33 +00:00
vscode.workspace.onDidChangeConfiguration(
_ => highlighter.removeHighlights(),
null,
2019-12-30 22:12:33 +00:00
ctx.subscriptions,
);
2019-12-30 19:29:21 +00:00
vscode.window.onDidChangeActiveTextEditor(
async (editor: vscode.TextEditor | undefined) => {
if (!editor || editor.document.languageId !== 'rust') return;
2019-12-30 19:46:14 +00:00
if (!ctx.config.highlightingOn) return;
const client = ctx.client;
2019-12-31 17:14:00 +00:00
if (!client) return;
2019-12-30 19:29:21 +00:00
const params: lc.TextDocumentIdentifier = {
uri: editor.document.uri.toString(),
};
2019-12-31 17:14:00 +00:00
const decorations = await sendRequestWithRetry<Decoration[]>(
client,
2019-12-30 19:29:21 +00:00
'rust-analyzer/decorationsRequest',
params,
);
2019-12-30 22:12:33 +00:00
highlighter.setHighlights(editor, decorations);
2019-12-30 19:29:21 +00:00
},
null,
2019-12-30 22:12:33 +00:00
ctx.subscriptions,
2019-12-30 19:29:21 +00:00
);
}
2018-10-07 20:44:25 +00:00
2019-12-30 22:12:33 +00:00
interface PublishDecorationsParams {
uri: string;
decorations: Decoration[];
}
2019-12-31 01:17:50 +00:00
interface Decoration {
2018-10-07 20:59:02 +00:00
range: lc.Range;
tag: string;
2019-05-27 09:26:15 +00:00
bindingHash?: string;
}
// Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76
function fancify(seed: string, shade: 'light' | 'dark') {
2020-02-04 22:13:46 +00:00
const random = randomU32Numbers(hashString(seed));
const randomInt = (min: number, max: number) => {
return Math.abs(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}%)`;
2018-10-07 20:44:25 +00:00
}
2019-12-30 22:12:33 +00:00
class Highlighter {
2019-12-30 22:40:48 +00:00
private ctx: Ctx;
2018-10-08 21:38:33 +00:00
private decorations: Map<
string,
vscode.TextEditorDecorationType
> | null = null;
2018-10-08 18:18:55 +00:00
2019-12-31 01:17:50 +00:00
constructor(ctx: Ctx) {
this.ctx = ctx;
}
2018-10-07 20:59:02 +00:00
public removeHighlights() {
2018-10-08 18:18:55 +00:00
if (this.decorations == null) {
return;
2018-10-07 20:44:25 +00:00
}
2018-10-08 18:18:55 +00:00
// Decorations are removed when the object is disposed
for (const decoration of this.decorations.values()) {
decoration.dispose();
}
this.decorations = null;
2018-10-07 20:44:25 +00:00
}
2018-10-08 21:38:33 +00:00
public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) {
const client = this.ctx.client;
2019-12-31 17:14:00 +00:00
if (!client) return;
2018-10-07 20:44:25 +00:00
// Initialize decorations if necessary
//
// Note: decoration objects need to be kept around so we can dispose them
// if the user disables syntax highlighting
2018-10-08 18:18:55 +00:00
if (this.decorations == null) {
2019-12-31 01:17:50 +00:00
this.decorations = initDecorations();
2018-10-07 20:44:25 +00:00
}
2018-10-07 20:59:02 +00:00
const byTag: Map<string, vscode.Range[]> = new Map();
2019-07-19 11:43:36 +00:00
const colorfulIdents: Map<
string,
[vscode.Range[], boolean]
> = new Map();
2019-12-30 22:40:48 +00:00
const rainbowTime = this.ctx.config.rainbowHighlightingOn;
2018-10-08 18:18:55 +00:00
for (const tag of this.decorations.keys()) {
2018-10-07 20:59:02 +00:00
byTag.set(tag, []);
2018-10-07 20:44:25 +00:00
}
2018-10-07 20:59:02 +00:00
for (const d of highlights) {
2018-10-07 20:44:25 +00:00
if (!byTag.get(d.tag)) {
2018-10-07 20:59:02 +00:00
continue;
2018-10-07 20:44:25 +00:00
}
2019-05-27 09:26:15 +00:00
if (rainbowTime && d.bindingHash) {
if (!colorfulIdents.has(d.bindingHash)) {
2019-07-19 11:43:36 +00:00
const mut = d.tag.endsWith('.mut');
colorfulIdents.set(d.bindingHash, [[], mut]);
}
colorfulIdents
2019-07-19 11:43:36 +00:00
.get(d.bindingHash)![0]
.push(
2019-12-31 17:14:00 +00:00
client.protocol2CodeConverter.asRange(d.range),
);
} else {
byTag
.get(d.tag)!
.push(
2019-12-31 17:14:00 +00:00
client.protocol2CodeConverter.asRange(d.range),
);
}
2018-10-07 20:44:25 +00:00
}
2018-10-07 20:59:02 +00:00
for (const tag of byTag.keys()) {
2018-10-08 21:38:33 +00:00
const dec = this.decorations.get(
2019-12-09 18:57:55 +00:00
tag,
2018-10-08 21:38:33 +00:00
) as vscode.TextEditorDecorationType;
2018-10-07 20:59:02 +00:00
const ranges = byTag.get(tag)!;
editor.setDecorations(dec, ranges);
2018-10-07 20:44:25 +00:00
}
2019-07-19 11:43:36 +00:00
for (const [hash, [ranges, mut]] of colorfulIdents.entries()) {
const textDecoration = mut ? 'underline' : undefined;
const dec = vscode.window.createTextEditorDecorationType({
2019-07-19 11:43:36 +00:00
light: { color: fancify(hash, 'light'), textDecoration },
2019-12-09 18:57:55 +00:00
dark: { color: fancify(hash, 'dark'), textDecoration },
});
editor.setDecorations(dec, ranges);
}
2018-10-07 20:44:25 +00:00
}
}
2019-12-31 01:17:50 +00:00
2019-12-31 10:06:50 +00:00
function initDecorations(): Map<string, vscode.TextEditorDecorationType> {
const theme = ColorTheme.load();
2019-12-31 17:55:34 +00:00
const res = new Map();
2019-12-31 10:06:50 +00:00
TAG_TO_SCOPES.forEach((scopes, tag) => {
2019-12-31 17:55:34 +00:00
if (!scopes) throw `unmapped tag: ${tag}`;
const rule = theme.lookup(scopes);
2019-12-31 10:06:50 +00:00
const decor = createDecorationFromTextmate(rule);
2019-12-31 17:55:34 +00:00
res.set(tag, decor);
});
2019-12-31 10:06:50 +00:00
return res;
2019-12-31 01:17:50 +00:00
}
function createDecorationFromTextmate(
themeStyle: TextMateRuleSettings,
2019-12-31 01:17:50 +00:00
): vscode.TextEditorDecorationType {
const decorationOptions: vscode.DecorationRenderOptions = {};
decorationOptions.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen;
if (themeStyle.foreground) {
decorationOptions.color = themeStyle.foreground;
}
if (themeStyle.background) {
decorationOptions.backgroundColor = themeStyle.background;
}
if (themeStyle.fontStyle) {
const parts: string[] = themeStyle.fontStyle.split(' ');
parts.forEach(part => {
switch (part) {
case 'italic':
decorationOptions.fontStyle = 'italic';
break;
case 'bold':
decorationOptions.fontWeight = 'bold';
break;
case 'underline':
decorationOptions.textDecoration = 'underline';
break;
default:
break;
}
});
}
return vscode.window.createTextEditorDecorationType(decorationOptions);
}
2019-12-31 10:06:50 +00:00
// sync with tags from `syntax_highlighting.rs`.
const TAG_TO_SCOPES = new Map<string, string[]>([
["field", ["entity.name.field"]],
["function", ["entity.name.function"]],
["module", ["entity.name.module"]],
["constant", ["entity.name.constant"]],
["macro", ["entity.name.macro"]],
["variable", ["variable"]],
["variable.mut", ["variable", "meta.mutable"]],
["type", ["entity.name.type"]],
["type.builtin", ["entity.name.type", "support.type.primitive"]],
["type.self", ["entity.name.type.parameter.self"]],
["type.param", ["entity.name.type.parameter"]],
["type.lifetime", ["entity.name.type.lifetime"]],
["literal.byte", ["constant.character.byte"]],
["literal.char", ["constant.character"]],
["literal.numeric", ["constant.numeric"]],
["comment", ["comment"]],
["string", ["string.quoted"]],
["attribute", ["meta.attribute"]],
["keyword", ["keyword"]],
["keyword.unsafe", ["keyword.other.unsafe"]],
["keyword.control", ["keyword.control"]],
]);
function randomU32Numbers(seed: number) {
let random = seed | 0;
return () => {
random ^= random << 13;
random ^= random >> 17;
random ^= random << 5;
random |= 0;
2020-02-04 22:13:46 +00:00
return random;
};
}
function hashString(str: string): number {
let res = 0;
for (let i = 0; i < str.length; ++i) {
2020-02-04 22:13:46 +00:00
const c = str.codePointAt(i)!;
res = (res * 31 + c) & ~0;
}
return res;
}