blob: cc87bdef2281292728a55d410786228e5e480b34 [file] [log] [blame]
import * as fs from 'fs';
import * as jsonc from "jsonc-parser";
import * as path from 'path';
import * as vscode from 'vscode';
// A rule for how to color TextMate scopes.
interface TokenColorRule {
// A TextMate scope that specifies the context of the token, e.g.
// "entity.name.function.cpp".
scope: string;
// foreground is the color tokens of this scope should have.
foreground: string;
}
// Get all token color rules provided by the theme.
function loadTheme(themeName: string): Promise<TokenColorRule[]> {
const extension =
vscode.extensions.all.find((extension: vscode.Extension<any>) => {
const contribs = extension.packageJSON.contributes;
if (!contribs || !contribs.themes)
return false;
return contribs.themes.some((theme: any) => theme.id === themeName ||
theme.label === themeName);
});
if (!extension) {
return Promise.reject('Could not find a theme with name: ' + themeName);
}
const themeInfo = extension.packageJSON.contributes.themes.find(
(theme: any) => theme.id === themeName || theme.label === themeName);
return parseThemeFile(path.join(extension.extensionPath, themeInfo.path));
}
/**
* Parse the TextMate theme at fullPath. If there are multiple TextMate scopes
* of the same name in the include chain only the earliest entry of the scope is
* saved.
* @param fullPath The absolute path to the theme.
* @param seenScopes A set containing the name of the scopes that have already
* been set.
*/
export async function parseThemeFile(
fullPath: string, seenScopes?: Set<string>): Promise<TokenColorRule[]> {
if (!seenScopes)
seenScopes = new Set();
// FIXME: Add support for themes written as .tmTheme.
if (path.extname(fullPath) === '.tmTheme')
return [];
try {
const contents = await readFileText(fullPath);
const parsed = jsonc.parse(contents);
const rules: TokenColorRule[] = [];
// To make sure it does not crash if tokenColors is undefined.
if (!parsed.tokenColors)
parsed.tokenColors = [];
parsed.tokenColors.forEach((rule: any) => {
if (!rule.scope || !rule.settings || !rule.settings.foreground)
return;
const textColor = rule.settings.foreground;
// Scopes that were found further up the TextMate chain should not be
// overwritten.
const addColor = (scope: string) => {
if (seenScopes.has(scope))
return;
rules.push({scope, foreground : textColor});
seenScopes.add(scope);
};
if (rule.scope instanceof Array) {
return rule.scope.forEach((s: string) => addColor(s));
}
addColor(rule.scope);
});
if (parsed.include)
// Get all includes and merge into a flat list of parsed json.
return [
...(await parseThemeFile(
path.join(path.dirname(fullPath), parsed.include), seenScopes)),
...rules
];
return rules;
} catch (err) {
// If there is an error opening a file, the TextMate files that were
// correctly found and parsed further up the chain should be returned.
// Otherwise there will be no highlightings at all.
console.warn('Could not open file: ' + fullPath + ', error: ', err);
}
return [];
}
function readFileText(path: string): Promise<string> {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) {
return reject(err);
}
return resolve(data);
});
});
}