Proof of concept theming and 'tokenColorCustomizations' support.

This commit is contained in:
Seivan Heidari 2019-10-24 17:25:23 +02:00
parent 95cf5c86fa
commit 3e8616cf6d
6 changed files with 231 additions and 35 deletions

View file

@ -598,9 +598,9 @@
} }
}, },
"https-proxy-agent": { "https-proxy-agent": {
"version": "2.2.2", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.3.tgz",
"integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", "integrity": "sha512-Ytgnz23gm2DVftnzqRRz2dOXZbGd2uiajSw/95bPp6v53zPRspQjLm/AfBgqbJ2qfeRXWIOMVLpp86+/5yX39Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"agent-base": "^4.3.0", "agent-base": "^4.3.0",
@ -720,6 +720,11 @@
"esprima": "^4.0.0" "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": { "lcid": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",

View file

@ -32,7 +32,8 @@
}, },
"dependencies": { "dependencies": {
"seedrandom": "^3.0.1", "seedrandom": "^3.0.1",
"vscode-languageclient": "^5.3.0-next.4" "vscode-languageclient": "^5.3.0-next.4",
"jsonc-parser": "^2.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/glob": "^7.1.1", "@types/glob": "^7.1.1",

View file

@ -1,5 +1,5 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as scopes from './scopes';
import { Server } from './server'; import { Server } from './server';
const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
@ -46,7 +46,11 @@ export class Config {
public userConfigChanged() { public userConfigChanged() {
const config = vscode.workspace.getConfiguration('rust-analyzer'); const config = vscode.workspace.getConfiguration('rust-analyzer');
Server.highlighter.removeHighlights();
scopes.load()
if (config.has('highlightingOn')) { if (config.has('highlightingOn')) {
this.highlightingOn = config.get('highlightingOn') as boolean; this.highlightingOn = config.get('highlightingOn') as boolean;
} }

View file

@ -91,11 +91,11 @@ export function activate(context: vscode.ExtensionContext) {
const allNotifications: Iterable< const allNotifications: Iterable<
[string, lc.GenericNotificationHandler] [string, lc.GenericNotificationHandler]
> = [ > = [
[ [
'rust-analyzer/publishDecorations', 'rust-analyzer/publishDecorations',
notifications.publishDecorations.handle notifications.publishDecorations.handle
] ]
]; ];
const syntaxTreeContentProvider = new SyntaxTreeContentProvider(); const syntaxTreeContentProvider = new SyntaxTreeContentProvider();
// The events below are plain old javascript events, triggered and handled by vscode // The events below are plain old javascript events, triggered and handled by vscode

View file

@ -1,6 +1,8 @@
import seedrandom = require('seedrandom'); import seedrandom = require('seedrandom');
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient'; import * as lc from 'vscode-languageclient';
import * as scopes from './scopes'
import { Server } from './server'; import { Server } from './server';
@ -23,6 +25,37 @@ function fancify(seed: string, shade: 'light' | 'dark') {
return `hsl(${h},${s}%,${l}%)`; 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 { export class Highlighter {
private static initDecorations(): Map< private static initDecorations(): Map<
string, string,
@ -32,36 +65,44 @@ export class Highlighter {
tag: string, tag: string,
textDecoration?: string textDecoration?: string
): [string, vscode.TextEditorDecorationType] => { ): [string, vscode.TextEditorDecorationType] => {
const color = new vscode.ThemeColor('ralsp.' + tag); const scope = scopes.find(tag)
const decor = vscode.window.createTextEditorDecorationType({
color, if (scope) {
textDecoration const decor = createDecorationFromTextmate(scope);
}); return [tag, decor];
return [tag, decor]; }
else {
const color = new vscode.ThemeColor('ralsp.' + tag);
const decor = vscode.window.createTextEditorDecorationType({
color,
textDecoration
});
return [tag, decor];
}
}; };
const decorations: Iterable< const decorations: Iterable<
[string, vscode.TextEditorDecorationType] [string, vscode.TextEditorDecorationType]
> = [ > = [
decoration('comment'), decoration('comment'),
decoration('string'), decoration('string'),
decoration('keyword'), decoration('keyword'),
decoration('keyword.control'), decoration('keyword.control'),
decoration('keyword.unsafe'), decoration('keyword.unsafe'),
decoration('function'), decoration('function'),
decoration('parameter'), decoration('parameter'),
decoration('constant'), decoration('constant'),
decoration('type'), decoration('type'),
decoration('builtin'), decoration('builtin'),
decoration('text'), decoration('text'),
decoration('attribute'), decoration('attribute'),
decoration('literal'), decoration('literal'),
decoration('macro'), decoration('macro'),
decoration('variable'), decoration('variable'),
decoration('variable.mut', 'underline'), decoration('variable.mut', 'underline'),
decoration('field'), decoration('field'),
decoration('module') decoration('module')
]; ];
return new Map<string, vscode.TextEditorDecorationType>(decorations); return new Map<string, vscode.TextEditorDecorationType>(decorations);
} }
@ -89,6 +130,8 @@ export class Highlighter {
// //
// Note: decoration objects need to be kept around so we can dispose them // Note: decoration objects need to be kept around so we can dispose them
// if the user disables syntax highlighting // if the user disables syntax highlighting
if (this.decorations == null) { if (this.decorations == null) {
this.decorations = Highlighter.initDecorations(); this.decorations = Highlighter.initDecorations();
} }
@ -133,6 +176,7 @@ export class Highlighter {
tag tag
) as vscode.TextEditorDecorationType; ) as vscode.TextEditorDecorationType;
const ranges = byTag.get(tag)!; const ranges = byTag.get(tag)!;
editor.setDecorations(dec, ranges); editor.setDecorations(dec, ranges);
} }

142
editors/code/src/scopes.ts Normal file
View file

@ -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<string, TextMateRuleSettings>()
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);
}