aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editors/code/src/config.ts2
-rw-r--r--editors/code/src/ctx.ts2
-rw-r--r--editors/code/src/highlighting.ts215
-rw-r--r--editors/code/src/scopes.ts182
-rw-r--r--editors/code/tsconfig.json2
5 files changed, 182 insertions, 221 deletions
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index e323110a4..f63d1ddce 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -1,5 +1,4 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as scopes from './scopes';
3import * as scopesMapper from './scopes_mapper'; 2import * as scopesMapper from './scopes_mapper';
4 3
5const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; 4const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
@@ -60,7 +59,6 @@ export class Config {
60 if (config.has('highlightingOn')) { 59 if (config.has('highlightingOn')) {
61 this.highlightingOn = config.get('highlightingOn') as boolean; 60 this.highlightingOn = config.get('highlightingOn') as boolean;
62 if (this.highlightingOn) { 61 if (this.highlightingOn) {
63 scopes.load();
64 scopesMapper.load(); 62 scopesMapper.load();
65 } 63 }
66 } 64 }
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 39eddfcbd..30dd9811c 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -65,7 +65,7 @@ export class Ctx {
65 async sendRequestWithRetry<R>( 65 async sendRequestWithRetry<R>(
66 method: string, 66 method: string,
67 param: any, 67 param: any,
68 token: vscode.CancellationToken, 68 token?: vscode.CancellationToken,
69 ): Promise<R> { 69 ): Promise<R> {
70 await this.client.onReady(); 70 await this.client.onReady();
71 for (const delay of [2, 4, 6, 8, 10, null]) { 71 for (const delay of [2, 4, 6, 8, 10, null]) {
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts
index 247673b5c..5e9cbe0de 100644
--- a/editors/code/src/highlighting.ts
+++ b/editors/code/src/highlighting.ts
@@ -3,7 +3,7 @@ import * as lc from 'vscode-languageclient';
3import * as seedrandom_ from 'seedrandom'; 3import * as seedrandom_ from 'seedrandom';
4const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular2-library/issues/221#issuecomment-355945207 4const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular2-library/issues/221#issuecomment-355945207
5 5
6import * as scopes from './scopes'; 6import { loadThemeColors, TextMateRuleSettings } from './scopes';
7import * as scopesMapper from './scopes_mapper'; 7import * as scopesMapper from './scopes_mapper';
8 8
9import { Ctx } from './ctx'; 9import { Ctx } from './ctx';
@@ -47,7 +47,7 @@ export function activateHighlighting(ctx: Ctx) {
47 const params: lc.TextDocumentIdentifier = { 47 const params: lc.TextDocumentIdentifier = {
48 uri: editor.document.uri.toString(), 48 uri: editor.document.uri.toString(),
49 }; 49 };
50 const decorations = await ctx.client.sendRequest<Decoration[]>( 50 const decorations = await ctx.sendRequestWithRetry<Decoration[]>(
51 'rust-analyzer/decorationsRequest', 51 'rust-analyzer/decorationsRequest',
52 params, 52 params,
53 ); 53 );
@@ -62,7 +62,7 @@ interface PublishDecorationsParams {
62 decorations: Decoration[]; 62 decorations: Decoration[];
63} 63}
64 64
65export interface Decoration { 65interface Decoration {
66 range: lc.Range; 66 range: lc.Range;
67 tag: string; 67 tag: string;
68 bindingHash?: string; 68 bindingHash?: string;
@@ -81,116 +81,17 @@ function fancify(seed: string, shade: 'light' | 'dark') {
81 return `hsl(${h},${s}%,${l}%)`; 81 return `hsl(${h},${s}%,${l}%)`;
82} 82}
83 83
84function createDecorationFromTextmate(
85 themeStyle: scopes.TextMateRuleSettings,
86): vscode.TextEditorDecorationType {
87 const decorationOptions: vscode.DecorationRenderOptions = {};
88 decorationOptions.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen;
89
90 if (themeStyle.foreground) {
91 decorationOptions.color = themeStyle.foreground;
92 }
93
94 if (themeStyle.background) {
95 decorationOptions.backgroundColor = themeStyle.background;
96 }
97
98 if (themeStyle.fontStyle) {
99 const parts: string[] = themeStyle.fontStyle.split(' ');
100 parts.forEach(part => {
101 switch (part) {
102 case 'italic':
103 decorationOptions.fontStyle = 'italic';
104 break;
105 case 'bold':
106 decorationOptions.fontWeight = 'bold';
107 break;
108 case 'underline':
109 decorationOptions.textDecoration = 'underline';
110 break;
111 default:
112 break;
113 }
114 });
115 }
116 return vscode.window.createTextEditorDecorationType(decorationOptions);
117}
118
119class Highlighter { 84class Highlighter {
120 private ctx: Ctx; 85 private ctx: Ctx;
121
122 constructor(ctx: Ctx) {
123 this.ctx = ctx;
124 }
125
126 private static initDecorations(): Map<
127 string,
128 vscode.TextEditorDecorationType
129 > {
130 const decoration = (
131 tag: string,
132 textDecoration?: string,
133 ): [string, vscode.TextEditorDecorationType] => {
134 const rule = scopesMapper.toRule(tag, scopes.find);
135
136 if (rule) {
137 const decor = createDecorationFromTextmate(rule);
138 return [tag, decor];
139 } else {
140 const fallBackTag = 'ralsp.' + tag;
141 // console.log(' ');
142 // console.log('Missing theme for: <"' + tag + '"> for following mapped scopes:');
143 // console.log(scopesMapper.find(tag));
144 // console.log('Falling back to values defined in: ' + fallBackTag);
145 // console.log(' ');
146 const color = new vscode.ThemeColor(fallBackTag);
147 const decor = vscode.window.createTextEditorDecorationType({
148 color,
149 textDecoration,
150 });
151 return [tag, decor];
152 }
153 };
154
155 const decorations: Iterable<[
156 string,
157 vscode.TextEditorDecorationType,
158 ]> = [
159 decoration('comment'),
160 decoration('string'),
161 decoration('keyword'),
162 decoration('keyword.control'),
163 decoration('keyword.unsafe'),
164 decoration('function'),
165 decoration('parameter'),
166 decoration('constant'),
167 decoration('type.builtin'),
168 decoration('type.generic'),
169 decoration('type.lifetime'),
170 decoration('type.param'),
171 decoration('type.self'),
172 decoration('type'),
173 decoration('text'),
174 decoration('attribute'),
175 decoration('literal'),
176 decoration('literal.numeric'),
177 decoration('literal.char'),
178 decoration('literal.byte'),
179 decoration('macro'),
180 decoration('variable'),
181 decoration('variable.mut', 'underline'),
182 decoration('field'),
183 decoration('module'),
184 ];
185
186 return new Map<string, vscode.TextEditorDecorationType>(decorations);
187 }
188
189 private decorations: Map< 86 private decorations: Map<
190 string, 87 string,
191 vscode.TextEditorDecorationType 88 vscode.TextEditorDecorationType
192 > | null = null; 89 > | null = null;
193 90
91 constructor(ctx: Ctx) {
92 this.ctx = ctx;
93 }
94
194 public removeHighlights() { 95 public removeHighlights() {
195 if (this.decorations == null) { 96 if (this.decorations == null) {
196 return; 97 return;
@@ -210,7 +111,7 @@ class Highlighter {
210 // Note: decoration objects need to be kept around so we can dispose them 111 // Note: decoration objects need to be kept around so we can dispose them
211 // if the user disables syntax highlighting 112 // if the user disables syntax highlighting
212 if (this.decorations == null) { 113 if (this.decorations == null) {
213 this.decorations = Highlighter.initDecorations(); 114 this.decorations = initDecorations();
214 } 115 }
215 116
216 const byTag: Map<string, vscode.Range[]> = new Map(); 117 const byTag: Map<string, vscode.Range[]> = new Map();
@@ -266,3 +167,103 @@ class Highlighter {
266 } 167 }
267 } 168 }
268} 169}
170
171function initDecorations(): Map<
172 string,
173 vscode.TextEditorDecorationType
174> {
175 const themeColors = loadThemeColors();
176
177 const decoration = (
178 tag: string,
179 textDecoration?: string,
180 ): [string, vscode.TextEditorDecorationType] => {
181 const rule = scopesMapper.toRule(tag, it => themeColors.get(it));
182
183 if (rule) {
184 const decor = createDecorationFromTextmate(rule);
185 return [tag, decor];
186 } else {
187 const fallBackTag = 'ralsp.' + tag;
188 // console.log(' ');
189 // console.log('Missing theme for: <"' + tag + '"> for following mapped scopes:');
190 // console.log(scopesMapper.find(tag));
191 // console.log('Falling back to values defined in: ' + fallBackTag);
192 // console.log(' ');
193 const color = new vscode.ThemeColor(fallBackTag);
194 const decor = vscode.window.createTextEditorDecorationType({
195 color,
196 textDecoration,
197 });
198 return [tag, decor];
199 }
200 };
201
202 const decorations: Iterable<[
203 string,
204 vscode.TextEditorDecorationType,
205 ]> = [
206 decoration('comment'),
207 decoration('string'),
208 decoration('keyword'),
209 decoration('keyword.control'),
210 decoration('keyword.unsafe'),
211 decoration('function'),
212 decoration('parameter'),
213 decoration('constant'),
214 decoration('type.builtin'),
215 decoration('type.generic'),
216 decoration('type.lifetime'),
217 decoration('type.param'),
218 decoration('type.self'),
219 decoration('type'),
220 decoration('text'),
221 decoration('attribute'),
222 decoration('literal'),
223 decoration('literal.numeric'),
224 decoration('literal.char'),
225 decoration('literal.byte'),
226 decoration('macro'),
227 decoration('variable'),
228 decoration('variable.mut', 'underline'),
229 decoration('field'),
230 decoration('module'),
231 ];
232
233 return new Map<string, vscode.TextEditorDecorationType>(decorations);
234}
235
236function createDecorationFromTextmate(
237 themeStyle: TextMateRuleSettings,
238): vscode.TextEditorDecorationType {
239 const decorationOptions: vscode.DecorationRenderOptions = {};
240 decorationOptions.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen;
241
242 if (themeStyle.foreground) {
243 decorationOptions.color = themeStyle.foreground;
244 }
245
246 if (themeStyle.background) {
247 decorationOptions.backgroundColor = themeStyle.background;
248 }
249
250 if (themeStyle.fontStyle) {
251 const parts: string[] = themeStyle.fontStyle.split(' ');
252 parts.forEach(part => {
253 switch (part) {
254 case 'italic':
255 decorationOptions.fontStyle = 'italic';
256 break;
257 case 'bold':
258 decorationOptions.fontWeight = 'bold';
259 break;
260 case 'underline':
261 decorationOptions.textDecoration = 'underline';
262 break;
263 default:
264 break;
265 }
266 });
267 }
268 return vscode.window.createTextEditorDecorationType(decorationOptions);
269}
diff --git a/editors/code/src/scopes.ts b/editors/code/src/scopes.ts
index cb250b853..73fabbf54 100644
--- a/editors/code/src/scopes.ts
+++ b/editors/code/src/scopes.ts
@@ -3,28 +3,14 @@ import * as jsonc from 'jsonc-parser';
3import * as path from 'path'; 3import * as path from 'path';
4import * as vscode from 'vscode'; 4import * as vscode from 'vscode';
5 5
6export interface TextMateRule {
7 scope: string | string[];
8 settings: TextMateRuleSettings;
9}
10
11export interface TextMateRuleSettings { 6export interface TextMateRuleSettings {
12 foreground: string | undefined; 7 foreground?: string;
13 background: string | undefined; 8 background?: string;
14 fontStyle: string | undefined; 9 fontStyle?: string;
15}
16
17// Current theme colors
18const rules = new Map<string, TextMateRuleSettings>();
19
20export function find(scope: string): TextMateRuleSettings | undefined {
21 return rules.get(scope);
22} 10}
23 11
24// Load all textmate scopes in the currently active theme 12// Load all textmate scopes in the currently active theme
25export function load() { 13export function loadThemeColors(): Map<string, TextMateRuleSettings> {
26 // Remove any previous theme
27 rules.clear();
28 // Find out current color theme 14 // Find out current color theme
29 const themeName = vscode.workspace 15 const themeName = vscode.workspace
30 .getConfiguration('workbench') 16 .getConfiguration('workbench')
@@ -32,115 +18,91 @@ export function load() {
32 18
33 if (typeof themeName !== 'string') { 19 if (typeof themeName !== 'string') {
34 // console.warn('workbench.colorTheme is', themeName) 20 // console.warn('workbench.colorTheme is', themeName)
35 return; 21 return new Map();
36 }
37 // Try to load colors from that theme
38 try {
39 loadThemeNamed(themeName);
40 } catch (e) {
41 // console.warn('failed to load theme', themeName, e)
42 } 22 }
23 return loadThemeNamed(themeName);
43} 24}
44 25
45function filterThemeExtensions(extension: vscode.Extension<any>): boolean { 26function loadThemeNamed(themeName: string): Map<string, TextMateRuleSettings> {
46 return ( 27 function isTheme(extension: vscode.Extension<any>): boolean {
47 extension.extensionKind === vscode.ExtensionKind.UI && 28 return (
48 extension.packageJSON.contributes && 29 extension.extensionKind === vscode.ExtensionKind.UI &&
49 extension.packageJSON.contributes.themes 30 extension.packageJSON.contributes &&
50 ); 31 extension.packageJSON.contributes.themes
51} 32 );
52 33 }
53// Find current theme on disk
54function loadThemeNamed(themeName: string) {
55 const themePaths = vscode.extensions.all
56 .filter(filterThemeExtensions)
57 .reduce((list, extension) => {
58 return extension.packageJSON.contributes.themes
59 .filter(
60 (element: any) =>
61 (element.id || element.label) === themeName,
62 )
63 .map((element: any) =>
64 path.join(extension.extensionPath, element.path),
65 )
66 .concat(list);
67 }, Array<string>());
68
69 themePaths.forEach(loadThemeFile);
70
71 const tokenColorCustomizations: [any] = [
72 vscode.workspace
73 .getConfiguration('editor')
74 .get('tokenColorCustomizations'),
75 ];
76
77 tokenColorCustomizations
78 .filter(custom => custom && custom.textMateRules)
79 .map(custom => custom.textMateRules)
80 .forEach(loadColors);
81}
82 34
83function loadThemeFile(themePath: string) { 35 let themePaths = vscode.extensions.all
84 const themeContent = [themePath] 36 .filter(isTheme)
85 .filter(isFile) 37 .flatMap(ext => {
86 .map(readFileText) 38 return ext.packageJSON.contributes.themes
87 .map(parseJSON) 39 .filter((it: any) => (it.id || it.label) === themeName)
88 .filter(theme => theme); 40 .map((it: any) => path.join(ext.extensionPath, it.path));
41 })
42
43 const res = new Map();
44 for (const themePath of themePaths) {
45 mergeInto(res, loadThemeFile(themePath))
46 }
89 47
90 themeContent 48 const customizations: any = vscode.workspace.getConfiguration('editor').get('tokenColorCustomizations');
91 .filter(theme => theme.tokenColors) 49 mergeInto(res, loadColors(customizations?.textMateRules ?? []))
92 .map(theme => theme.tokenColors)
93 .forEach(loadColors);
94 50
95 themeContent 51 return res;
96 .filter(theme => theme.include)
97 .map(theme => path.join(path.dirname(themePath), theme.include))
98 .forEach(loadThemeFile);
99} 52}
100 53
101function mergeRuleSettings( 54function loadThemeFile(themePath: string): Map<string, TextMateRuleSettings> {
102 defaultSetting: TextMateRuleSettings | undefined, 55 let text;
103 override: TextMateRuleSettings, 56 try {
104): TextMateRuleSettings { 57 text = fs.readFileSync(themePath, 'utf8')
105 if (defaultSetting === undefined) { 58 } catch {
106 return override; 59 return new Map();
60 }
61 const obj = jsonc.parse(text);
62 const tokenColors = obj?.tokenColors ?? [];
63 const res = loadColors(tokenColors);
64
65 for (const include in obj?.include ?? []) {
66 const includePath = path.join(path.dirname(themePath), include);
67 const tmp = loadThemeFile(includePath);
68 mergeInto(res, tmp);
107 } 69 }
108 const mergedRule = defaultSetting;
109
110 mergedRule.background = override.background || defaultSetting.background;
111 mergedRule.foreground = override.foreground || defaultSetting.foreground;
112 mergedRule.fontStyle = override.fontStyle || defaultSetting.foreground;
113 70
114 return mergedRule; 71 return res;
115} 72}
116 73
117function updateRules( 74interface TextMateRule {
118 scope: string, 75 scope: string | string[];
119 updatedSettings: TextMateRuleSettings, 76 settings: TextMateRuleSettings;
120): void {
121 [rules.get(scope)]
122 .map(settings => mergeRuleSettings(settings, updatedSettings))
123 .forEach(settings => rules.set(scope, settings));
124} 77}
125 78
126function loadColors(textMateRules: TextMateRule[]): void { 79function loadColors(textMateRules: TextMateRule[]): Map<string, TextMateRuleSettings> {
127 textMateRules.forEach(rule => { 80 const res = new Map();
128 if (typeof rule.scope === 'string') { 81 for (const rule of textMateRules) {
129 updateRules(rule.scope, rule.settings); 82 const scopes = typeof rule.scope === 'string'
130 } else if (rule.scope instanceof Array) { 83 ? [rule.scope]
131 rule.scope.forEach(scope => updateRules(scope, rule.settings)); 84 : rule.scope;
85 for (const scope of scopes) {
86 res.set(scope, rule.settings)
132 } 87 }
133 }); 88 }
134} 89 return res
135
136function isFile(filePath: string): boolean {
137 return [filePath].map(fs.statSync).every(stat => stat.isFile());
138} 90}
139 91
140function readFileText(filePath: string): string { 92function mergeRuleSettings(
141 return fs.readFileSync(filePath, 'utf8'); 93 defaultSetting: TextMateRuleSettings | undefined,
94 override: TextMateRuleSettings,
95): TextMateRuleSettings {
96 return {
97 foreground: defaultSetting?.foreground ?? override.foreground,
98 background: defaultSetting?.background ?? override.background,
99 fontStyle: defaultSetting?.fontStyle ?? override.fontStyle,
100 }
142} 101}
143 102
144function parseJSON(content: string): any { 103function mergeInto(dst: Map<string, TextMateRuleSettings>, addition: Map<string, TextMateRuleSettings>) {
145 return jsonc.parse(content); 104 addition.forEach((value, key) => {
105 const merged = mergeRuleSettings(dst.get(key), value)
106 dst.set(key, merged)
107 })
146} 108}
diff --git a/editors/code/tsconfig.json b/editors/code/tsconfig.json
index fe3b40f34..e60eb8e5e 100644
--- a/editors/code/tsconfig.json
+++ b/editors/code/tsconfig.json
@@ -4,7 +4,7 @@
4 "target": "es2018", 4 "target": "es2018",
5 "outDir": "out", 5 "outDir": "out",
6 "lib": [ 6 "lib": [
7 "es2018" 7 "es2019"
8 ], 8 ],
9 "sourceMap": true, 9 "sourceMap": true,
10 "rootDir": "src", 10 "rootDir": "src",