Skip to content

Commit

Permalink
Merge #2061
Browse files Browse the repository at this point in the history
2061: Theme loading and "editor.tokenColorCustomizations" support. r=matklad a=seivan

Fixes: [Issue#1294](rust-lang/rust-analyzer#1294 (comment))

TODO: 
- [x] Load themes
- [x] Load existing `ralsp`-prefixed overrides from `"workbench.colorCustomizations"`.
- [x] Load overrides from `"editor.tokenColorCustomizations.textMateRules"`.
- [x] Use RA tags to load `vscode.DecorationRenderOptions` (colors) from theme & overrides.
- [x] Map RA tags to common TextMate scopes before loading colors.
- [x] Add default scope mappings in extension.
- [x] Cache mappings between settings updates. 
- [x] Add scope mapping configuration manifest in `package.json`
- [x] Load configurable scope mappings from settings.
- [x] Load JSON Scheme for text mate scope rules in settings.
- [x] Update [Readme](https://github.com/seivan/rust-analyzer/blob/feature/themes/docs/user/README.md#settings).

Borrowed the theme loading (`scopes.ts`) from `Tree Sitter` with some modifications to reading `"editor.tokenColorCustomizations"` for merging with loaded themes and had to remove the async portions to be able to load it from settings updates. 

~Just a PoC and an idea I toyed around with a lot of room for improvement.~
For starters, certain keywords aren't part of the standard TextMate grammar, so it still reads colors from the `ralsp` prefixed values in `"workbench.colorCustomizations"`. 

But I think there's more value making the extension work with existing themes by maping some of the decoration tags to existing key or keys. 

<img width="453" alt="Screenshot 2019-11-09 at 17 43 18" src="https://user-images.githubusercontent.com/55424/68531968-71b4e380-0318-11ea-924e-cdbb8d5eae06.png">
<img width="780" alt="Screenshot 2019-11-09 at 17 41 45" src="https://user-images.githubusercontent.com/55424/68531950-4b8f4380-0318-11ea-8f85-24a84efaf23b.png">
<img width="468" alt="Screenshot 2019-11-09 at 17 40 29" src="https://user-images.githubusercontent.com/55424/68531952-51852480-0318-11ea-800a-6ae9215f5368.png">


These will merge with the default ones coming with the extension, so you don't have to implement all of them and works well with overrides defined in settings. 

```jsonc
    "editor.tokenColorCustomizations": {
        "textMateRules": [
            {
                "scope": "keyword",
                "settings": {
                    "fontStyle": "bold",
                }
            },
        ]
    },
```


Edit: The idea is to work with 90% of the themes out there by working within existing scopes available that are generally styled. It's not to say I want to erase the custom Rust scopes - those should still remain and eventually worked into a custom grammar bundle for Rust specific themes that target those, I just want to make it work with generic themes offered on the market place for now. 

A custom grammar bundle and themes for Rust specific scopes is out of... scope for this PR. 
We'll make another round to tackle those issues. 


Current fallbacks implemented

```typescript
    [
        'comment',
        [
            'comment',
            'comment.block',
            'comment.line',
            'comment.block.documentation'
        ]
    ],
    ['string', ['string']],
    ['keyword', ['keyword']],
    ['keyword.control', ['keyword.control', 'keyword', 'keyword.other']],
    [
        'keyword.unsafe',
        ['storage.modifier', 'keyword.other', 'keyword.control', 'keyword']
    ],
    ['function', ['entity.name.function']],
    ['parameter', ['variable.parameter']],
    ['constant', ['constant', 'variable']],
    ['type', ['entity.name.type']],
    ['builtin', ['variable.language', 'support.type', 'support.type']],
    ['text', ['string', 'string.quoted', 'string.regexp']],
    ['attribute', ['keyword']],
    ['literal', ['string', 'string.quoted', 'string.regexp']],
    ['macro', ['support.other']],
    ['variable', ['variable']],
    ['variable.mut', ['variable', 'storage.modifier']],
    [
        'field',
        [
            'variable.object.property',
            'meta.field.declaration',
            'meta.definition.property',
            'variable.other'
        ]
    ],
    ['module', ['entity.name.section', 'entity.other']]
```


Co-authored-by: Seivan Heidari <seivan.heidari@icloud.com>
  • Loading branch information
bors[bot] and seivan authored Dec 29, 2019
2 parents d5c2f26 + 1ba526e commit 7f6751b
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 12 deletions.
5 changes: 5 additions & 0 deletions rust-analyzer/editors/code/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 64 additions & 1 deletion rust-analyzer/editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"dependencies": {
"lookpath": "^1.0.4",
"seedrandom": "^3.0.5",
"vscode-languageclient": "^6.0.0-next.9"
"vscode-languageclient": "^6.0.0-next.9",
"jsonc-parser": "^2.1.0"
},
"devDependencies": {
"@types/glob": "^7.1.1",
Expand Down Expand Up @@ -166,6 +167,68 @@
"default": false,
"description": "Highlight Rust code (overrides built-in syntax highlighting)"
},
"rust-analyzer.scopeMappings": {
"type": "object",
"definitions": {},
"properties": {
"comment": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"string": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"keyword": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"keyword.control": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"keyword.unsafe": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"function": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"parameter": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"constant": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"type": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"builtin": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"text": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"attribute": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"literal": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"macro": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"variable": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"variable.mut": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"field": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
},
"module": {
"$ref": "vscode://schemas/textmate-colors#/items/properties/scope"
}
},
"additionalProperties": false,
"description": "Mapping Rust Analyzer scopes to TextMateRule scopes list."
},
"rust-analyzer.rainbowHighlightingOn": {
"type": "boolean",
"default": false,
Expand Down
14 changes: 9 additions & 5 deletions rust-analyzer/editors/code/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode';

import * as scopes from './scopes';
import * as scopesMapper from './scopes_mapper';
import { Server } from './server';

const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
Expand Down Expand Up @@ -54,10 +55,17 @@ export class Config {

public userConfigChanged() {
const config = vscode.workspace.getConfiguration('rust-analyzer');

Server.highlighter.removeHighlights();

let requireReloadMessage = null;

if (config.has('highlightingOn')) {
this.highlightingOn = config.get('highlightingOn') as boolean;
if (this.highlightingOn) {
scopes.load();
scopesMapper.load();
}
}

if (config.has('rainbowHighlightingOn')) {
Expand All @@ -66,10 +74,6 @@ export class Config {
) as boolean;
}

if (!this.highlightingOn && Server) {
Server.highlighter.removeHighlights();
}

if (config.has('enableEnhancedTyping')) {
this.enableEnhancedTyping = config.get(
'enableEnhancedTyping',
Expand Down
62 changes: 56 additions & 6 deletions rust-analyzer/editors/code/src/highlighting.ts
Original file line number Diff line number Diff line change
@@ -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 * as scopesMapper from './scopes_mapper';

import { Server } from './server';

Expand All @@ -23,6 +25,41 @@ function fancify(seed: string, shade: 'light' | 'dark') {
return `hsl(${h},${s}%,${l}%)`;
}

function createDecorationFromTextmate(
themeStyle: scopes.TextMateRuleSettings,
): vscode.TextEditorDecorationType {
const decorationOptions: vscode.DecorationRenderOptions = {};
decorationOptions.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen;

if (themeStyle.foreground) {
decorationOptions.color = themeStyle.foreground;
}

if (themeStyle.background) {
decorationOptions.backgroundColor = themeStyle.background;
}

if (themeStyle.fontStyle) {
const parts: string[] = themeStyle.fontStyle.split(' ');
parts.forEach(part => {
switch (part) {
case 'italic':
decorationOptions.fontStyle = 'italic';
break;
case 'bold':
decorationOptions.fontWeight = 'bold';
break;
case 'underline':
decorationOptions.textDecoration = 'underline';
break;
default:
break;
}
});
}
return vscode.window.createTextEditorDecorationType(decorationOptions);
}

export class Highlighter {
private static initDecorations(): Map<
string,
Expand All @@ -32,12 +69,25 @@ 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 rule = scopesMapper.toRule(tag, scopes.find);

if (rule) {
const decor = createDecorationFromTextmate(rule);
return [tag, decor];
} else {
const fallBackTag = 'ralsp.' + tag;
// console.log(' ');
// console.log('Missing theme for: <"' + tag + '"> for following mapped scopes:');
// console.log(scopesMapper.find(tag));
// console.log('Falling back to values defined in: ' + fallBackTag);
// console.log(' ');
const color = new vscode.ThemeColor(fallBackTag);
const decor = vscode.window.createTextEditorDecorationType({
color,
textDecoration,
});
return [tag, decor];
}
};

const decorations: Iterable<[
Expand Down
146 changes: 146 additions & 0 deletions rust-analyzer/editors/code/src/scopes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
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 rules = new Map<string, TextMateRuleSettings>();

export function find(scope: string): TextMateRuleSettings | undefined {
return rules.get(scope);
}

// Load all textmate scopes in the currently active theme
export function load() {
// Remove any previous theme
rules.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)
}
}

function filterThemeExtensions(extension: vscode.Extension<any>): boolean {
return (
extension.extensionKind === vscode.ExtensionKind.UI &&
extension.packageJSON.contributes &&
extension.packageJSON.contributes.themes
);
}

// Find current theme on disk
function loadThemeNamed(themeName: string) {
const themePaths = vscode.extensions.all
.filter(filterThemeExtensions)
.reduce((list, extension) => {
return extension.packageJSON.contributes.themes
.filter(
(element: any) =>
(element.id || element.label) === themeName,
)
.map((element: any) =>
path.join(extension.extensionPath, element.path),
)
.concat(list);
}, Array<string>());

themePaths.forEach(loadThemeFile);

const tokenColorCustomizations: [any] = [
vscode.workspace
.getConfiguration('editor')
.get('tokenColorCustomizations'),
];

tokenColorCustomizations
.filter(custom => custom && custom.textMateRules)
.map(custom => custom.textMateRules)
.forEach(loadColors);
}

function loadThemeFile(themePath: string) {
const themeContent = [themePath]
.filter(isFile)
.map(readFileText)
.map(parseJSON)
.filter(theme => theme);

themeContent
.filter(theme => theme.tokenColors)
.map(theme => theme.tokenColors)
.forEach(loadColors);

themeContent
.filter(theme => theme.include)
.map(theme => path.join(path.dirname(themePath), theme.include))
.forEach(loadThemeFile);
}

function mergeRuleSettings(
defaultSetting: TextMateRuleSettings | undefined,
override: TextMateRuleSettings,
): TextMateRuleSettings {
if (defaultSetting === undefined) {
return override;
}
const mergedRule = defaultSetting;

mergedRule.background = override.background || defaultSetting.background;
mergedRule.foreground = override.foreground || defaultSetting.foreground;
mergedRule.fontStyle = override.fontStyle || defaultSetting.foreground;

return mergedRule;
}

function updateRules(
scope: string,
updatedSettings: TextMateRuleSettings,
): void {
[rules.get(scope)]
.map(settings => mergeRuleSettings(settings, updatedSettings))
.forEach(settings => rules.set(scope, settings));
}

function loadColors(textMateRules: TextMateRule[]): void {
textMateRules.forEach(rule => {
if (typeof rule.scope === 'string') {
updateRules(rule.scope, rule.settings);
} else if (rule.scope instanceof Array) {
rule.scope.forEach(scope => updateRules(scope, rule.settings));
}
});
}

function isFile(filePath: string): boolean {
return [filePath].map(fs.statSync).every(stat => stat.isFile());
}

function readFileText(filePath: string): string {
return fs.readFileSync(filePath, 'utf8');
}

function parseJSON(content: string): any {
return jsonc.parse(content);
}
Loading

0 comments on commit 7f6751b

Please sign in to comment.