1 import * as vscode from 'vscode';
2 import * as lc from 'vscode-languageclient';
3 import * as seedrandom_ from 'seedrandom';
4 const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular2-library/issues/221#issuecomment-355945207
6 import { ColorTheme, TextMateRuleSettings } from './color_theme';
8 import { Ctx } from './ctx';
10 export function activateHighlighting(ctx: Ctx) {
11 const highlighter = new Highlighter(ctx);
13 ctx.client.onReady().then(() => {
14 ctx.client.onNotification(
15 'rust-analyzer/publishDecorations',
16 (params: PublishDecorationsParams) => {
17 if (!ctx.config.highlightingOn) return;
19 const targetEditor = vscode.window.visibleTextEditors.find(
21 const unescapedUri = unescape(
22 editor.document.uri.toString(),
24 // Unescaped URI looks like:
25 // file:///c:/Workspace/ra-test/src/main.rs
26 return unescapedUri === params.uri;
29 if (!targetEditor) return;
31 highlighter.setHighlights(targetEditor, params.decorations);
36 vscode.workspace.onDidChangeConfiguration(
37 _ => highlighter.removeHighlights(),
41 vscode.window.onDidChangeActiveTextEditor(
42 async (editor: vscode.TextEditor | undefined) => {
43 if (!editor || editor.document.languageId !== 'rust') return;
44 if (!ctx.config.highlightingOn) return;
46 const params: lc.TextDocumentIdentifier = {
47 uri: editor.document.uri.toString(),
49 const decorations = await ctx.sendRequestWithRetry<Decoration[]>(
50 'rust-analyzer/decorationsRequest',
53 highlighter.setHighlights(editor, decorations);
59 interface PublishDecorationsParams {
61 decorations: Decoration[];
64 interface Decoration {
70 // Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76
71 function fancify(seed: string, shade: 'light' | 'dark') {
72 const random = seedrandom(seed);
73 const randomInt = (min: number, max: number) => {
74 return Math.floor(random() * (max - min + 1)) + min;
77 const h = randomInt(0, 360);
78 const s = randomInt(42, 98);
79 const l = shade === 'light' ? randomInt(15, 40) : randomInt(40, 90);
80 return `hsl(${h},${s}%,${l}%)`;
85 private decorations: Map<
87 vscode.TextEditorDecorationType
90 constructor(ctx: Ctx) {
94 public removeHighlights() {
95 if (this.decorations == null) {
99 // Decorations are removed when the object is disposed
100 for (const decoration of this.decorations.values()) {
101 decoration.dispose();
104 this.decorations = null;
107 public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) {
108 // Initialize decorations if necessary
110 // Note: decoration objects need to be kept around so we can dispose them
111 // if the user disables syntax highlighting
112 if (this.decorations == null) {
113 this.decorations = initDecorations();
116 const byTag: Map<string, vscode.Range[]> = new Map();
117 const colorfulIdents: Map<
119 [vscode.Range[], boolean]
121 const rainbowTime = this.ctx.config.rainbowHighlightingOn;
123 for (const tag of this.decorations.keys()) {
127 for (const d of highlights) {
128 if (!byTag.get(d.tag)) {
132 if (rainbowTime && d.bindingHash) {
133 if (!colorfulIdents.has(d.bindingHash)) {
134 const mut = d.tag.endsWith('.mut');
135 colorfulIdents.set(d.bindingHash, [[], mut]);
138 .get(d.bindingHash)![0]
140 this.ctx.client.protocol2CodeConverter.asRange(d.range),
146 this.ctx.client.protocol2CodeConverter.asRange(d.range),
151 for (const tag of byTag.keys()) {
152 const dec = this.decorations.get(
154 ) as vscode.TextEditorDecorationType;
155 const ranges = byTag.get(tag)!;
156 editor.setDecorations(dec, ranges);
159 for (const [hash, [ranges, mut]] of colorfulIdents.entries()) {
160 const textDecoration = mut ? 'underline' : undefined;
161 const dec = vscode.window.createTextEditorDecorationType({
162 light: { color: fancify(hash, 'light'), textDecoration },
163 dark: { color: fancify(hash, 'dark'), textDecoration },
165 editor.setDecorations(dec, ranges);
170 function initDecorations(): Map<string, vscode.TextEditorDecorationType> {
171 const theme = ColorTheme.load();
172 const res = new Map()
173 TAG_TO_SCOPES.forEach((scopes, tag) => {
174 if (!scopes) throw `unmapped tag: ${tag}`
175 let rule = theme.lookup(scopes)
176 const decor = createDecorationFromTextmate(rule);
182 function createDecorationFromTextmate(
183 themeStyle: TextMateRuleSettings,
184 ): vscode.TextEditorDecorationType {
185 const decorationOptions: vscode.DecorationRenderOptions = {};
186 decorationOptions.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen;
188 if (themeStyle.foreground) {
189 decorationOptions.color = themeStyle.foreground;
192 if (themeStyle.background) {
193 decorationOptions.backgroundColor = themeStyle.background;
196 if (themeStyle.fontStyle) {
197 const parts: string[] = themeStyle.fontStyle.split(' ');
198 parts.forEach(part => {
201 decorationOptions.fontStyle = 'italic';
204 decorationOptions.fontWeight = 'bold';
207 decorationOptions.textDecoration = 'underline';
214 return vscode.window.createTextEditorDecorationType(decorationOptions);
217 // sync with tags from `syntax_highlighting.rs`.
218 const TAG_TO_SCOPES = new Map<string, string[]>([
219 ["field", ["entity.name.field"]],
220 ["function", ["entity.name.function"]],
221 ["module", ["entity.name.module"]],
222 ["constant", ["entity.name.constant"]],
223 ["macro", ["entity.name.macro"]],
225 ["variable", ["variable"]],
226 ["variable.mut", ["variable", "meta.mutable"]],
228 ["type", ["entity.name.type"]],
229 ["type.builtin", ["entity.name.type", "support.type.primitive"]],
230 ["type.self", ["entity.name.type.parameter.self"]],
231 ["type.param", ["entity.name.type.parameter"]],
232 ["type.lifetime", ["entity.name.type.lifetime"]],
234 ["literal.byte", ["constant.character.byte"]],
235 ["literal.char", ["constant.character"]],
236 ["literal.numeric", ["constant.numeric"]],
238 ["comment", ["comment"]],
239 ["string", ["string.quoted"]],
240 ["attribute", ["meta.attribute"]],
242 ["keyword", ["keyword"]],
243 ["keyword.unsafe", ["keyword.other.unsafe"]],
244 ["keyword.control", ["keyword.control"]],