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 @@ -101,6 +101,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 @@ -35,4 +35,19 @@ testCases.forEach((testCase, i) => assert.deepEqual( TM.decodeTokens(testCase), expected[i])); }); + 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]); + }); });