aboutsummaryrefslogtreecommitdiff
path: root/editors/code
diff options
context:
space:
mode:
authorSeivan Heidari <[email protected]>2019-10-24 16:25:23 +0100
committerSeivan Heidari <[email protected]>2019-10-24 16:25:23 +0100
commit3e8616cf6df5ba6e5dceba8b834dc63c20bac4d4 (patch)
treebce2c26d159b4c48eceb3ddce5a3c51cbbda904f /editors/code
parent95cf5c86fae3adf3bb38521905bf357450125709 (diff)
Proof of concept theming and 'tokenColorCustomizations' support.
Diffstat (limited to 'editors/code')
-rw-r--r--editors/code/package-lock.json11
-rw-r--r--editors/code/package.json3
-rw-r--r--editors/code/src/config.ts6
-rw-r--r--editors/code/src/extension.ts10
-rw-r--r--editors/code/src/highlighting.ts94
-rw-r--r--editors/code/src/scopes.ts142
6 files changed, 231 insertions, 35 deletions
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index e6bcbfa60..a632c7395 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -598,9 +598,9 @@
598 } 598 }
599 }, 599 },
600 "https-proxy-agent": { 600 "https-proxy-agent": {
601 "version": "2.2.2", 601 "version": "2.2.3",
602 "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", 602 "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.3.tgz",
603 "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", 603 "integrity": "sha512-Ytgnz23gm2DVftnzqRRz2dOXZbGd2uiajSw/95bPp6v53zPRspQjLm/AfBgqbJ2qfeRXWIOMVLpp86+/5yX39Q==",
604 "dev": true, 604 "dev": true,
605 "requires": { 605 "requires": {
606 "agent-base": "^4.3.0", 606 "agent-base": "^4.3.0",
@@ -720,6 +720,11 @@
720 "esprima": "^4.0.0" 720 "esprima": "^4.0.0"
721 } 721 }
722 }, 722 },
723 "jsonc-parser": {
724 "version": "2.1.1",
725 "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.1.1.tgz",
726 "integrity": "sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g=="
727 },
723 "lcid": { 728 "lcid": {
724 "version": "2.0.0", 729 "version": "2.0.0",
725 "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", 730 "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
diff --git a/editors/code/package.json b/editors/code/package.json
index 4b719aada..46b8cd47c 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -32,7 +32,8 @@
32 }, 32 },
33 "dependencies": { 33 "dependencies": {
34 "seedrandom": "^3.0.1", 34 "seedrandom": "^3.0.1",
35 "vscode-languageclient": "^5.3.0-next.4" 35 "vscode-languageclient": "^5.3.0-next.4",
36 "jsonc-parser": "^2.1.0"
36 }, 37 },
37 "devDependencies": { 38 "devDependencies": {
38 "@types/glob": "^7.1.1", 39 "@types/glob": "^7.1.1",
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 331936b5e..9f8c810b6 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -1,5 +1,5 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2 2import * as scopes from './scopes';
3import { Server } from './server'; 3import { Server } from './server';
4 4
5const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; 5const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
@@ -46,7 +46,11 @@ export class Config {
46 46
47 public userConfigChanged() { 47 public userConfigChanged() {
48 const config = vscode.workspace.getConfiguration('rust-analyzer'); 48 const config = vscode.workspace.getConfiguration('rust-analyzer');
49
50 Server.highlighter.removeHighlights();
51 scopes.load()
49 if (config.has('highlightingOn')) { 52 if (config.has('highlightingOn')) {
53
50 this.highlightingOn = config.get('highlightingOn') as boolean; 54 this.highlightingOn = config.get('highlightingOn') as boolean;
51 } 55 }
52 56
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
index 39fe6efd8..1e1bc1a67 100644
--- a/editors/code/src/extension.ts
+++ b/editors/code/src/extension.ts
@@ -91,11 +91,11 @@ export function activate(context: vscode.ExtensionContext) {
91 const allNotifications: Iterable< 91 const allNotifications: Iterable<
92 [string, lc.GenericNotificationHandler] 92 [string, lc.GenericNotificationHandler]
93 > = [ 93 > = [
94 [ 94 [
95 'rust-analyzer/publishDecorations', 95 'rust-analyzer/publishDecorations',
96 notifications.publishDecorations.handle 96 notifications.publishDecorations.handle
97 ] 97 ]
98 ]; 98 ];
99 const syntaxTreeContentProvider = new SyntaxTreeContentProvider(); 99 const syntaxTreeContentProvider = new SyntaxTreeContentProvider();
100 100
101 // The events below are plain old javascript events, triggered and handled by vscode 101 // The events below are plain old javascript events, triggered and handled by vscode
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts
index d21d8a06a..4b961170b 100644
--- a/editors/code/src/highlighting.ts
+++ b/editors/code/src/highlighting.ts
@@ -1,6 +1,8 @@
1import seedrandom = require('seedrandom'); 1import seedrandom = require('seedrandom');
2import * as vscode from 'vscode'; 2import * as vscode from 'vscode';
3import * as lc from 'vscode-languageclient'; 3import * as lc from 'vscode-languageclient';
4import * as scopes from './scopes'
5
4 6
5import { Server } from './server'; 7import { Server } from './server';
6 8
@@ -23,6 +25,37 @@ function fancify(seed: string, shade: 'light' | 'dark') {
23 return `hsl(${h},${s}%,${l}%)`; 25 return `hsl(${h},${s}%,${l}%)`;
24} 26}
25 27
28function createDecorationFromTextmate(themeStyle: scopes.TextMateRuleSettings): vscode.TextEditorDecorationType {
29 const options: vscode.DecorationRenderOptions = {}
30 options.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen
31 if (themeStyle.foreground) {
32 options.color = themeStyle.foreground
33 }
34 if (themeStyle.background) {
35 options.backgroundColor = themeStyle.background
36 }
37 if (themeStyle.fontStyle) {
38 const parts: string[] = themeStyle.fontStyle.split(' ')
39 parts.forEach((part) => {
40 switch (part) {
41 case 'italic':
42 options.fontStyle = 'italic'
43 break
44 case 'bold':
45 options.fontWeight = 'bold'
46
47 break
48 case 'underline':
49 options.textDecoration = 'underline'
50 break
51 default:
52 break
53 }
54 })
55 }
56 return vscode.window.createTextEditorDecorationType(options)
57}
58
26export class Highlighter { 59export class Highlighter {
27 private static initDecorations(): Map< 60 private static initDecorations(): Map<
28 string, 61 string,
@@ -32,36 +65,44 @@ export class Highlighter {
32 tag: string, 65 tag: string,
33 textDecoration?: string 66 textDecoration?: string
34 ): [string, vscode.TextEditorDecorationType] => { 67 ): [string, vscode.TextEditorDecorationType] => {
35 const color = new vscode.ThemeColor('ralsp.' + tag); 68 const scope = scopes.find(tag)
36 const decor = vscode.window.createTextEditorDecorationType({ 69
37 color, 70 if (scope) {
38 textDecoration 71 const decor = createDecorationFromTextmate(scope);
39 }); 72 return [tag, decor];
40 return [tag, decor]; 73 }
74 else {
75 const color = new vscode.ThemeColor('ralsp.' + tag);
76 const decor = vscode.window.createTextEditorDecorationType({
77 color,
78 textDecoration
79 });
80 return [tag, decor];
81 }
41 }; 82 };
42 83
43 const decorations: Iterable< 84 const decorations: Iterable<
44 [string, vscode.TextEditorDecorationType] 85 [string, vscode.TextEditorDecorationType]
45 > = [ 86 > = [
46 decoration('comment'), 87 decoration('comment'),
47 decoration('string'), 88 decoration('string'),
48 decoration('keyword'), 89 decoration('keyword'),
49 decoration('keyword.control'), 90 decoration('keyword.control'),
50 decoration('keyword.unsafe'), 91 decoration('keyword.unsafe'),
51 decoration('function'), 92 decoration('function'),
52 decoration('parameter'), 93 decoration('parameter'),
53 decoration('constant'), 94 decoration('constant'),
54 decoration('type'), 95 decoration('type'),
55 decoration('builtin'), 96 decoration('builtin'),
56 decoration('text'), 97 decoration('text'),
57 decoration('attribute'), 98 decoration('attribute'),
58 decoration('literal'), 99 decoration('literal'),
59 decoration('macro'), 100 decoration('macro'),
60 decoration('variable'), 101 decoration('variable'),
61 decoration('variable.mut', 'underline'), 102 decoration('variable.mut', 'underline'),
62 decoration('field'), 103 decoration('field'),
63 decoration('module') 104 decoration('module')
64 ]; 105 ];
65 106
66 return new Map<string, vscode.TextEditorDecorationType>(decorations); 107 return new Map<string, vscode.TextEditorDecorationType>(decorations);
67 } 108 }
@@ -89,6 +130,8 @@ export class Highlighter {
89 // 130 //
90 // Note: decoration objects need to be kept around so we can dispose them 131 // Note: decoration objects need to be kept around so we can dispose them
91 // if the user disables syntax highlighting 132 // if the user disables syntax highlighting
133
134
92 if (this.decorations == null) { 135 if (this.decorations == null) {
93 this.decorations = Highlighter.initDecorations(); 136 this.decorations = Highlighter.initDecorations();
94 } 137 }
@@ -133,6 +176,7 @@ export class Highlighter {
133 tag 176 tag
134 ) as vscode.TextEditorDecorationType; 177 ) as vscode.TextEditorDecorationType;
135 const ranges = byTag.get(tag)!; 178 const ranges = byTag.get(tag)!;
179
136 editor.setDecorations(dec, ranges); 180 editor.setDecorations(dec, ranges);
137 } 181 }
138 182
diff --git a/editors/code/src/scopes.ts b/editors/code/src/scopes.ts
new file mode 100644
index 000000000..19d309828
--- /dev/null
+++ b/editors/code/src/scopes.ts
@@ -0,0 +1,142 @@
1import * as fs from 'fs'
2import * as jsonc from 'jsonc-parser'
3import * as path from 'path'
4import * as vscode from 'vscode'
5
6
7
8export interface TextMateRule {
9 scope: string | string[]
10 settings: TextMateRuleSettings
11}
12
13export interface TextMateRuleSettings {
14 foreground: string | undefined
15 background: string | undefined
16 fontStyle: string | undefined
17}
18
19// Current theme colors
20const colors = new Map<string, TextMateRuleSettings>()
21
22export function find(scope: string): TextMateRuleSettings | undefined {
23 return colors.get(scope)
24}
25
26// Load all textmate scopes in the currently active theme
27export function load() {
28 // Remove any previous theme
29 colors.clear()
30 // Find out current color theme
31 const themeName = vscode.workspace.getConfiguration('workbench').get('colorTheme')
32
33 if (typeof themeName !== 'string') {
34 console.warn('workbench.colorTheme is', themeName)
35 return
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 }
43}
44
45// Find current theme on disk
46function loadThemeNamed(themeName: string) {
47 for (const extension of vscode.extensions.all) {
48 const extensionPath: string = extension.extensionPath
49 const extensionPackageJsonPath: string = path.join(extensionPath, 'package.json')
50 if (!checkFileExists(extensionPackageJsonPath)) {
51 continue
52 }
53 const packageJsonText: string = readFileText(extensionPackageJsonPath)
54 const packageJson: any = jsonc.parse(packageJsonText)
55 if (packageJson.contributes && packageJson.contributes.themes) {
56 for (const theme of packageJson.contributes.themes) {
57 const id = theme.id || theme.label
58 if (id === themeName) {
59 const themeRelativePath: string = theme.path
60 const themeFullPath: string = path.join(extensionPath, themeRelativePath)
61 loadThemeFile(themeFullPath)
62 }
63 }
64 }
65
66 const customization: any = vscode.workspace.getConfiguration('editor').get('tokenColorCustomizations');
67 if (customization && customization.textMateRules) {
68 loadColors(customization.textMateRules)
69 }
70 }
71}
72
73function loadThemeFile(themePath: string) {
74 if (checkFileExists(themePath)) {
75 const themeContentText: string = readFileText(themePath)
76 const themeContent: any = jsonc.parse(themeContentText)
77
78 if (themeContent && themeContent.tokenColors) {
79 loadColors(themeContent.tokenColors)
80 if (themeContent.include) {
81 // parse included theme file
82 const includedThemePath: string = path.join(path.dirname(themePath), themeContent.include)
83 loadThemeFile(includedThemePath)
84 }
85 }
86 }
87}
88function mergeRuleSettings(defaultRule: TextMateRuleSettings, override: TextMateRuleSettings): TextMateRuleSettings {
89 const mergedRule = defaultRule;
90 if (override.background) {
91 mergedRule.background = override.background
92 }
93 if (override.foreground) {
94 mergedRule.foreground = override.foreground
95 }
96 if (override.background) {
97 mergedRule.fontStyle = override.fontStyle
98 }
99 return mergedRule;
100}
101
102function loadColors(textMateRules: TextMateRule[]): void {
103 for (const rule of textMateRules) {
104
105 if (typeof rule.scope === 'string') {
106 const existingRule = colors.get(rule.scope);
107 if (existingRule) {
108 colors.set(rule.scope, mergeRuleSettings(existingRule, rule.settings))
109 }
110 else {
111 colors.set(rule.scope, rule.settings)
112 }
113 } else if (rule.scope instanceof Array) {
114 for (const scope of rule.scope) {
115 const existingRule = colors.get(scope);
116 if (existingRule) {
117 colors.set(scope, mergeRuleSettings(existingRule, rule.settings))
118 }
119 else {
120 colors.set(scope, rule.settings)
121 }
122 }
123 }
124 }
125}
126
127function checkFileExists(filePath: string): boolean {
128
129 const stats = fs.statSync(filePath);
130 if (stats && stats.isFile()) {
131 return true;
132 } else {
133 console.warn('no such file', filePath)
134 return false;
135 }
136
137
138}
139
140function readFileText(filePath: string, encoding: string = 'utf8'): string {
141 return fs.readFileSync(filePath, encoding);
142} \ No newline at end of file