aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src')
-rw-r--r--editors/code/src/config.ts8
-rw-r--r--editors/code/src/extension.ts10
-rw-r--r--editors/code/src/highlighting.ts99
-rw-r--r--editors/code/src/scopes.ts134
-rw-r--r--editors/code/src/scopes_mapper.ts51
5 files changed, 271 insertions, 31 deletions
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 95c3f42e5..12823f319 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,7 +46,12 @@ 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 scopes.load()
52 scopesMapper.load()
48 if (config.has('highlightingOn')) { 53 if (config.has('highlightingOn')) {
54
49 this.highlightingOn = config.get('highlightingOn') as boolean; 55 this.highlightingOn = config.get('highlightingOn') as boolean;
50 } 56 }
51 57
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..dad99254e 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
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,49 @@ 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 console.log(' ');
77 console.log('Missing theme for: <"' + tag + '"> for following mapped scopes:')
78 console.log(scopesMapper.find(tag))
79 console.log(' ');
80 const color = new vscode.ThemeColor('ralsp.' + tag);
81 const decor = vscode.window.createTextEditorDecorationType({
82 color,
83 textDecoration
84 });
85 return [tag, decor];
86 }
41 }; 87 };
42 88
43 const decorations: Iterable< 89 const decorations: Iterable<
44 [string, vscode.TextEditorDecorationType] 90 [string, vscode.TextEditorDecorationType]
45 > = [ 91 > = [
46 decoration('comment'), 92 decoration('comment'),
47 decoration('string'), 93 decoration('string'),
48 decoration('keyword'), 94 decoration('keyword'),
49 decoration('keyword.control'), 95 decoration('keyword.control'),
50 decoration('keyword.unsafe'), 96 decoration('keyword.unsafe'),
51 decoration('function'), 97 decoration('function'),
52 decoration('parameter'), 98 decoration('parameter'),
53 decoration('constant'), 99 decoration('constant'),
54 decoration('type'), 100 decoration('type'),
55 decoration('builtin'), 101 decoration('builtin'),
56 decoration('text'), 102 decoration('text'),
57 decoration('attribute'), 103 decoration('attribute'),
58 decoration('literal'), 104 decoration('literal'),
59 decoration('macro'), 105 decoration('macro'),
60 decoration('variable'), 106 decoration('variable'),
61 decoration('variable.mut', 'underline'), 107 decoration('variable.mut', 'underline'),
62 decoration('field'), 108 decoration('field'),
63 decoration('module') 109 decoration('module')
64 ]; 110 ];
65 111
66 return new Map<string, vscode.TextEditorDecorationType>(decorations); 112 return new Map<string, vscode.TextEditorDecorationType>(decorations);
67 } 113 }
@@ -89,6 +135,8 @@ export class Highlighter {
89 // 135 //
90 // Note: decoration objects need to be kept around so we can dispose them 136 // Note: decoration objects need to be kept around so we can dispose them
91 // if the user disables syntax highlighting 137 // if the user disables syntax highlighting
138
139
92 if (this.decorations == null) { 140 if (this.decorations == null) {
93 this.decorations = Highlighter.initDecorations(); 141 this.decorations = Highlighter.initDecorations();
94 } 142 }
@@ -133,6 +181,7 @@ export class Highlighter {
133 tag 181 tag
134 ) as vscode.TextEditorDecorationType; 182 ) as vscode.TextEditorDecorationType;
135 const ranges = byTag.get(tag)!; 183 const ranges = byTag.get(tag)!;
184
136 editor.setDecorations(dec, ranges); 185 editor.setDecorations(dec, ranges);
137 } 186 }
138 187
diff --git a/editors/code/src/scopes.ts b/editors/code/src/scopes.ts
new file mode 100644
index 000000000..5d4395930
--- /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..19a4213d4
--- /dev/null
+++ b/editors/code/src/scopes_mapper.ts
@@ -0,0 +1,51 @@
1import * as vscode from 'vscode'
2import { TextMateRuleSettings } from './scopes'
3
4
5
6
7let mappings = new Map<string, string[]>()
8
9
10const defaultMapping = new Map<string, string[]>([
11 ['comment', ['comment', 'comment.block', 'comment.line', 'comment.block.documentation']],
12 ['string', ['string']],
13 ['keyword', ['keyword']],
14 ['keyword.control', ['keyword.control', 'keyword', 'keyword.other']],
15 ['keyword.unsafe', ['storage.modifier', 'keyword.other', 'keyword.control', 'keyword']],
16 ['function', ['entity.name.function']],
17 ['parameter', ['variable.parameter']],
18 ['constant', ['constant', 'variable']],
19 ['type', ['entity.name.type']],
20 ['builtin', ['variable.language', 'support.type', 'support.type']],
21 ['text', ['string', 'string.quoted', 'string.regexp']],
22 ['attribute', ['keyword']],
23 ['literal', ['string', 'string.quoted', 'string.regexp']],
24 ['macro', ['support.other']],
25 ['variable', ['variable']],
26 ['variable.mut', ['variable', 'storage.modifier']],
27 ['field', ['variable.object.property', 'meta.field.declaration', 'meta.definition.property', 'variable.other',]],
28 ['module', ['entity.name.section', 'entity.other']]
29]
30)
31
32// Temporary exported for debugging for now.
33export function find(scope: string): string[] {
34 return mappings.get(scope) || []
35}
36
37export function toRule(scope: string, intoRule: (scope: string) => TextMateRuleSettings | undefined): TextMateRuleSettings | undefined {
38 return find(scope).map(intoRule).filter(rule => rule !== undefined)[0]
39}
40
41
42export function load() {
43 const configuration = vscode.workspace
44 .getConfiguration('rust-analyzer')
45 .get('scopeMappings') as Map<string, string[]> | undefined
46 || new Map()
47
48 mappings = new Map([...Array.from(defaultMapping.entries()), ...Array.from(configuration.entries())])
49
50
51} \ No newline at end of file