]> git.lizzy.rs Git - rust.git/blob - editors/code/src/scopes.ts
cb250b853ad0ecf50e721fdd441a4a73ea7a64e0
[rust.git] / editors / code / src / scopes.ts
1 import * as fs from 'fs';
2 import * as jsonc from 'jsonc-parser';
3 import * as path from 'path';
4 import * as vscode from 'vscode';
5
6 export interface TextMateRule {
7     scope: string | string[];
8     settings: TextMateRuleSettings;
9 }
10
11 export interface TextMateRuleSettings {
12     foreground: string | undefined;
13     background: string | undefined;
14     fontStyle: string | undefined;
15 }
16
17 // Current theme colors
18 const rules = new Map<string, TextMateRuleSettings>();
19
20 export function find(scope: string): TextMateRuleSettings | undefined {
21     return rules.get(scope);
22 }
23
24 // Load all textmate scopes in the currently active theme
25 export function load() {
26     // Remove any previous theme
27     rules.clear();
28     // Find out current color theme
29     const themeName = vscode.workspace
30         .getConfiguration('workbench')
31         .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 function filterThemeExtensions(extension: vscode.Extension<any>): boolean {
46     return (
47         extension.extensionKind === vscode.ExtensionKind.UI &&
48         extension.packageJSON.contributes &&
49         extension.packageJSON.contributes.themes
50     );
51 }
52
53 // Find current theme on disk
54 function 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
83 function loadThemeFile(themePath: string) {
84     const themeContent = [themePath]
85         .filter(isFile)
86         .map(readFileText)
87         .map(parseJSON)
88         .filter(theme => theme);
89
90     themeContent
91         .filter(theme => theme.tokenColors)
92         .map(theme => theme.tokenColors)
93         .forEach(loadColors);
94
95     themeContent
96         .filter(theme => theme.include)
97         .map(theme => path.join(path.dirname(themePath), theme.include))
98         .forEach(loadThemeFile);
99 }
100
101 function mergeRuleSettings(
102     defaultSetting: TextMateRuleSettings | undefined,
103     override: TextMateRuleSettings,
104 ): TextMateRuleSettings {
105     if (defaultSetting === undefined) {
106         return override;
107     }
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
114     return mergedRule;
115 }
116
117 function updateRules(
118     scope: string,
119     updatedSettings: TextMateRuleSettings,
120 ): void {
121     [rules.get(scope)]
122         .map(settings => mergeRuleSettings(settings, updatedSettings))
123         .forEach(settings => rules.set(scope, settings));
124 }
125
126 function loadColors(textMateRules: TextMateRule[]): void {
127     textMateRules.forEach(rule => {
128         if (typeof rule.scope === 'string') {
129             updateRules(rule.scope, rule.settings);
130         } else if (rule.scope instanceof Array) {
131             rule.scope.forEach(scope => updateRules(scope, rule.settings));
132         }
133     });
134 }
135
136 function isFile(filePath: string): boolean {
137     return [filePath].map(fs.statSync).every(stat => stat.isFile());
138 }
139
140 function readFileText(filePath: string): string {
141     return fs.readFileSync(filePath, 'utf8');
142 }
143
144 function parseJSON(content: string): any {
145     return jsonc.parse(content);
146 }