]> git.lizzy.rs Git - rust.git/blob - editors/code/src/highlighting.ts
Drop support for legacy colorization
[rust.git] / editors / code / src / highlighting.ts
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
5
6 import { ColorTheme, TextMateRuleSettings } from './color_theme';
7
8 import { Ctx } from './ctx';
9
10 export function activateHighlighting(ctx: Ctx) {
11     const highlighter = new Highlighter(ctx);
12
13     ctx.client.onReady().then(() => {
14         ctx.client.onNotification(
15             'rust-analyzer/publishDecorations',
16             (params: PublishDecorationsParams) => {
17                 if (!ctx.config.highlightingOn) return;
18
19                 const targetEditor = vscode.window.visibleTextEditors.find(
20                     editor => {
21                         const unescapedUri = unescape(
22                             editor.document.uri.toString(),
23                         );
24                         // Unescaped URI looks like:
25                         // file:///c:/Workspace/ra-test/src/main.rs
26                         return unescapedUri === params.uri;
27                     },
28                 );
29                 if (!targetEditor) return;
30
31                 highlighter.setHighlights(targetEditor, params.decorations);
32             },
33         );
34     });
35
36     vscode.workspace.onDidChangeConfiguration(
37         _ => highlighter.removeHighlights(),
38         ctx.subscriptions,
39     );
40
41     vscode.window.onDidChangeActiveTextEditor(
42         async (editor: vscode.TextEditor | undefined) => {
43             if (!editor || editor.document.languageId !== 'rust') return;
44             if (!ctx.config.highlightingOn) return;
45
46             const params: lc.TextDocumentIdentifier = {
47                 uri: editor.document.uri.toString(),
48             };
49             const decorations = await ctx.sendRequestWithRetry<Decoration[]>(
50                 'rust-analyzer/decorationsRequest',
51                 params,
52             );
53             highlighter.setHighlights(editor, decorations);
54         },
55         ctx.subscriptions,
56     );
57 }
58
59 interface PublishDecorationsParams {
60     uri: string;
61     decorations: Decoration[];
62 }
63
64 interface Decoration {
65     range: lc.Range;
66     tag: string;
67     bindingHash?: string;
68 }
69
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;
75     };
76
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}%)`;
81 }
82
83 class Highlighter {
84     private ctx: Ctx;
85     private decorations: Map<
86         string,
87         vscode.TextEditorDecorationType
88     > | null = null;
89
90     constructor(ctx: Ctx) {
91         this.ctx = ctx;
92     }
93
94     public removeHighlights() {
95         if (this.decorations == null) {
96             return;
97         }
98
99         // Decorations are removed when the object is disposed
100         for (const decoration of this.decorations.values()) {
101             decoration.dispose();
102         }
103
104         this.decorations = null;
105     }
106
107     public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) {
108         // Initialize decorations if necessary
109         //
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();
114         }
115
116         const byTag: Map<string, vscode.Range[]> = new Map();
117         const colorfulIdents: Map<
118             string,
119             [vscode.Range[], boolean]
120         > = new Map();
121         const rainbowTime = this.ctx.config.rainbowHighlightingOn;
122
123         for (const tag of this.decorations.keys()) {
124             byTag.set(tag, []);
125         }
126
127         for (const d of highlights) {
128             if (!byTag.get(d.tag)) {
129                 continue;
130             }
131
132             if (rainbowTime && d.bindingHash) {
133                 if (!colorfulIdents.has(d.bindingHash)) {
134                     const mut = d.tag.endsWith('.mut');
135                     colorfulIdents.set(d.bindingHash, [[], mut]);
136                 }
137                 colorfulIdents
138                     .get(d.bindingHash)![0]
139                     .push(
140                         this.ctx.client.protocol2CodeConverter.asRange(d.range),
141                     );
142             } else {
143                 byTag
144                     .get(d.tag)!
145                     .push(
146                         this.ctx.client.protocol2CodeConverter.asRange(d.range),
147                     );
148             }
149         }
150
151         for (const tag of byTag.keys()) {
152             const dec = this.decorations.get(
153                 tag,
154             ) as vscode.TextEditorDecorationType;
155             const ranges = byTag.get(tag)!;
156             editor.setDecorations(dec, ranges);
157         }
158
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 },
164             });
165             editor.setDecorations(dec, ranges);
166         }
167     }
168 }
169
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);
177         res.set(tag, decor)
178     })
179     return res;
180 }
181
182 function createDecorationFromTextmate(
183     themeStyle: TextMateRuleSettings,
184 ): vscode.TextEditorDecorationType {
185     const decorationOptions: vscode.DecorationRenderOptions = {};
186     decorationOptions.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen;
187
188     if (themeStyle.foreground) {
189         decorationOptions.color = themeStyle.foreground;
190     }
191
192     if (themeStyle.background) {
193         decorationOptions.backgroundColor = themeStyle.background;
194     }
195
196     if (themeStyle.fontStyle) {
197         const parts: string[] = themeStyle.fontStyle.split(' ');
198         parts.forEach(part => {
199             switch (part) {
200                 case 'italic':
201                     decorationOptions.fontStyle = 'italic';
202                     break;
203                 case 'bold':
204                     decorationOptions.fontWeight = 'bold';
205                     break;
206                 case 'underline':
207                     decorationOptions.textDecoration = 'underline';
208                     break;
209                 default:
210                     break;
211             }
212         });
213     }
214     return vscode.window.createTextEditorDecorationType(decorationOptions);
215 }
216
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"]],
224
225     ["variable", ["variable"]],
226     ["variable.mut", ["variable", "meta.mutable"]],
227
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"]],
233
234     ["literal.byte", ["constant.character.byte"]],
235     ["literal.char", ["constant.character"]],
236     ["literal.numeric", ["constant.numeric"]],
237
238     ["comment", ["comment"]],
239     ["string", ["string.quoted"]],
240     ["attribute", ["meta.attribute"]],
241
242     ["keyword", ["keyword"]],
243     ["keyword.unsafe", ["keyword.other.unsafe"]],
244     ["keyword.control", ["keyword.control"]],
245 ]);