Index: clang-tools-extra/trunk/clangd/clients/clangd-vscode/package.json =================================================================== --- clang-tools-extra/trunk/clangd/clients/clangd-vscode/package.json +++ clang-tools-extra/trunk/clangd/clients/clangd-vscode/package.json @@ -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" }, Index: clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/semantic-highlighting.ts =================================================================== --- clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/semantic-highlighting.ts @@ -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 { + const extension = + vscode.extensions.all.find((extension: vscode.Extension) => { + 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): Promise { + 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 { + return new Promise((resolve, reject) => { + fs.readFile(path, 'utf8', (err, data) => { + if (err) { + return reject(err); + } + return resolve(data); + }); + }); +} Index: clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/assets/includeTheme.jsonc =================================================================== --- clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/assets/includeTheme.jsonc +++ clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/assets/includeTheme.jsonc @@ -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" + } + } + ] +} Index: clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/assets/simpleTheme.jsonc =================================================================== --- clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/assets/simpleTheme.jsonc +++ clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/assets/simpleTheme.jsonc @@ -0,0 +1,17 @@ +{ + // Some comment + "tokenColors": [ + { + "scope": "a", + "settings": { + "foreground": "#ff0000" + } + }, + { + "scope": "c", + "settings": { + "foreground": "#bcd" + } + } + ] +} Index: clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts =================================================================== --- clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts @@ -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'}); + }); +});