[clangd] Added a TextMate theme parser to the vscode extension.

Summary:
Adds a TextMate parser module to the vscode extension. It parses a theme into an array of a pair of TextMate scopes and text colors.

Reviewers: hokein, ilya-biryukov

Subscribers: MaskRay, jkorous, arphaman, kadircet, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D65738

llvm-svn: 368136
This commit is contained in:
Johan Vikstrom 2019-08-07 08:48:52 +00:00
parent 843280bfe3
commit 57ced1e155
5 changed files with 167 additions and 1 deletions

View File

@ -36,14 +36,15 @@
"test": "node ./node_modules/vscode/bin/test"
},
"dependencies": {
"jsonc-parser": "^2.1.0",
"vscode-languageclient": "^5.3.0-next.6",
"vscode-languageserver": "^5.3.0-next.6"
},
"devDependencies": {
"@types/mocha": "^2.2.32",
"@types/node": "^6.0.40",
"mocha": "^5.2.0",
"clang-format": "1.2.4",
"mocha": "^5.2.0",
"typescript": "^2.0.3",
"vscode": "^1.1.0"
},

View File

@ -0,0 +1,102 @@
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);
});
});
}

View File

@ -0,0 +1,28 @@
{
// Some comment
"include": "simpleTheme.jsonc",
"name": "TestTheme",
"type": "dark",
"colors": {
"dropdown.background": "#fff"
},
"tokenColors": [
{
"settings": {
"foreground": "#fff"
}
},
{
"scope": "a",
"settings": {
"foreground": "#fff"
}
},
{
"scope": ["a", "b"],
"settings": {
"foreground": "#000"
}
}
]
}

View File

@ -0,0 +1,17 @@
{
// Some comment
"tokenColors": [
{
"scope": "a",
"settings": {
"foreground": "#ff0000"
}
},
{
"scope": "c",
"settings": {
"foreground": "#bcd"
}
}
]
}

View File

@ -0,0 +1,18 @@
import * as assert from 'assert';
import * as path from 'path';
import * as TM from '../src/semantic-highlighting';
suite('TextMate Tests', () => {
test('Parses arrays of textmate themes.', async () => {
const themePath =
path.join(__dirname, '../../test/assets/includeTheme.jsonc');
const scopeColorRules = await TM.parseThemeFile(themePath);
const getScopeRule = (scope: string) =>
scopeColorRules.find((v) => v.scope === scope);
assert.equal(scopeColorRules.length, 3);
assert.deepEqual(getScopeRule('a'), {scope : 'a', textColor : '#fff'});
assert.deepEqual(getScopeRule('b'), {scope : 'b', textColor : '#000'});
assert.deepEqual(getScopeRule('c'), {scope : 'c', textColor : '#bcd'});
});
});