aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/user/README.md13
-rw-r--r--editors/code/package-lock.json6
-rw-r--r--editors/code/package.json64
-rw-r--r--editors/code/src/config.ts13
-rw-r--r--editors/code/src/extension.ts10
-rw-r--r--editors/code/src/highlighting.ts101
-rw-r--r--editors/code/src/scopes.ts134
-rw-r--r--editors/code/src/scopes_mapper.ts63
8 files changed, 364 insertions, 40 deletions
diff --git a/docs/user/README.md b/docs/user/README.md
index eb1d5ed14..909e574d0 100644
--- a/docs/user/README.md
+++ b/docs/user/README.md
@@ -82,7 +82,16 @@ host.
82 82
83### Settings 83### Settings
84 84
85* `rust-analyzer.highlightingOn`: enables experimental syntax highlighting 85* `rust-analyzer.highlightingOn`: enables experimental syntax highlighting.
86* `rust-analyzer.scopeMappings` -- a scheme backed JSON object to tweak Rust Analyzer scopes to TextMate scopes.
87 ```jsonc
88 {
89 //Will autocomplete keys to available RA scopes.
90 "keyword.unsafe": ["keyword", "keyword.control"],
91 //Values are string | TextMateScope | [string | TextMateScope]
92 "variable.mut": "variable"
93 }
94 ```
86* `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts 95* `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts
87 `Enter` key to make it easier to continue comments. Note that it may conflict with VIM emulation plugin. 96 `Enter` key to make it easier to continue comments. Note that it may conflict with VIM emulation plugin.
88* `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable 97* `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable
@@ -101,7 +110,7 @@ host.
101* `rust-analyzer.trace.cargo-watch`: enables cargo-watch logging 110* `rust-analyzer.trace.cargo-watch`: enables cargo-watch logging
102* `RUST_SRC_PATH`: environment variable that overwrites the sysroot 111* `RUST_SRC_PATH`: environment variable that overwrites the sysroot
103* `rust-analyzer.featureFlags` -- a JSON object to tweak fine-grained behavior: 112* `rust-analyzer.featureFlags` -- a JSON object to tweak fine-grained behavior:
104 ```js 113 ```jsonc
105 { 114 {
106 // Show diagnostics produced by rust-analyzer itself. 115 // Show diagnostics produced by rust-analyzer itself.
107 "lsp.diagnostics": true, 116 "lsp.diagnostics": true,
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index e6bcbfa60..9c55c257f 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",
diff --git a/editors/code/package.json b/editors/code/package.json
index ee997e58f..35f2f1e62 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -167,6 +167,68 @@
167 "default": false, 167 "default": false,
168 "description": "Highlight Rust code (overrides built-in syntax highlighting)" 168 "description": "Highlight Rust code (overrides built-in syntax highlighting)"
169 }, 169 },
170 "rust-analyzer.scopeMappings": {
171 "type": "object",
172 "definitions": {},
173 "properties": {
174 "comment": {
175 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
176 },
177 "string": {
178 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
179 },
180 "keyword": {
181 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
182 },
183 "keyword.control": {
184 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
185 },
186 "keyword.unsafe": {
187 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
188 },
189 "function": {
190 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
191 },
192 "parameter": {
193 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
194 },
195 "constant": {
196 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
197 },
198 "type": {
199 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
200 },
201 "builtin": {
202 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
203 },
204 "text": {
205 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
206 },
207 "attribute": {
208 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
209 },
210 "literal": {
211 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
212 },
213 "macro": {
214 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
215 },
216 "variable": {
217 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
218 },
219 "variable.mut": {
220 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
221 },
222 "field": {
223 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
224 },
225 "module": {
226 "$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
227 }
228 },
229 "additionalProperties": false,
230 "description": "Mapping Rust Analyzer scopes to TextMateRule scopes list."
231 },
170 "rust-analyzer.rainbowHighlightingOn": { 232 "rust-analyzer.rainbowHighlightingOn": {
171 "type": "boolean", 233 "type": "boolean",
172 "default": false, 234 "default": false,
@@ -488,4 +550,4 @@
488 } 550 }
489 ] 551 ]
490 } 552 }
491} 553} \ No newline at end of file
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 95c3f42e5..234a390ac 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -1,5 +1,6 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2 2import * as scopes from './scopes';
3import * as scopesMapper from './scopes_mapper';
3import { Server } from './server'; 4import { Server } from './server';
4 5
5const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; 6const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
@@ -45,8 +46,15 @@ export class Config {
45 46
46 public userConfigChanged() { 47 public userConfigChanged() {
47 const config = vscode.workspace.getConfiguration('rust-analyzer'); 48 const config = vscode.workspace.getConfiguration('rust-analyzer');
49
50 Server.highlighter.removeHighlights();
51
48 if (config.has('highlightingOn')) { 52 if (config.has('highlightingOn')) {
49 this.highlightingOn = config.get('highlightingOn') as boolean; 53 this.highlightingOn = config.get('highlightingOn') as boolean;
54 if (this.highlightingOn) {
55 scopes.load();
56 scopesMapper.load();
57 }
50 } 58 }
51 59
52 if (config.has('rainbowHighlightingOn')) { 60 if (config.has('rainbowHighlightingOn')) {
@@ -55,9 +63,6 @@ export class Config {
55 ) as boolean; 63 ) as boolean;
56 } 64 }
57 65
58 if (!this.highlightingOn && Server) {
59 Server.highlighter.removeHighlights();
60 }
61 66
62 if (config.has('enableEnhancedTyping')) { 67 if (config.has('enableEnhancedTyping')) {
63 this.enableEnhancedTyping = config.get( 68 this.enableEnhancedTyping = config.get(
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
index c06928d12..07a5c59e8 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..ee39ca64c 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';
5import * as scopesMapper from './scopes_mapper';
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
28
29function createDecorationFromTextmate(themeStyle: scopes.TextMateRuleSettings): vscode.TextEditorDecorationType {
30 const options: vscode.DecorationRenderOptions = {};
31 options.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen;
32 if (themeStyle.foreground) {
33 options.color = themeStyle.foreground;
34 }
35 if (themeStyle.background) {
36 options.backgroundColor = themeStyle.background;
37 }
38 if (themeStyle.fontStyle) {
39 const parts: string[] = themeStyle.fontStyle.split(' ');
40 parts.forEach((part) => {
41 switch (part) {
42 case 'italic':
43 options.fontStyle = 'italic';
44 break;
45 case 'bold':
46 options.fontWeight = 'bold';
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,51 @@ 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
36 const decor = vscode.window.createTextEditorDecorationType({ 69 const rule = scopesMapper.toRule(tag, scopes.find);
37 color, 70
38 textDecoration 71 if (rule) {
39 }); 72 const decor = createDecorationFromTextmate(rule);
40 return [tag, decor]; 73 return [tag, decor];
74 }
75 else {
76 const fallBackTag = 'ralsp.' + tag;
77 // console.log(' ');
78 // console.log('Missing theme for: <"' + tag + '"> for following mapped scopes:');
79 // console.log(scopesMapper.find(tag));
80 // console.log('Falling back to values defined in: ' + fallBackTag);
81 // console.log(' ');
82 const color = new vscode.ThemeColor(fallBackTag);
83 const decor = vscode.window.createTextEditorDecorationType({
84 color,
85 textDecoration
86 });
87 return [tag, decor];
88 }
41 }; 89 };
42 90
43 const decorations: Iterable< 91 const decorations: Iterable<
44 [string, vscode.TextEditorDecorationType] 92 [string, vscode.TextEditorDecorationType]
45 > = [ 93 > = [
46 decoration('comment'), 94 decoration('comment'),
47 decoration('string'), 95 decoration('string'),
48 decoration('keyword'), 96 decoration('keyword'),
49 decoration('keyword.control'), 97 decoration('keyword.control'),
50 decoration('keyword.unsafe'), 98 decoration('keyword.unsafe'),
51 decoration('function'), 99 decoration('function'),
52 decoration('parameter'), 100 decoration('parameter'),
53 decoration('constant'), 101 decoration('constant'),
54 decoration('type'), 102 decoration('type'),
55 decoration('builtin'), 103 decoration('builtin'),
56 decoration('text'), 104 decoration('text'),
57 decoration('attribute'), 105 decoration('attribute'),
58 decoration('literal'), 106 decoration('literal'),
59 decoration('macro'), 107 decoration('macro'),
60 decoration('variable'), 108 decoration('variable'),
61 decoration('variable.mut', 'underline'), 109 decoration('variable.mut', 'underline'),
62 decoration('field'), 110 decoration('field'),
63 decoration('module') 111 decoration('module')
64 ]; 112 ];
65 113
66 return new Map<string, vscode.TextEditorDecorationType>(decorations); 114 return new Map<string, vscode.TextEditorDecorationType>(decorations);
67 } 115 }
@@ -89,6 +137,8 @@ export class Highlighter {
89 // 137 //
90 // Note: decoration objects need to be kept around so we can dispose them 138 // Note: decoration objects need to be kept around so we can dispose them
91 // if the user disables syntax highlighting 139 // if the user disables syntax highlighting
140
141
92 if (this.decorations == null) { 142 if (this.decorations == null) {
93 this.decorations = Highlighter.initDecorations(); 143 this.decorations = Highlighter.initDecorations();
94 } 144 }
@@ -133,6 +183,7 @@ export class Highlighter {
133 tag 183 tag
134 ) as vscode.TextEditorDecorationType; 184 ) as vscode.TextEditorDecorationType;
135 const ranges = byTag.get(tag)!; 185 const ranges = byTag.get(tag)!;
186
136 editor.setDecorations(dec, ranges); 187 editor.setDecorations(dec, ranges);
137 } 188 }
138 189
diff --git a/editors/code/src/scopes.ts b/editors/code/src/scopes.ts
new file mode 100644
index 000000000..a6138fad0
--- /dev/null
+++ b/editors/code/src/scopes.ts
@@ -0,0 +1,134 @@
1import * as fs from 'fs';
2import * as path from 'path';
3import * as vscode from 'vscode';
4
5
6
7export interface TextMateRule {
8 scope: string | string[];
9 settings: TextMateRuleSettings;
10}
11
12export interface TextMateRuleSettings {
13 foreground: string | undefined;
14 background: string | undefined;
15 fontStyle: string | undefined;
16}
17
18// Current theme colors
19const rules = new Map<string, TextMateRuleSettings>();
20
21export function find(scope: string): TextMateRuleSettings | undefined {
22 return rules.get(scope);
23}
24
25// Load all textmate scopes in the currently active theme
26export function load() {
27 // Remove any previous theme
28 rules.clear();
29 // Find out current color theme
30 const themeName = vscode.workspace.getConfiguration('workbench').get('colorTheme');
31
32 if (typeof themeName !== 'string') {
33 // console.warn('workbench.colorTheme is', themeName)
34 return;
35 }
36 // Try to load colors from that theme
37 try {
38 loadThemeNamed(themeName);
39 } catch (e) {
40 // console.warn('failed to load theme', themeName, e)
41 }
42}
43
44function filterThemeExtensions(extension: vscode.Extension<any>): boolean {
45 return extension.extensionKind === vscode.ExtensionKind.UI &&
46 extension.packageJSON.contributes &&
47 extension.packageJSON.contributes.themes;
48}
49
50
51
52// Find current theme on disk
53function loadThemeNamed(themeName: string) {
54
55 const themePaths = vscode.extensions.all
56 .filter(filterThemeExtensions)
57 .reduce((list, extension) => {
58 return extension.packageJSON.contributes.themes
59 .filter((element: any) => (element.id || element.label) === themeName)
60 .map((element: any) => path.join(extension.extensionPath, element.path))
61 .concat(list)
62 }, Array<string>());
63
64
65 themePaths.forEach(loadThemeFile);
66
67 const tokenColorCustomizations: [any] = [vscode.workspace.getConfiguration('editor').get('tokenColorCustomizations')]
68
69 tokenColorCustomizations
70 .filter(custom => custom && custom.textMateRules)
71 .map(custom => custom.textMateRules)
72 .forEach(loadColors);
73
74}
75
76
77function loadThemeFile(themePath: string) {
78 const themeContent = [themePath]
79 .filter(isFile)
80 .map(readFileText)
81 .map(parseJSON)
82 .filter(theme => theme);
83
84 themeContent
85 .filter(theme => theme.tokenColors)
86 .map(theme => theme.tokenColors)
87 .forEach(loadColors);
88
89 themeContent
90 .filter(theme => theme.include)
91 .map(theme => path.join(path.dirname(themePath), theme.include))
92 .forEach(loadThemeFile);
93}
94
95function mergeRuleSettings(defaultSetting: TextMateRuleSettings | undefined, override: TextMateRuleSettings): TextMateRuleSettings {
96 if (defaultSetting === undefined) { return override; }
97 const mergedRule = defaultSetting;
98
99 mergedRule.background = override.background || defaultSetting.background;
100 mergedRule.foreground = override.foreground || defaultSetting.foreground;
101 mergedRule.fontStyle = override.fontStyle || defaultSetting.foreground;
102
103 return mergedRule
104}
105
106function updateRules(scope: string, updatedSettings: TextMateRuleSettings): void {
107 [rules.get(scope)]
108 .map(settings => mergeRuleSettings(settings, updatedSettings))
109 .forEach(settings => rules.set(scope, settings));
110}
111
112function loadColors(textMateRules: TextMateRule[]): void {
113 textMateRules.forEach(rule => {
114 if (typeof rule.scope === 'string') {
115 updateRules(rule.scope, rule.settings);
116 }
117 else if (rule.scope instanceof Array) {
118 rule.scope.forEach(scope => updateRules(scope, rule.settings));
119 }
120 })
121}
122
123function isFile(filePath: string): boolean {
124 return [filePath].map(fs.statSync).every(stat => stat.isFile());
125}
126
127function readFileText(filePath: string): string {
128 return fs.readFileSync(filePath, 'utf8');
129}
130
131// Might need to replace with JSONC if a theme contains comments.
132function parseJSON(content: string): any {
133 return JSON.parse(content);
134} \ No newline at end of file
diff --git a/editors/code/src/scopes_mapper.ts b/editors/code/src/scopes_mapper.ts
new file mode 100644
index 000000000..dfb8bf217
--- /dev/null
+++ b/editors/code/src/scopes_mapper.ts
@@ -0,0 +1,63 @@
1import * as vscode from 'vscode';
2import { TextMateRuleSettings } from './scopes';
3
4
5
6let mappings = new Map<string, string[]>();
7
8
9const defaultMapping = new Map<string, string[]>([
10 ['comment', ['comment', 'comment.block', 'comment.line', 'comment.block.documentation']],
11 ['string', ['string']],
12 ['keyword', ['keyword']],
13 ['keyword.control', ['keyword.control', 'keyword', 'keyword.other']],
14 ['keyword.unsafe', ['storage.modifier', 'keyword.other', 'keyword.control', 'keyword']],
15 ['function', ['entity.name.function']],
16 ['parameter', ['variable.parameter']],
17 ['constant', ['constant', 'variable']],
18 ['type', ['entity.name.type']],
19 ['builtin', ['variable.language', 'support.type', 'support.type']],
20 ['text', ['string', 'string.quoted', 'string.regexp']],
21 ['attribute', ['keyword']],
22 ['literal', ['string', 'string.quoted', 'string.regexp']],
23 ['macro', ['support.other']],
24 ['variable', ['variable']],
25 ['variable.mut', ['variable', 'storage.modifier']],
26 ['field', ['variable.object.property', 'meta.field.declaration', 'meta.definition.property', 'variable.other',]],
27 ['module', ['entity.name.section', 'entity.other']]
28]
29);
30
31// Temporary exported for debugging for now.
32export function find(scope: string): string[] {
33 return mappings.get(scope) || [];
34}
35
36export function toRule(scope: string, intoRule: (scope: string) => TextMateRuleSettings | undefined): TextMateRuleSettings | undefined {
37 return find(scope).map(intoRule).filter(rule => rule !== undefined)[0];
38}
39
40
41function isString(value: any): value is string {
42 return typeof value === 'string';
43}
44
45function isArrayOfString(value: any): value is string[] {
46 return Array.isArray(value) && value.every(item => isString(item));
47}
48
49
50export function load() {
51 const rawConfig: { [key: string]: any } = vscode.workspace
52 .getConfiguration('rust-analyzer')
53 .get('scopeMappings')
54 || {};
55
56 mappings = Object
57 .entries(rawConfig)
58 .filter(([_, value]) => isString(value) || isArrayOfString(value))
59 .reduce((list, [key, value]: [string, string | string[]]) => {
60 return list.set(key, isString(value) ? [value] : value);
61 }, defaultMapping);
62
63} \ No newline at end of file