import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; export interface TextMateRule { scope: string | string[]; settings: TextMateRuleSettings; } export interface TextMateRuleSettings { foreground: string | undefined; background: string | undefined; fontStyle: string | undefined; } // Current theme colors const rules = new Map(); export function find(scope: string): TextMateRuleSettings | undefined { return rules.get(scope); } // Load all textmate scopes in the currently active theme export function load() { // Remove any previous theme rules.clear(); // Find out current color theme const themeName = vscode.workspace.getConfiguration('workbench').get('colorTheme'); if (typeof themeName !== 'string') { // console.warn('workbench.colorTheme is', themeName) return; } // Try to load colors from that theme try { loadThemeNamed(themeName); } catch (e) { // console.warn('failed to load theme', themeName, e) } } function filterThemeExtensions(extension: vscode.Extension): boolean { return extension.extensionKind === vscode.ExtensionKind.UI && extension.packageJSON.contributes && extension.packageJSON.contributes.themes; } // Find current theme on disk function loadThemeNamed(themeName: string) { const themePaths = vscode.extensions.all .filter(filterThemeExtensions) .reduce((list, extension) => { return extension.packageJSON.contributes.themes .filter((element: any) => (element.id || element.label) === themeName) .map((element: any) => path.join(extension.extensionPath, element.path)) .concat(list) }, Array()); themePaths.forEach(loadThemeFile); const tokenColorCustomizations: [any] = [vscode.workspace.getConfiguration('editor').get('tokenColorCustomizations')] tokenColorCustomizations .filter(custom => custom && custom.textMateRules) .map(custom => custom.textMateRules) .forEach(loadColors); } function loadThemeFile(themePath: string) { const themeContent = [themePath] .filter(isFile) .map(readFileText) .map(parseJSON) .filter(theme => theme); themeContent .filter(theme => theme.tokenColors) .map(theme => theme.tokenColors) .forEach(loadColors); themeContent .filter(theme => theme.include) .map(theme => path.join(path.dirname(themePath), theme.include)) .forEach(loadThemeFile); } function mergeRuleSettings(defaultSetting: TextMateRuleSettings | undefined, override: TextMateRuleSettings): TextMateRuleSettings { if (defaultSetting === undefined) { return override; } const mergedRule = defaultSetting; mergedRule.background = override.background || defaultSetting.background; mergedRule.foreground = override.foreground || defaultSetting.foreground; mergedRule.fontStyle = override.fontStyle || defaultSetting.foreground; return mergedRule } function updateRules(scope: string, updatedSettings: TextMateRuleSettings): void { [rules.get(scope)] .map(settings => mergeRuleSettings(settings, updatedSettings)) .forEach(settings => rules.set(scope, settings)); } function loadColors(textMateRules: TextMateRule[]): void { textMateRules.forEach(rule => { if (typeof rule.scope === 'string') { updateRules(rule.scope, rule.settings); } else if (rule.scope instanceof Array) { rule.scope.forEach(scope => updateRules(scope, rule.settings)); } }) } function isFile(filePath: string): boolean { return [filePath].map(fs.statSync).every(stat => stat.isFile()); } function readFileText(filePath: string): string { return fs.readFileSync(filePath, 'utf8'); } // Might need to replace with JSONC if a theme contains comments. function parseJSON(content: string): any { return JSON.parse(content); }