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.json62
-rw-r--r--editors/code/src/config.ts14
-rw-r--r--editors/code/src/highlighting.ts64
-rw-r--r--editors/code/src/scopes.ts146
-rw-r--r--editors/code/src/scopes_mapper.ts79
7 files changed, 368 insertions, 16 deletions
diff --git a/docs/user/README.md b/docs/user/README.md
index 1861c69ab..eac9c50d2 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..5c5be34db 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,
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 95c3f42e5..4cedbea46 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,10 +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
62 if (config.has('enableEnhancedTyping')) { 66 if (config.has('enableEnhancedTyping')) {
63 this.enableEnhancedTyping = config.get( 67 this.enableEnhancedTyping = config.get(
64 'enableEnhancedTyping' 68 'enableEnhancedTyping'
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts
index d21d8a06a..0a38c9ef6 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,41 @@ 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(
29 themeStyle: scopes.TextMateRuleSettings
30): vscode.TextEditorDecorationType {
31 const decorationOptions: vscode.DecorationRenderOptions = {};
32 decorationOptions.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen;
33
34 if (themeStyle.foreground) {
35 decorationOptions.color = themeStyle.foreground;
36 }
37
38 if (themeStyle.background) {
39 decorationOptions.backgroundColor = themeStyle.background;
40 }
41
42 if (themeStyle.fontStyle) {
43 const parts: string[] = themeStyle.fontStyle.split(' ');
44 parts.forEach(part => {
45 switch (part) {
46 case 'italic':
47 decorationOptions.fontStyle = 'italic';
48 break;
49 case 'bold':
50 decorationOptions.fontWeight = 'bold';
51 break;
52 case 'underline':
53 decorationOptions.textDecoration = 'underline';
54 break;
55 default:
56 break;
57 }
58 });
59 }
60 return vscode.window.createTextEditorDecorationType(decorationOptions);
61}
62
26export class Highlighter { 63export class Highlighter {
27 private static initDecorations(): Map< 64 private static initDecorations(): Map<
28 string, 65 string,
@@ -32,12 +69,25 @@ export class Highlighter {
32 tag: string, 69 tag: string,
33 textDecoration?: string 70 textDecoration?: string
34 ): [string, vscode.TextEditorDecorationType] => { 71 ): [string, vscode.TextEditorDecorationType] => {
35 const color = new vscode.ThemeColor('ralsp.' + tag); 72 const rule = scopesMapper.toRule(tag, scopes.find);
36 const decor = vscode.window.createTextEditorDecorationType({ 73
37 color, 74 if (rule) {
38 textDecoration 75 const decor = createDecorationFromTextmate(rule);
39 }); 76 return [tag, decor];
40 return [tag, decor]; 77 } else {
78 const fallBackTag = 'ralsp.' + tag;
79 // console.log(' ');
80 // console.log('Missing theme for: <"' + tag + '"> for following mapped scopes:');
81 // console.log(scopesMapper.find(tag));
82 // console.log('Falling back to values defined in: ' + fallBackTag);
83 // console.log(' ');
84 const color = new vscode.ThemeColor(fallBackTag);
85 const decor = vscode.window.createTextEditorDecorationType({
86 color,
87 textDecoration
88 });
89 return [tag, decor];
90 }
41 }; 91 };
42 92
43 const decorations: Iterable< 93 const decorations: Iterable<
@@ -89,6 +139,7 @@ export class Highlighter {
89 // 139 //
90 // Note: decoration objects need to be kept around so we can dispose them 140 // Note: decoration objects need to be kept around so we can dispose them
91 // if the user disables syntax highlighting 141 // if the user disables syntax highlighting
142
92 if (this.decorations == null) { 143 if (this.decorations == null) {
93 this.decorations = Highlighter.initDecorations(); 144 this.decorations = Highlighter.initDecorations();
94 } 145 }
@@ -133,6 +184,7 @@ export class Highlighter {
133 tag 184 tag
134 ) as vscode.TextEditorDecorationType; 185 ) as vscode.TextEditorDecorationType;
135 const ranges = byTag.get(tag)!; 186 const ranges = byTag.get(tag)!;
187
136 editor.setDecorations(dec, ranges); 188 editor.setDecorations(dec, ranges);
137 } 189 }
138 190
diff --git a/editors/code/src/scopes.ts b/editors/code/src/scopes.ts
new file mode 100644
index 000000000..98099872c
--- /dev/null
+++ b/editors/code/src/scopes.ts
@@ -0,0 +1,146 @@
1import * as fs from 'fs';
2import * as path from 'path';
3import * as vscode from 'vscode';
4
5export interface TextMateRule {
6 scope: string | string[];
7 settings: TextMateRuleSettings;
8}
9
10export interface TextMateRuleSettings {
11 foreground: string | undefined;
12 background: string | undefined;
13 fontStyle: string | undefined;
14}
15
16// Current theme colors
17const rules = new Map<string, TextMateRuleSettings>();
18
19export function find(scope: string): TextMateRuleSettings | undefined {
20 return rules.get(scope);
21}
22
23// Load all textmate scopes in the currently active theme
24export function load() {
25 // Remove any previous theme
26 rules.clear();
27 // Find out current color theme
28 const themeName = vscode.workspace
29 .getConfiguration('workbench')
30 .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 (
46 extension.extensionKind === vscode.ExtensionKind.UI &&
47 extension.packageJSON.contributes &&
48 extension.packageJSON.contributes.themes
49 );
50}
51
52// Find current theme on disk
53function loadThemeNamed(themeName: string) {
54 const themePaths = vscode.extensions.all
55 .filter(filterThemeExtensions)
56 .reduce((list, extension) => {
57 return extension.packageJSON.contributes.themes
58 .filter(
59 (element: any) =>
60 (element.id || element.label) === themeName
61 )
62 .map((element: any) =>
63 path.join(extension.extensionPath, element.path)
64 )
65 .concat(list);
66 }, Array<string>());
67
68 themePaths.forEach(loadThemeFile);
69
70 const tokenColorCustomizations: [any] = [
71 vscode.workspace
72 .getConfiguration('editor')
73 .get('tokenColorCustomizations')
74 ];
75
76 tokenColorCustomizations
77 .filter(custom => custom && custom.textMateRules)
78 .map(custom => custom.textMateRules)
79 .forEach(loadColors);
80}
81
82function loadThemeFile(themePath: string) {
83 const themeContent = [themePath]
84 .filter(isFile)
85 .map(readFileText)
86 .map(parseJSON)
87 .filter(theme => theme);
88
89 themeContent
90 .filter(theme => theme.tokenColors)
91 .map(theme => theme.tokenColors)
92 .forEach(loadColors);
93
94 themeContent
95 .filter(theme => theme.include)
96 .map(theme => path.join(path.dirname(themePath), theme.include))
97 .forEach(loadThemeFile);
98}
99
100function mergeRuleSettings(
101 defaultSetting: TextMateRuleSettings | undefined,
102 override: TextMateRuleSettings
103): TextMateRuleSettings {
104 if (defaultSetting === undefined) {
105 return override;
106 }
107 const mergedRule = defaultSetting;
108
109 mergedRule.background = override.background || defaultSetting.background;
110 mergedRule.foreground = override.foreground || defaultSetting.foreground;
111 mergedRule.fontStyle = override.fontStyle || defaultSetting.foreground;
112
113 return mergedRule;
114}
115
116function updateRules(
117 scope: string,
118 updatedSettings: TextMateRuleSettings
119): void {
120 [rules.get(scope)]
121 .map(settings => mergeRuleSettings(settings, updatedSettings))
122 .forEach(settings => rules.set(scope, settings));
123}
124
125function loadColors(textMateRules: TextMateRule[]): void {
126 textMateRules.forEach(rule => {
127 if (typeof rule.scope === 'string') {
128 updateRules(rule.scope, rule.settings);
129 } else if (rule.scope instanceof Array) {
130 rule.scope.forEach(scope => updateRules(scope, rule.settings));
131 }
132 });
133}
134
135function isFile(filePath: string): boolean {
136 return [filePath].map(fs.statSync).every(stat => stat.isFile());
137}
138
139function readFileText(filePath: string): string {
140 return fs.readFileSync(filePath, 'utf8');
141}
142
143// Might need to replace with JSONC if a theme contains comments.
144function parseJSON(content: string): any {
145 return JSON.parse(content);
146}
diff --git a/editors/code/src/scopes_mapper.ts b/editors/code/src/scopes_mapper.ts
new file mode 100644
index 000000000..85c791ff5
--- /dev/null
+++ b/editors/code/src/scopes_mapper.ts
@@ -0,0 +1,79 @@
1import * as vscode from 'vscode';
2import { TextMateRuleSettings } from './scopes';
3
4let mappings = new Map<string, string[]>();
5
6const defaultMapping = new Map<string, string[]>([
7 [
8 'comment',
9 [
10 'comment',
11 'comment.block',
12 'comment.line',
13 'comment.block.documentation'
14 ]
15 ],
16 ['string', ['string']],
17 ['keyword', ['keyword']],
18 ['keyword.control', ['keyword.control', 'keyword', 'keyword.other']],
19 [
20 'keyword.unsafe',
21 ['storage.modifier', 'keyword.other', 'keyword.control', 'keyword']
22 ],
23 ['function', ['entity.name.function']],
24 ['parameter', ['variable.parameter']],
25 ['constant', ['constant', 'variable']],
26 ['type', ['entity.name.type']],
27 ['builtin', ['variable.language', 'support.type', 'support.type']],
28 ['text', ['string', 'string.quoted', 'string.regexp']],
29 ['attribute', ['keyword']],
30 ['literal', ['string', 'string.quoted', 'string.regexp']],
31 ['macro', ['support.other']],
32 ['variable', ['variable']],
33 ['variable.mut', ['variable', 'storage.modifier']],
34 [
35 'field',
36 [
37 'variable.object.property',
38 'meta.field.declaration',
39 'meta.definition.property',
40 'variable.other'
41 ]
42 ],
43 ['module', ['entity.name.section', 'entity.other']]
44]);
45
46// Temporary exported for debugging for now.
47export function find(scope: string): string[] {
48 return mappings.get(scope) || [];
49}
50
51export function toRule(
52 scope: string,
53 intoRule: (scope: string) => TextMateRuleSettings | undefined
54): TextMateRuleSettings | undefined {
55 return find(scope)
56 .map(intoRule)
57 .filter(rule => rule !== undefined)[0];
58}
59
60function isString(value: any): value is string {
61 return typeof value === 'string';
62}
63
64function isArrayOfString(value: any): value is string[] {
65 return Array.isArray(value) && value.every(item => isString(item));
66}
67
68export function load() {
69 const rawConfig: { [key: string]: any } =
70 vscode.workspace
71 .getConfiguration('rust-analyzer')
72 .get('scopeMappings') || {};
73
74 mappings = Object.entries(rawConfig)
75 .filter(([_, value]) => isString(value) || isArrayOfString(value))
76 .reduce((list, [key, value]: [string, string | string[]]) => {
77 return list.set(key, isString(value) ? [value] : value);
78 }, defaultMapping);
79}