[clangd] Loading TokenColorRules as a class mapping the rules to their associated clangd TextMate scope index.

Summary: Loads a mapping of the clangd scope lookup table scopes to the most specific rule with the highest "precedence" on initialize. Preprocesses into a class so it's simple/fast to access when doing the actual coloring later.

Reviewers: hokein, ilya-biryukov

Subscribers: MaskRay, jkorous, arphaman, kadircet, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D65856

llvm-svn: 368834
This commit is contained in:
Johan Vikstrom 2019-08-14 12:11:58 +00:00
parent 4c8deb6172
commit 9fa2599e9b
2 changed files with 67 additions and 3 deletions

View File

@ -47,6 +47,8 @@ export class SemanticHighlightingFeature implements vscodelc.StaticFeature {
// The TextMate scope lookup table. A token with scope index i has the scopes
// on index i in the lookup table.
scopeLookupTable: string[][];
// The rules for the current theme.
themeRuleMatcher: ThemeRuleMatcher;
fillClientCapabilities(capabilities: vscodelc.ClientCapabilities) {
// Extend the ClientCapabilities type and add semantic highlighting
// capability to the object.
@ -58,6 +60,12 @@ export class SemanticHighlightingFeature implements vscodelc.StaticFeature {
};
}
async loadCurrentTheme() {
this.themeRuleMatcher = new ThemeRuleMatcher(
await loadTheme(vscode.workspace.getConfiguration('workbench')
.get<string>('colorTheme')));
}
initialize(capabilities: vscodelc.ServerCapabilities,
documentSelector: vscodelc.DocumentSelector|undefined) {
// The semantic highlighting capability information is in the capabilities
@ -68,6 +76,7 @@ export class SemanticHighlightingFeature implements vscodelc.StaticFeature {
if (!serverCapabilities.semanticHighlighting)
return;
this.scopeLookupTable = serverCapabilities.semanticHighlighting.scopes;
this.loadCurrentTheme();
}
handleNotification(params: SemanticHighlightingParams) {}
@ -101,6 +110,39 @@ interface TokenColorRule {
foreground: string;
}
export class ThemeRuleMatcher {
// The rules for the theme.
private themeRules: TokenColorRule[];
// A cache for the getBestThemeRule function.
private bestRuleCache: Map<string, TokenColorRule> = new Map();
constructor(rules: TokenColorRule[]) { this.themeRules = rules; }
// Returns the best rule for a scope.
getBestThemeRule(scope: string): TokenColorRule {
if (this.bestRuleCache.has(scope))
return this.bestRuleCache.get(scope);
let bestRule: TokenColorRule = {scope : '', foreground : ''};
this.themeRules.forEach((rule) => {
// The best rule for a scope is the rule that is the longest prefix of the
// scope (unless a perfect match exists in which case the perfect match is
// the best). If a rule is not a prefix and we tried to match with longest
// common prefix instead variables would be highlighted as `less`
// variables when using Light+ (as variable.other would be matched against
// variable.other.less in this case). Doing common prefix matching also
// means we could match variable.cpp to variable.css if variable.css
// occurs before variable in themeRules.
// FIXME: This is not defined in the TextMate standard (it is explicitly
// undefined, https://macromates.com/manual/en/scope_selectors). Might
// want to rank some other way.
if (scope.startsWith(rule.scope) &&
rule.scope.length > bestRule.scope.length)
// This rule matches and is more specific than the old rule.
bestRule = rule;
});
this.bestRuleCache.set(scope, bestRule);
return bestRule;
}
}
// Get all token color rules provided by the theme.
function loadTheme(themeName: string): Promise<TokenColorRule[]> {
const extension =

View File

@ -1,13 +1,13 @@
import * as assert from 'assert';
import * as path from 'path';
import * as TM from '../src/semantic-highlighting';
import * as SM from '../src/semantic-highlighting';
suite('SemanticHighlighting Tests', () => {
test('Parses arrays of textmate themes.', async () => {
const themePath =
path.join(__dirname, '../../test/assets/includeTheme.jsonc');
const scopeColorRules = await TM.parseThemeFile(themePath);
const scopeColorRules = await SM.parseThemeFile(themePath);
const getScopeRule = (scope: string) =>
scopeColorRules.find((v) => v.scope === scope);
assert.equal(scopeColorRules.length, 3);
@ -33,6 +33,28 @@ suite('SemanticHighlighting Tests', () => {
]
];
testCases.forEach((testCase, i) => assert.deepEqual(
TM.decodeTokens(testCase), expected[i]));
SM.decodeTokens(testCase), expected[i]));
});
test('ScopeRules overrides for more specific themes', () => {
const rules = [
{scope : 'variable.other.css', foreground : '1'},
{scope : 'variable.other', foreground : '2'},
{scope : 'storage', foreground : '3'},
{scope : 'storage.static', foreground : '4'},
{scope : 'storage', foreground : '5'},
{scope : 'variable.other.parameter', foreground : '6'},
];
const tm = new SM.ThemeRuleMatcher(rules);
assert.deepEqual(tm.getBestThemeRule('variable.other.cpp').scope,
'variable.other');
assert.deepEqual(tm.getBestThemeRule('storage.static').scope,
'storage.static');
assert.deepEqual(
tm.getBestThemeRule('storage'),
rules[2]); // Match the first element if there are duplicates.
assert.deepEqual(tm.getBestThemeRule('variable.other.parameter').scope,
'variable.other.parameter');
assert.deepEqual(tm.getBestThemeRule('variable.other.parameter.cpp').scope,
'variable.other.parameter');
});
});