rust/editors/code/src/highlighting.ts

227 lines
7.4 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
import * as seedrandom from 'seedrandom';
import * as scopes from './scopes';
import * as scopesMapper from './scopes_mapper';
2018-10-07 20:44:25 +00:00
import { Server } from './server';
2019-12-30 19:29:21 +00:00
import { Ctx } from './ctx';
export function activateHighlighting(ctx: Ctx) {
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;
2019-12-30 19:29:21 +00:00
const params: lc.TextDocumentIdentifier = {
uri: editor.document.uri.toString(),
};
const decorations = await ctx.client.sendRequest<Decoration[]>(
'rust-analyzer/decorationsRequest',
params,
);
Server.highlighter.setHighlights(editor, decorations);
},
);
}
2018-10-07 20:44:25 +00:00
export 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') {
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}%)`;
2018-10-07 20:44:25 +00:00
}
function createDecorationFromTextmate(
themeStyle: scopes.TextMateRuleSettings,
): 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);
}
2018-10-07 20:44:25 +00:00
export class Highlighter {
2018-10-08 21:38:33 +00:00
private static initDecorations(): Map<
string,
vscode.TextEditorDecorationType
> {
2019-07-19 11:43:36 +00:00
const decoration = (
tag: string,
2019-12-09 18:57:55 +00:00
textDecoration?: string,
): [string, vscode.TextEditorDecorationType] => {
const rule = scopesMapper.toRule(tag, scopes.find);
if (rule) {
const decor = createDecorationFromTextmate(rule);
return [tag, decor];
} else {
const fallBackTag = 'ralsp.' + tag;
2019-11-05 13:22:09 +00:00
// console.log(' ');
// console.log('Missing theme for: <"' + tag + '"> for following mapped scopes:');
// console.log(scopesMapper.find(tag));
// console.log('Falling back to values defined in: ' + fallBackTag);
// console.log(' ');
const color = new vscode.ThemeColor(fallBackTag);
const decor = vscode.window.createTextEditorDecorationType({
color,
textDecoration,
});
return [tag, decor];
}
};
2018-10-08 18:18:55 +00:00
2019-12-08 18:27:50 +00:00
const decorations: Iterable<[
string,
2019-12-09 18:57:55 +00:00
vscode.TextEditorDecorationType,
2019-12-08 18:27:50 +00:00
]> = [
decoration('comment'),
decoration('string'),
decoration('keyword'),
decoration('keyword.control'),
decoration('keyword.unsafe'),
decoration('function'),
decoration('parameter'),
decoration('constant'),
2019-12-17 13:43:37 +00:00
decoration('type.builtin'),
2019-12-14 11:24:07 +00:00
decoration('type.generic'),
2019-12-14 11:29:42 +00:00
decoration('type.lifetime'),
2019-12-17 13:43:37 +00:00
decoration('type.param'),
decoration('type.self'),
decoration('type'),
decoration('text'),
decoration('attribute'),
decoration('literal'),
2019-12-14 11:24:07 +00:00
decoration('literal.numeric'),
decoration('literal.char'),
decoration('literal.byte'),
decoration('macro'),
decoration('variable'),
decoration('variable.mut', 'underline'),
decoration('field'),
2019-12-09 18:57:55 +00:00
decoration('module'),
];
2018-10-08 18:18:55 +00:00
return new Map<string, vscode.TextEditorDecorationType>(decorations);
2018-10-07 20:44:25 +00:00
}
2018-10-08 21:38:33 +00:00
private decorations: Map<
string,
vscode.TextEditorDecorationType
> | null = null;
2018-10-08 18:18:55 +00:00
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[]) {
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) {
this.decorations = Highlighter.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-05-27 09:26:15 +00:00
const rainbowTime = Server.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-09 18:57:55 +00:00
Server.client.protocol2CodeConverter.asRange(d.range),
);
} else {
byTag
.get(d.tag)!
.push(
2019-12-09 18:57:55 +00:00
Server.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
}
}