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": {
"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",

View File

@ -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",

View File

@ -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;
}

View File

@ -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

View File

@ -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<string, vscode.TextEditorDecorationType>(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);
}

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);
}