* Adding scope mapping configuration manifest in package.json

* Loading configurable scope mappings from settings.
* Updating Readme with `rust-analyzer.scopeMappings`.
`rust-analyzer.scopeMappings` -- a scheme backed JSON object to tweak Rust Analyzer scopes to TextMate scopes.
   ```jsonc
    {
        //Will autocomplete keys to available RA scopes.
        "keyword.unsafe": ["keyword", "keyword.control"],
        //Values are string | TextMateScope | [string | TextMateScope]
        "comments": "comment.block"
    }
   ```
This commit is contained in:
Seivan Heidari 2019-11-04 23:59:11 +01:00
parent dad9bc6caa
commit c60f9bf4c6
6 changed files with 151 additions and 68 deletions

View file

@ -82,7 +82,16 @@ host.
### Settings ### Settings
* `rust-analyzer.highlightingOn`: enables experimental syntax highlighting * `rust-analyzer.highlightingOn`: enables experimental syntax highlighting.
* `rust-analyzer.scopeMappings` -- a scheme backed JSON object to tweak Rust Analyzer scopes to TextMate scopes.
```jsonc
{
//Will autocomplete keys to available RA scopes.
"keyword.unsafe": ["keyword", "keyword.control"],
//Values are string | TextMateScope | [string | TextMateScope]
"comments": "comment.block"
}
```
* `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts * `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts
`Enter` key to make it easier to continue comments. Note that it may conflict with VIM emulation plugin. `Enter` key to make it easier to continue comments. Note that it may conflict with VIM emulation plugin.
* `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable * `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable
@ -101,7 +110,7 @@ host.
* `rust-analyzer.trace.cargo-watch`: enables cargo-watch logging * `rust-analyzer.trace.cargo-watch`: enables cargo-watch logging
* `RUST_SRC_PATH`: environment variable that overwrites the sysroot * `RUST_SRC_PATH`: environment variable that overwrites the sysroot
* `rust-analyzer.featureFlags` -- a JSON object to tweak fine-grained behavior: * `rust-analyzer.featureFlags` -- a JSON object to tweak fine-grained behavior:
```js ```jsonc
{ {
// Show diagnostics produced by rust-analyzer itself. // Show diagnostics produced by rust-analyzer itself.
"lsp.diagnostics": true, "lsp.diagnostics": true,

View file

@ -167,6 +167,68 @@
"default": false, "default": false,
"description": "Highlight Rust code (overrides built-in syntax highlighting)" "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": { "rust-analyzer.rainbowHighlightingOn": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
@ -488,4 +550,4 @@
} }
] ]
} }
} }

View file

@ -48,11 +48,13 @@ export class Config {
const config = vscode.workspace.getConfiguration('rust-analyzer'); const config = vscode.workspace.getConfiguration('rust-analyzer');
Server.highlighter.removeHighlights(); Server.highlighter.removeHighlights();
scopes.load()
scopesMapper.load()
if (config.has('highlightingOn')) {
if (config.has('highlightingOn')) {
this.highlightingOn = config.get('highlightingOn') as boolean; this.highlightingOn = config.get('highlightingOn') as boolean;
if (this.highlightingOn) {
scopes.load();
scopesMapper.load();
}
} }
if (config.has('rainbowHighlightingOn')) { if (config.has('rainbowHighlightingOn')) {
@ -61,9 +63,6 @@ export class Config {
) as boolean; ) as boolean;
} }
if (!this.highlightingOn && Server) {
Server.highlighter.removeHighlights();
}
if (config.has('enableEnhancedTyping')) { if (config.has('enableEnhancedTyping')) {
this.enableEnhancedTyping = config.get( this.enableEnhancedTyping = config.get(

View file

@ -1,7 +1,7 @@
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 * as scopes from './scopes';
import * as scopesMapper from './scopes_mapper'; import * as scopesMapper from './scopes_mapper';
import { Server } from './server'; import { Server } from './server';
@ -25,35 +25,35 @@ function fancify(seed: string, shade: 'light' | 'dark') {
return `hsl(${h},${s}%,${l}%)`; return `hsl(${h},${s}%,${l}%)`;
} }
function createDecorationFromTextmate(themeStyle: scopes.TextMateRuleSettings): vscode.TextEditorDecorationType { function createDecorationFromTextmate(themeStyle: scopes.TextMateRuleSettings): vscode.TextEditorDecorationType {
const options: vscode.DecorationRenderOptions = {} const options: vscode.DecorationRenderOptions = {};
options.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen options.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen;
if (themeStyle.foreground) { if (themeStyle.foreground) {
options.color = themeStyle.foreground options.color = themeStyle.foreground;
} }
if (themeStyle.background) { if (themeStyle.background) {
options.backgroundColor = themeStyle.background options.backgroundColor = themeStyle.background;
} }
if (themeStyle.fontStyle) { if (themeStyle.fontStyle) {
const parts: string[] = themeStyle.fontStyle.split(' ') const parts: string[] = themeStyle.fontStyle.split(' ');
parts.forEach((part) => { parts.forEach((part) => {
switch (part) { switch (part) {
case 'italic': case 'italic':
options.fontStyle = 'italic' options.fontStyle = 'italic';
break break;
case 'bold': case 'bold':
options.fontWeight = 'bold' options.fontWeight = 'bold';
break;
break
case 'underline': case 'underline':
options.textDecoration = 'underline' options.textDecoration = 'underline';
break break;
default: default:
break break;
} }
}) })
} }
return vscode.window.createTextEditorDecorationType(options) return vscode.window.createTextEditorDecorationType(options);
} }
export class Highlighter { export class Highlighter {
@ -66,7 +66,7 @@ export class Highlighter {
textDecoration?: string textDecoration?: string
): [string, vscode.TextEditorDecorationType] => { ): [string, vscode.TextEditorDecorationType] => {
const rule = scopesMapper.toRule(tag, scopes.find) const rule = scopesMapper.toRule(tag, scopes.find);
if (rule) { if (rule) {
const decor = createDecorationFromTextmate(rule); const decor = createDecorationFromTextmate(rule);

View file

@ -1,41 +1,41 @@
import * as fs from 'fs' import * as fs from 'fs';
import * as path from 'path' import * as path from 'path';
import * as vscode from 'vscode' import * as vscode from 'vscode';
export interface TextMateRule { export interface TextMateRule {
scope: string | string[] scope: string | string[];
settings: TextMateRuleSettings settings: TextMateRuleSettings;
} }
export interface TextMateRuleSettings { export interface TextMateRuleSettings {
foreground: string | undefined foreground: string | undefined;
background: string | undefined background: string | undefined;
fontStyle: string | undefined fontStyle: string | undefined;
} }
// Current theme colors // Current theme colors
const rules = new Map<string, TextMateRuleSettings>() const rules = new Map<string, TextMateRuleSettings>();
export function find(scope: string): TextMateRuleSettings | undefined { export function find(scope: string): TextMateRuleSettings | undefined {
return rules.get(scope) return rules.get(scope);
} }
// Load all textmate scopes in the currently active theme // Load all textmate scopes in the currently active theme
export function load() { export function load() {
// Remove any previous theme // Remove any previous theme
rules.clear() rules.clear();
// Find out current color theme // Find out current color theme
const themeName = vscode.workspace.getConfiguration('workbench').get('colorTheme') const themeName = vscode.workspace.getConfiguration('workbench').get('colorTheme');
if (typeof themeName !== 'string') { if (typeof themeName !== 'string') {
// console.warn('workbench.colorTheme is', themeName) // console.warn('workbench.colorTheme is', themeName)
return return;
} }
// Try to load colors from that theme // Try to load colors from that theme
try { try {
loadThemeNamed(themeName) loadThemeNamed(themeName);
} catch (e) { } catch (e) {
// console.warn('failed to load theme', themeName, e) // console.warn('failed to load theme', themeName, e)
} }
@ -44,7 +44,7 @@ export function load() {
function filterThemeExtensions(extension: vscode.Extension<any>): boolean { function filterThemeExtensions(extension: vscode.Extension<any>): boolean {
return extension.extensionKind === vscode.ExtensionKind.UI && return extension.extensionKind === vscode.ExtensionKind.UI &&
extension.packageJSON.contributes && extension.packageJSON.contributes &&
extension.packageJSON.contributes.themes extension.packageJSON.contributes.themes;
} }
@ -59,17 +59,17 @@ function loadThemeNamed(themeName: string) {
.filter((element: any) => (element.id || element.label) === themeName) .filter((element: any) => (element.id || element.label) === themeName)
.map((element: any) => path.join(extension.extensionPath, element.path)) .map((element: any) => path.join(extension.extensionPath, element.path))
.concat(list) .concat(list)
}, Array<string>()) }, Array<string>());
themePaths.forEach(loadThemeFile) themePaths.forEach(loadThemeFile);
const tokenColorCustomizations: [any] = [vscode.workspace.getConfiguration('editor').get('tokenColorCustomizations')] const tokenColorCustomizations: [any] = [vscode.workspace.getConfiguration('editor').get('tokenColorCustomizations')]
tokenColorCustomizations tokenColorCustomizations
.filter(custom => custom && custom.textMateRules) .filter(custom => custom && custom.textMateRules)
.map(custom => custom.textMateRules) .map(custom => custom.textMateRules)
.forEach(loadColors) .forEach(loadColors);
} }
@ -79,26 +79,26 @@ function loadThemeFile(themePath: string) {
.filter(isFile) .filter(isFile)
.map(readFileText) .map(readFileText)
.map(parseJSON) .map(parseJSON)
.filter(theme => theme) .filter(theme => theme);
themeContent themeContent
.filter(theme => theme.tokenColors) .filter(theme => theme.tokenColors)
.map(theme => theme.tokenColors) .map(theme => theme.tokenColors)
.forEach(loadColors) .forEach(loadColors);
themeContent themeContent
.filter(theme => theme.include) .filter(theme => theme.include)
.map(theme => path.join(path.dirname(themePath), theme.include)) .map(theme => path.join(path.dirname(themePath), theme.include))
.forEach(loadThemeFile) .forEach(loadThemeFile);
} }
function mergeRuleSettings(defaultSetting: TextMateRuleSettings | undefined, override: TextMateRuleSettings): TextMateRuleSettings { function mergeRuleSettings(defaultSetting: TextMateRuleSettings | undefined, override: TextMateRuleSettings): TextMateRuleSettings {
if (defaultSetting === undefined) { return override } if (defaultSetting === undefined) { return override; }
const mergedRule = defaultSetting const mergedRule = defaultSetting;
mergedRule.background = override.background || defaultSetting.background mergedRule.background = override.background || defaultSetting.background;
mergedRule.foreground = override.foreground || defaultSetting.foreground mergedRule.foreground = override.foreground || defaultSetting.foreground;
mergedRule.fontStyle = override.fontStyle || defaultSetting.foreground mergedRule.fontStyle = override.fontStyle || defaultSetting.foreground;
return mergedRule return mergedRule
} }
@ -106,29 +106,29 @@ function mergeRuleSettings(defaultSetting: TextMateRuleSettings | undefined, ove
function updateRules(scope: string, updatedSettings: TextMateRuleSettings): void { function updateRules(scope: string, updatedSettings: TextMateRuleSettings): void {
[rules.get(scope)] [rules.get(scope)]
.map(settings => mergeRuleSettings(settings, updatedSettings)) .map(settings => mergeRuleSettings(settings, updatedSettings))
.forEach(settings => rules.set(scope, settings)) .forEach(settings => rules.set(scope, settings));
} }
function loadColors(textMateRules: TextMateRule[]): void { function loadColors(textMateRules: TextMateRule[]): void {
textMateRules.forEach(rule => { textMateRules.forEach(rule => {
if (typeof rule.scope === 'string') { if (typeof rule.scope === 'string') {
updateRules(rule.scope, rule.settings) updateRules(rule.scope, rule.settings);
} }
else if (rule.scope instanceof Array) { else if (rule.scope instanceof Array) {
rule.scope.forEach(scope => updateRules(scope, rule.settings)) rule.scope.forEach(scope => updateRules(scope, rule.settings));
} }
}) })
} }
function isFile(filePath: string): boolean { function isFile(filePath: string): boolean {
return [filePath].map(fs.statSync).every(stat => stat.isFile()) return [filePath].map(fs.statSync).every(stat => stat.isFile());
} }
function readFileText(filePath: string): string { function readFileText(filePath: string): string {
return fs.readFileSync(filePath, 'utf8') return fs.readFileSync(filePath, 'utf8');
} }
// Might need to replace with JSONC if a theme contains comments. // Might need to replace with JSONC if a theme contains comments.
function parseJSON(content: string): any { function parseJSON(content: string): any {
return JSON.parse(content) return JSON.parse(content);
} }

View file

@ -1,10 +1,9 @@
import * as vscode from 'vscode' import * as vscode from 'vscode';
import { TextMateRuleSettings } from './scopes' import { TextMateRuleSettings } from './scopes';
let mappings = new Map<string, string[]>();
let mappings = new Map<string, string[]>()
const defaultMapping = new Map<string, string[]>([ const defaultMapping = new Map<string, string[]>([
@ -27,25 +26,39 @@ const defaultMapping = new Map<string, string[]>([
['field', ['variable.object.property', 'meta.field.declaration', 'meta.definition.property', 'variable.other',]], ['field', ['variable.object.property', 'meta.field.declaration', 'meta.definition.property', 'variable.other',]],
['module', ['entity.name.section', 'entity.other']] ['module', ['entity.name.section', 'entity.other']]
] ]
) );
// Temporary exported for debugging for now. // Temporary exported for debugging for now.
export function find(scope: string): string[] { export function find(scope: string): string[] {
return mappings.get(scope) || [] return mappings.get(scope) || [];
} }
export function toRule(scope: string, intoRule: (scope: string) => TextMateRuleSettings | undefined): TextMateRuleSettings | undefined { export function toRule(scope: string, intoRule: (scope: string) => TextMateRuleSettings | undefined): TextMateRuleSettings | undefined {
return find(scope).map(intoRule).filter(rule => rule !== undefined)[0] return find(scope).map(intoRule).filter(rule => rule !== undefined)[0];
}
function isString(value: any): value is string {
return typeof value === 'string';
}
function isArrayOfString(value: any): value is string[] {
return Array.isArray(value) && value.every(item => isString(item));
} }
export function load() { export function load() {
const configuration = vscode.workspace const rawConfig: { [key: string]: any } = vscode.workspace
.getConfiguration('rust-analyzer') .getConfiguration('rust-analyzer')
.get('scopeMappings') as Map<string, string[]> | undefined .get('scopeMappings')
|| new Map() || {};
mappings = new Map([...Array.from(defaultMapping.entries()), ...Array.from(configuration.entries())]) mappings = Object
.entries(rawConfig)
.filter(([_, value]) => isString(value) || isArrayOfString(value))
.reduce((list, [key, value]: [string, string | string[]]) => {
return list.set(key, isString(value) ? [value] : value);
}, defaultMapping);
} }