From 3e8616cf6df5ba6e5dceba8b834dc63c20bac4d4 Mon Sep 17 00:00:00 2001 From: Seivan Heidari Date: Thu, 24 Oct 2019 17:25:23 +0200 Subject: Proof of concept theming and 'tokenColorCustomizations' support. --- editors/code/package-lock.json | 11 ++- editors/code/package.json | 3 +- editors/code/src/config.ts | 6 +- editors/code/src/extension.ts | 10 +-- editors/code/src/highlighting.ts | 94 +++++++++++++++++++------- editors/code/src/scopes.ts | 142 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 231 insertions(+), 35 deletions(-) create mode 100644 editors/code/src/scopes.ts diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index e6bcbfa60..a632c7395 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -598,9 +598,9 @@ } }, "https-proxy-agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", - "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.3.tgz", + "integrity": "sha512-Ytgnz23gm2DVftnzqRRz2dOXZbGd2uiajSw/95bPp6v53zPRspQjLm/AfBgqbJ2qfeRXWIOMVLpp86+/5yX39Q==", "dev": true, "requires": { "agent-base": "^4.3.0", @@ -720,6 +720,11 @@ "esprima": "^4.0.0" } }, + "jsonc-parser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.1.1.tgz", + "integrity": "sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g==" + }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", diff --git a/editors/code/package.json b/editors/code/package.json index 4b719aada..46b8cd47c 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -32,7 +32,8 @@ }, "dependencies": { "seedrandom": "^3.0.1", - "vscode-languageclient": "^5.3.0-next.4" + "vscode-languageclient": "^5.3.0-next.4", + "jsonc-parser": "^2.1.0" }, "devDependencies": { "@types/glob": "^7.1.1", diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 331936b5e..9f8c810b6 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; - +import * as scopes from './scopes'; import { Server } from './server'; const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; @@ -46,7 +46,11 @@ export class Config { public userConfigChanged() { const config = vscode.workspace.getConfiguration('rust-analyzer'); + + Server.highlighter.removeHighlights(); + scopes.load() if (config.has('highlightingOn')) { + this.highlightingOn = config.get('highlightingOn') as boolean; } diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index 39fe6efd8..1e1bc1a67 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -91,11 +91,11 @@ export function activate(context: vscode.ExtensionContext) { const allNotifications: Iterable< [string, lc.GenericNotificationHandler] > = [ - [ - 'rust-analyzer/publishDecorations', - notifications.publishDecorations.handle - ] - ]; + [ + 'rust-analyzer/publishDecorations', + notifications.publishDecorations.handle + ] + ]; const syntaxTreeContentProvider = new SyntaxTreeContentProvider(); // The events below are plain old javascript events, triggered and handled by vscode diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index d21d8a06a..4b961170b 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -1,6 +1,8 @@ import seedrandom = require('seedrandom'); import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; +import * as scopes from './scopes' + import { Server } from './server'; @@ -23,6 +25,37 @@ function fancify(seed: string, shade: 'light' | 'dark') { return `hsl(${h},${s}%,${l}%)`; } +function createDecorationFromTextmate(themeStyle: scopes.TextMateRuleSettings): vscode.TextEditorDecorationType { + const options: vscode.DecorationRenderOptions = {} + options.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen + if (themeStyle.foreground) { + options.color = themeStyle.foreground + } + if (themeStyle.background) { + options.backgroundColor = themeStyle.background + } + if (themeStyle.fontStyle) { + const parts: string[] = themeStyle.fontStyle.split(' ') + parts.forEach((part) => { + switch (part) { + case 'italic': + options.fontStyle = 'italic' + break + case 'bold': + options.fontWeight = 'bold' + + break + case 'underline': + options.textDecoration = 'underline' + break + default: + break + } + }) + } + return vscode.window.createTextEditorDecorationType(options) +} + export class Highlighter { private static initDecorations(): Map< string, @@ -32,36 +65,44 @@ export class Highlighter { tag: string, textDecoration?: string ): [string, vscode.TextEditorDecorationType] => { - const color = new vscode.ThemeColor('ralsp.' + tag); - const decor = vscode.window.createTextEditorDecorationType({ - color, - textDecoration - }); - return [tag, decor]; + const scope = scopes.find(tag) + + if (scope) { + const decor = createDecorationFromTextmate(scope); + return [tag, decor]; + } + else { + const color = new vscode.ThemeColor('ralsp.' + tag); + const decor = vscode.window.createTextEditorDecorationType({ + color, + textDecoration + }); + return [tag, decor]; + } }; const decorations: Iterable< [string, vscode.TextEditorDecorationType] > = [ - decoration('comment'), - decoration('string'), - decoration('keyword'), - decoration('keyword.control'), - decoration('keyword.unsafe'), - decoration('function'), - decoration('parameter'), - decoration('constant'), - decoration('type'), - decoration('builtin'), - decoration('text'), - decoration('attribute'), - decoration('literal'), - decoration('macro'), - decoration('variable'), - decoration('variable.mut', 'underline'), - decoration('field'), - decoration('module') - ]; + decoration('comment'), + decoration('string'), + decoration('keyword'), + decoration('keyword.control'), + decoration('keyword.unsafe'), + decoration('function'), + decoration('parameter'), + decoration('constant'), + decoration('type'), + decoration('builtin'), + decoration('text'), + decoration('attribute'), + decoration('literal'), + decoration('macro'), + decoration('variable'), + decoration('variable.mut', 'underline'), + decoration('field'), + decoration('module') + ]; return new Map(decorations); } @@ -89,6 +130,8 @@ export class Highlighter { // // Note: decoration objects need to be kept around so we can dispose them // if the user disables syntax highlighting + + if (this.decorations == null) { this.decorations = Highlighter.initDecorations(); } @@ -133,6 +176,7 @@ export class Highlighter { tag ) as vscode.TextEditorDecorationType; const ranges = byTag.get(tag)!; + editor.setDecorations(dec, ranges); } diff --git a/editors/code/src/scopes.ts b/editors/code/src/scopes.ts new file mode 100644 index 000000000..19d309828 --- /dev/null +++ b/editors/code/src/scopes.ts @@ -0,0 +1,142 @@ +import * as fs from 'fs' +import * as jsonc from 'jsonc-parser' +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 colors = new Map() + +export function find(scope: string): TextMateRuleSettings | undefined { + return colors.get(scope) +} + +// Load all textmate scopes in the currently active theme +export function load() { + // Remove any previous theme + colors.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) + } +} + +// Find current theme on disk +function loadThemeNamed(themeName: string) { + for (const extension of vscode.extensions.all) { + const extensionPath: string = extension.extensionPath + const extensionPackageJsonPath: string = path.join(extensionPath, 'package.json') + if (!checkFileExists(extensionPackageJsonPath)) { + continue + } + const packageJsonText: string = readFileText(extensionPackageJsonPath) + const packageJson: any = jsonc.parse(packageJsonText) + if (packageJson.contributes && packageJson.contributes.themes) { + for (const theme of packageJson.contributes.themes) { + const id = theme.id || theme.label + if (id === themeName) { + const themeRelativePath: string = theme.path + const themeFullPath: string = path.join(extensionPath, themeRelativePath) + loadThemeFile(themeFullPath) + } + } + } + + const customization: any = vscode.workspace.getConfiguration('editor').get('tokenColorCustomizations'); + if (customization && customization.textMateRules) { + loadColors(customization.textMateRules) + } + } +} + +function loadThemeFile(themePath: string) { + if (checkFileExists(themePath)) { + const themeContentText: string = readFileText(themePath) + const themeContent: any = jsonc.parse(themeContentText) + + if (themeContent && themeContent.tokenColors) { + loadColors(themeContent.tokenColors) + if (themeContent.include) { + // parse included theme file + const includedThemePath: string = path.join(path.dirname(themePath), themeContent.include) + loadThemeFile(includedThemePath) + } + } + } +} +function mergeRuleSettings(defaultRule: TextMateRuleSettings, override: TextMateRuleSettings): TextMateRuleSettings { + const mergedRule = defaultRule; + if (override.background) { + mergedRule.background = override.background + } + if (override.foreground) { + mergedRule.foreground = override.foreground + } + if (override.background) { + mergedRule.fontStyle = override.fontStyle + } + return mergedRule; +} + +function loadColors(textMateRules: TextMateRule[]): void { + for (const rule of textMateRules) { + + if (typeof rule.scope === 'string') { + const existingRule = colors.get(rule.scope); + if (existingRule) { + colors.set(rule.scope, mergeRuleSettings(existingRule, rule.settings)) + } + else { + colors.set(rule.scope, rule.settings) + } + } else if (rule.scope instanceof Array) { + for (const scope of rule.scope) { + const existingRule = colors.get(scope); + if (existingRule) { + colors.set(scope, mergeRuleSettings(existingRule, rule.settings)) + } + else { + colors.set(scope, rule.settings) + } + } + } + } +} + +function checkFileExists(filePath: string): boolean { + + const stats = fs.statSync(filePath); + if (stats && stats.isFile()) { + return true; + } else { + console.warn('no such file', filePath) + return false; + } + + +} + +function readFileText(filePath: string, encoding: string = 'utf8'): string { + return fs.readFileSync(filePath, encoding); +} \ No newline at end of file -- cgit v1.2.3