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 @@ -12,6 +12,39 @@ foreground: string; } +export class ScopeRules { + // The TextMate scopes that should be mapped to a color. + private scopes: string[]; + // Contains the current best matching scope for the scope at the corresponding + // index. + private scopeRules: TokenColorRule[]; + + constructor(scopes: string[]) { + this.scopes = scopes; + this.scopeRules = + this.scopes.map(() => ({scope : '', foreground : '#000'})); + } + + addRule(rule: TokenColorRule) { + // Find the associated clangd scope(s) index for this scope. A scope being a + // possible candidate means that the clangd scope must have the rule's scope + // as a prefix. + const allCandidates = + this.scopes.map((s, i) => ({s : s, i : i})) + .filter(({s}) => s.substr(0, rule.scope.length) === rule.scope); + // If this scope is more specific than any of current scopes for the clangd + // scopes it should be replaced. As both options are prefixes of the clangd + // scope it's enough to compare lengths. + allCandidates.forEach(({i}) => { + if (rule.scope.length > this.scopeRules[i].scope.length) { + this.scopeRules[i] = rule; + } + }); + } + + getRule(idx: number) { return this.scopeRules[idx]; } +} + // 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 @@ -11,8 +11,23 @@ const getScopeRule = (scope: string) => scopeColorRules.find((v) => v.scope === scope); assert.equal(scopeColorRules.length, 3); - assert.deepEqual(getScopeRule('a'), {scope : 'a', textColor : '#fff'}); - assert.deepEqual(getScopeRule('b'), {scope : 'b', textColor : '#000'}); - assert.deepEqual(getScopeRule('c'), {scope : 'c', textColor : '#bcd'}); + assert.deepEqual(getScopeRule('a'), {scope : 'a', foreground : '#fff'}); + assert.deepEqual(getScopeRule('b'), {scope : 'b', foreground : '#000'}); + assert.deepEqual(getScopeRule('c'), {scope : 'c', foreground : '#bcd'}); + }); + test('ScopeRules overrides for more specific themes', () => { + const scopes = [ 'a.b.c.d', 'a.b.f', 'a' ]; + const rules = [ + {scope : 'a.b.c', foreground : '1'}, + {scope : 'a.b', foreground : '2'}, + {scope : 'a.b.c.d', foreground : '3'}, + {scope : 'a', foreground : '4'}, + ]; + + const tm = new TM.ScopeRules(scopes); + rules.forEach((r) => tm.addRule(r)); + assert.deepEqual(tm.getRule(0), rules[2]); + assert.deepEqual(tm.getRule(1), rules[1]); + assert.deepEqual(tm.getRule(2), rules[3]); }); });