232785251b
2061: Theme loading and "editor.tokenColorCustomizations" support. r=matklad a=seivan Fixes: [Issue#1294](https://github.com/rust-analyzer/rust-analyzer/issues/1294#issuecomment-497450325) 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>
202 lines
6.4 KiB
TypeScript
202 lines
6.4 KiB
TypeScript
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;
|
|
|
|
export interface CargoWatchOptions {
|
|
enable: boolean;
|
|
arguments: string[];
|
|
command: string;
|
|
allTargets: boolean;
|
|
}
|
|
|
|
export interface CargoFeatures {
|
|
noDefaultFeatures: boolean;
|
|
allFeatures: boolean;
|
|
features: string[];
|
|
}
|
|
|
|
export class Config {
|
|
public highlightingOn = true;
|
|
public rainbowHighlightingOn = false;
|
|
public enableEnhancedTyping = true;
|
|
public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
|
|
public lruCapacity: null | number = null;
|
|
public displayInlayHints = true;
|
|
public maxInlayHintLength: null | number = null;
|
|
public excludeGlobs = [];
|
|
public useClientWatching = true;
|
|
public featureFlags = {};
|
|
// for internal use
|
|
public withSysroot: null | boolean = null;
|
|
public cargoWatchOptions: CargoWatchOptions = {
|
|
enable: true,
|
|
arguments: [],
|
|
command: '',
|
|
allTargets: true,
|
|
};
|
|
public cargoFeatures: CargoFeatures = {
|
|
noDefaultFeatures: false,
|
|
allFeatures: true,
|
|
features: [],
|
|
};
|
|
|
|
private prevEnhancedTyping: null | boolean = null;
|
|
private prevCargoFeatures: null | CargoFeatures = null;
|
|
|
|
constructor() {
|
|
vscode.workspace.onDidChangeConfiguration(_ =>
|
|
this.userConfigChanged(),
|
|
);
|
|
this.userConfigChanged();
|
|
}
|
|
|
|
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')) {
|
|
this.rainbowHighlightingOn = config.get(
|
|
'rainbowHighlightingOn',
|
|
) as boolean;
|
|
}
|
|
|
|
if (config.has('enableEnhancedTyping')) {
|
|
this.enableEnhancedTyping = config.get(
|
|
'enableEnhancedTyping',
|
|
) as boolean;
|
|
|
|
if (this.prevEnhancedTyping === null) {
|
|
this.prevEnhancedTyping = this.enableEnhancedTyping;
|
|
}
|
|
} else if (this.prevEnhancedTyping === null) {
|
|
this.prevEnhancedTyping = this.enableEnhancedTyping;
|
|
}
|
|
|
|
if (this.prevEnhancedTyping !== this.enableEnhancedTyping) {
|
|
requireReloadMessage =
|
|
'Changing enhanced typing setting requires a reload';
|
|
this.prevEnhancedTyping = this.enableEnhancedTyping;
|
|
}
|
|
|
|
if (config.has('raLspServerPath')) {
|
|
this.raLspServerPath =
|
|
RA_LSP_DEBUG || (config.get('raLspServerPath') as string);
|
|
}
|
|
|
|
if (config.has('cargo-watch.enable')) {
|
|
this.cargoWatchOptions.enable = config.get<boolean>(
|
|
'cargo-watch.enable',
|
|
true,
|
|
);
|
|
}
|
|
|
|
if (config.has('cargo-watch.arguments')) {
|
|
this.cargoWatchOptions.arguments = config.get<string[]>(
|
|
'cargo-watch.arguments',
|
|
[],
|
|
);
|
|
}
|
|
|
|
if (config.has('cargo-watch.command')) {
|
|
this.cargoWatchOptions.command = config.get<string>(
|
|
'cargo-watch.command',
|
|
'',
|
|
);
|
|
}
|
|
|
|
if (config.has('cargo-watch.allTargets')) {
|
|
this.cargoWatchOptions.allTargets = config.get<boolean>(
|
|
'cargo-watch.allTargets',
|
|
true,
|
|
);
|
|
}
|
|
|
|
if (config.has('lruCapacity')) {
|
|
this.lruCapacity = config.get('lruCapacity') as number;
|
|
}
|
|
|
|
if (config.has('displayInlayHints')) {
|
|
this.displayInlayHints = config.get('displayInlayHints') as boolean;
|
|
}
|
|
if (config.has('maxInlayHintLength')) {
|
|
this.maxInlayHintLength = config.get(
|
|
'maxInlayHintLength',
|
|
) as number;
|
|
}
|
|
if (config.has('excludeGlobs')) {
|
|
this.excludeGlobs = config.get('excludeGlobs') || [];
|
|
}
|
|
if (config.has('useClientWatching')) {
|
|
this.useClientWatching = config.get('useClientWatching') || true;
|
|
}
|
|
if (config.has('featureFlags')) {
|
|
this.featureFlags = config.get('featureFlags') || {};
|
|
}
|
|
if (config.has('withSysroot')) {
|
|
this.withSysroot = config.get('withSysroot') || false;
|
|
}
|
|
|
|
if (config.has('cargoFeatures.noDefaultFeatures')) {
|
|
this.cargoFeatures.noDefaultFeatures = config.get(
|
|
'cargoFeatures.noDefaultFeatures',
|
|
false,
|
|
);
|
|
}
|
|
if (config.has('cargoFeatures.allFeatures')) {
|
|
this.cargoFeatures.allFeatures = config.get(
|
|
'cargoFeatures.allFeatures',
|
|
true,
|
|
);
|
|
}
|
|
if (config.has('cargoFeatures.features')) {
|
|
this.cargoFeatures.features = config.get(
|
|
'cargoFeatures.features',
|
|
[],
|
|
);
|
|
}
|
|
|
|
if (
|
|
this.prevCargoFeatures !== null &&
|
|
(this.cargoFeatures.allFeatures !==
|
|
this.prevCargoFeatures.allFeatures ||
|
|
this.cargoFeatures.noDefaultFeatures !==
|
|
this.prevCargoFeatures.noDefaultFeatures ||
|
|
this.cargoFeatures.features.length !==
|
|
this.prevCargoFeatures.features.length ||
|
|
this.cargoFeatures.features.some(
|
|
(v, i) => v !== this.prevCargoFeatures!.features[i],
|
|
))
|
|
) {
|
|
requireReloadMessage = 'Changing cargo features requires a reload';
|
|
}
|
|
this.prevCargoFeatures = { ...this.cargoFeatures };
|
|
|
|
if (requireReloadMessage !== null) {
|
|
const reloadAction = 'Reload now';
|
|
vscode.window
|
|
.showInformationMessage(requireReloadMessage, reloadAction)
|
|
.then(selectedAction => {
|
|
if (selectedAction === reloadAction) {
|
|
vscode.commands.executeCommand(
|
|
'workbench.action.reloadWindow',
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|