forked from OSchip/llvm-project
[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:
parent
4c8deb6172
commit
9fa2599e9b
|
@ -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 =
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue