mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-27 20:35:09 +00:00
* 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:
parent
dad9bc6caa
commit
c60f9bf4c6
6 changed files with 151 additions and 68 deletions
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue