diff options
Diffstat (limited to 'editors/code/src/highlighting.ts')
-rw-r--r-- | editors/code/src/highlighting.ts | 209 |
1 files changed, 151 insertions, 58 deletions
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index e1b0d13e7..014e96f75 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts | |||
@@ -1,10 +1,69 @@ | |||
1 | import seedrandom = require('seedrandom'); | ||
2 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
3 | import * as lc from 'vscode-languageclient'; | 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, sendRequestWithRetry } from './ctx'; | ||
9 | |||
10 | export function activateHighlighting(ctx: Ctx) { | ||
11 | const highlighter = new Highlighter(ctx); | ||
12 | ctx.onDidRestart(client => { | ||
13 | client.onNotification( | ||
14 | 'rust-analyzer/publishDecorations', | ||
15 | (params: PublishDecorationsParams) => { | ||
16 | if (!ctx.config.highlightingOn) return; | ||
17 | |||
18 | const targetEditor = vscode.window.visibleTextEditors.find( | ||
19 | editor => { | ||
20 | const unescapedUri = unescape( | ||
21 | editor.document.uri.toString(), | ||
22 | ); | ||
23 | // Unescaped URI looks like: | ||
24 | // file:///c:/Workspace/ra-test/src/main.rs | ||
25 | return unescapedUri === params.uri; | ||
26 | }, | ||
27 | ); | ||
28 | if (!targetEditor) return; | ||
29 | |||
30 | highlighter.setHighlights(targetEditor, params.decorations); | ||
31 | }, | ||
32 | ); | ||
33 | }); | ||
34 | |||
35 | vscode.workspace.onDidChangeConfiguration( | ||
36 | _ => highlighter.removeHighlights(), | ||
37 | ctx.subscriptions, | ||
38 | ); | ||
39 | |||
40 | vscode.window.onDidChangeActiveTextEditor( | ||
41 | async (editor: vscode.TextEditor | undefined) => { | ||
42 | if (!editor || editor.document.languageId !== 'rust') return; | ||
43 | if (!ctx.config.highlightingOn) return; | ||
44 | let client = ctx.client; | ||
45 | if (!client) return; | ||
46 | |||
47 | const params: lc.TextDocumentIdentifier = { | ||
48 | uri: editor.document.uri.toString(), | ||
49 | }; | ||
50 | const decorations = await sendRequestWithRetry<Decoration[]>( | ||
51 | client, | ||
52 | 'rust-analyzer/decorationsRequest', | ||
53 | params, | ||
54 | ); | ||
55 | highlighter.setHighlights(editor, decorations); | ||
56 | }, | ||
57 | ctx.subscriptions, | ||
58 | ); | ||
59 | } | ||
4 | 60 | ||
5 | import { Server } from './server'; | 61 | interface PublishDecorationsParams { |
62 | uri: string; | ||
63 | decorations: Decoration[]; | ||
64 | } | ||
6 | 65 | ||
7 | export interface Decoration { | 66 | interface Decoration { |
8 | range: lc.Range; | 67 | range: lc.Range; |
9 | tag: string; | 68 | tag: string; |
10 | bindingHash?: string; | 69 | bindingHash?: string; |
@@ -23,62 +82,17 @@ function fancify(seed: string, shade: 'light' | 'dark') { | |||
23 | return `hsl(${h},${s}%,${l}%)`; | 82 | return `hsl(${h},${s}%,${l}%)`; |
24 | } | 83 | } |
25 | 84 | ||
26 | export class Highlighter { | 85 | class Highlighter { |
27 | private static initDecorations(): Map< | 86 | private ctx: Ctx; |
28 | string, | ||
29 | vscode.TextEditorDecorationType | ||
30 | > { | ||
31 | const decoration = ( | ||
32 | tag: string, | ||
33 | textDecoration?: string, | ||
34 | ): [string, vscode.TextEditorDecorationType] => { | ||
35 | const color = new vscode.ThemeColor('ralsp.' + tag); | ||
36 | const decor = vscode.window.createTextEditorDecorationType({ | ||
37 | color, | ||
38 | textDecoration, | ||
39 | }); | ||
40 | return [tag, decor]; | ||
41 | }; | ||
42 | |||
43 | const decorations: Iterable<[ | ||
44 | string, | ||
45 | vscode.TextEditorDecorationType, | ||
46 | ]> = [ | ||
47 | decoration('comment'), | ||
48 | decoration('string'), | ||
49 | decoration('keyword'), | ||
50 | decoration('keyword.control'), | ||
51 | decoration('keyword.unsafe'), | ||
52 | decoration('function'), | ||
53 | decoration('parameter'), | ||
54 | decoration('constant'), | ||
55 | decoration('type.builtin'), | ||
56 | decoration('type.generic'), | ||
57 | decoration('type.lifetime'), | ||
58 | decoration('type.param'), | ||
59 | decoration('type.self'), | ||
60 | decoration('type'), | ||
61 | decoration('text'), | ||
62 | decoration('attribute'), | ||
63 | decoration('literal'), | ||
64 | decoration('literal.numeric'), | ||
65 | decoration('literal.char'), | ||
66 | decoration('literal.byte'), | ||
67 | decoration('macro'), | ||
68 | decoration('variable'), | ||
69 | decoration('variable.mut', 'underline'), | ||
70 | decoration('field'), | ||
71 | decoration('module'), | ||
72 | ]; | ||
73 | |||
74 | return new Map<string, vscode.TextEditorDecorationType>(decorations); | ||
75 | } | ||
76 | |||
77 | private decorations: Map< | 87 | private decorations: Map< |
78 | string, | 88 | string, |
79 | vscode.TextEditorDecorationType | 89 | vscode.TextEditorDecorationType |
80 | > | null = null; | 90 | > | null = null; |
81 | 91 | ||
92 | constructor(ctx: Ctx) { | ||
93 | this.ctx = ctx; | ||
94 | } | ||
95 | |||
82 | public removeHighlights() { | 96 | public removeHighlights() { |
83 | if (this.decorations == null) { | 97 | if (this.decorations == null) { |
84 | return; | 98 | return; |
@@ -93,12 +107,14 @@ export class Highlighter { | |||
93 | } | 107 | } |
94 | 108 | ||
95 | public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) { | 109 | public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) { |
110 | let client = this.ctx.client; | ||
111 | if (!client) return; | ||
96 | // Initialize decorations if necessary | 112 | // Initialize decorations if necessary |
97 | // | 113 | // |
98 | // Note: decoration objects need to be kept around so we can dispose them | 114 | // Note: decoration objects need to be kept around so we can dispose them |
99 | // if the user disables syntax highlighting | 115 | // if the user disables syntax highlighting |
100 | if (this.decorations == null) { | 116 | if (this.decorations == null) { |
101 | this.decorations = Highlighter.initDecorations(); | 117 | this.decorations = initDecorations(); |
102 | } | 118 | } |
103 | 119 | ||
104 | const byTag: Map<string, vscode.Range[]> = new Map(); | 120 | const byTag: Map<string, vscode.Range[]> = new Map(); |
@@ -106,7 +122,7 @@ export class Highlighter { | |||
106 | string, | 122 | string, |
107 | [vscode.Range[], boolean] | 123 | [vscode.Range[], boolean] |
108 | > = new Map(); | 124 | > = new Map(); |
109 | const rainbowTime = Server.config.rainbowHighlightingOn; | 125 | const rainbowTime = this.ctx.config.rainbowHighlightingOn; |
110 | 126 | ||
111 | for (const tag of this.decorations.keys()) { | 127 | for (const tag of this.decorations.keys()) { |
112 | byTag.set(tag, []); | 128 | byTag.set(tag, []); |
@@ -125,13 +141,13 @@ export class Highlighter { | |||
125 | colorfulIdents | 141 | colorfulIdents |
126 | .get(d.bindingHash)![0] | 142 | .get(d.bindingHash)![0] |
127 | .push( | 143 | .push( |
128 | Server.client.protocol2CodeConverter.asRange(d.range), | 144 | client.protocol2CodeConverter.asRange(d.range), |
129 | ); | 145 | ); |
130 | } else { | 146 | } else { |
131 | byTag | 147 | byTag |
132 | .get(d.tag)! | 148 | .get(d.tag)! |
133 | .push( | 149 | .push( |
134 | Server.client.protocol2CodeConverter.asRange(d.range), | 150 | client.protocol2CodeConverter.asRange(d.range), |
135 | ); | 151 | ); |
136 | } | 152 | } |
137 | } | 153 | } |
@@ -154,3 +170,80 @@ export class Highlighter { | |||
154 | } | 170 | } |
155 | } | 171 | } |
156 | } | 172 | } |
173 | |||
174 | function initDecorations(): Map<string, vscode.TextEditorDecorationType> { | ||
175 | const theme = ColorTheme.load(); | ||
176 | const res = new Map(); | ||
177 | TAG_TO_SCOPES.forEach((scopes, tag) => { | ||
178 | if (!scopes) throw `unmapped tag: ${tag}`; | ||
179 | let rule = theme.lookup(scopes); | ||
180 | const decor = createDecorationFromTextmate(rule); | ||
181 | res.set(tag, decor); | ||
182 | }); | ||
183 | return res; | ||
184 | } | ||
185 | |||
186 | function createDecorationFromTextmate( | ||
187 | themeStyle: TextMateRuleSettings, | ||
188 | ): vscode.TextEditorDecorationType { | ||
189 | const decorationOptions: vscode.DecorationRenderOptions = {}; | ||
190 | decorationOptions.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen; | ||
191 | |||
192 | if (themeStyle.foreground) { | ||
193 | decorationOptions.color = themeStyle.foreground; | ||
194 | } | ||
195 | |||
196 | if (themeStyle.background) { | ||
197 | decorationOptions.backgroundColor = themeStyle.background; | ||
198 | } | ||
199 | |||
200 | if (themeStyle.fontStyle) { | ||
201 | const parts: string[] = themeStyle.fontStyle.split(' '); | ||
202 | parts.forEach(part => { | ||
203 | switch (part) { | ||
204 | case 'italic': | ||
205 | decorationOptions.fontStyle = 'italic'; | ||
206 | break; | ||
207 | case 'bold': | ||
208 | decorationOptions.fontWeight = 'bold'; | ||
209 | break; | ||
210 | case 'underline': | ||
211 | decorationOptions.textDecoration = 'underline'; | ||
212 | break; | ||
213 | default: | ||
214 | break; | ||
215 | } | ||
216 | }); | ||
217 | } | ||
218 | return vscode.window.createTextEditorDecorationType(decorationOptions); | ||
219 | } | ||
220 | |||
221 | // sync with tags from `syntax_highlighting.rs`. | ||
222 | const TAG_TO_SCOPES = new Map<string, string[]>([ | ||
223 | ["field", ["entity.name.field"]], | ||
224 | ["function", ["entity.name.function"]], | ||
225 | ["module", ["entity.name.module"]], | ||
226 | ["constant", ["entity.name.constant"]], | ||
227 | ["macro", ["entity.name.macro"]], | ||
228 | |||
229 | ["variable", ["variable"]], | ||
230 | ["variable.mut", ["variable", "meta.mutable"]], | ||
231 | |||
232 | ["type", ["entity.name.type"]], | ||
233 | ["type.builtin", ["entity.name.type", "support.type.primitive"]], | ||
234 | ["type.self", ["entity.name.type.parameter.self"]], | ||
235 | ["type.param", ["entity.name.type.parameter"]], | ||
236 | ["type.lifetime", ["entity.name.type.lifetime"]], | ||
237 | |||
238 | ["literal.byte", ["constant.character.byte"]], | ||
239 | ["literal.char", ["constant.character"]], | ||
240 | ["literal.numeric", ["constant.numeric"]], | ||
241 | |||
242 | ["comment", ["comment"]], | ||
243 | ["string", ["string.quoted"]], | ||
244 | ["attribute", ["meta.attribute"]], | ||
245 | |||
246 | ["keyword", ["keyword"]], | ||
247 | ["keyword.unsafe", ["keyword.other.unsafe"]], | ||
248 | ["keyword.control", ["keyword.control"]], | ||
249 | ]); | ||