diff --git a/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts b/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts --- a/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ b/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts @@ -47,6 +47,8 @@ // 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 @@ }; } + async loadCurrentTheme() { + this.themeRuleMatcher = new ThemeRuleMatcher( + await loadTheme(vscode.workspace.getConfiguration('workbench') + .get('colorTheme'))); + } + initialize(capabilities: vscodelc.ServerCapabilities, documentSelector: vscodelc.DocumentSelector|undefined) { // The semantic highlighting capability information is in the capabilities @@ -68,6 +76,7 @@ if (!serverCapabilities.semanticHighlighting) return; this.scopeLookupTable = serverCapabilities.semanticHighlighting.scopes; + this.loadCurrentTheme(); } handleNotification(params: SemanticHighlightingParams) {} @@ -101,6 +110,35 @@ foreground: string; } +export class ThemeRuleMatcher { + // The rules for the theme. + private rules: TokenColorRule[]; + // A cache for the getBestThemeRule function. + private bestRuleCache: Map = new Map(); + constructor(rules: TokenColorRule[]) { this.rules = 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.rules.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). + // 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 (rule.scope.length <= scope.length && + scope.substr(0, rule.scope.length) === 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 { const extension = diff --git a/clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts b/clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts --- a/clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ b/clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts @@ -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,21 @@ ] ]; 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 : 'c.b', foreground : '1'}, + {scope : 'a', foreground : '2'}, + {scope : 'a.b', foreground : '3'}, + {scope : 'a', foreground : '4'}, + {scope : 'c.b.a', foreground : '5'}, + ]; + const tm = new SM.ThemeRuleMatcher(rules); + assert.deepEqual(tm.getBestThemeRule('c.b'), rules[0]); + assert.deepEqual(tm.getBestThemeRule('a.b'), rules[2]); + assert.deepEqual(tm.getBestThemeRule('a'), rules[1]); + assert.deepEqual(tm.getBestThemeRule('c.b.a'), rules[4]); + assert.deepEqual(tm.getBestThemeRule('c.b.a.d.f'), rules[4]); }); });