[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
This revision was automatically updated to reflect the committed changes. Closed by commit rL368834: [clangd] Loading TokenColorRules as a class mapping the rules to their… (authored by jvikstrom, committed by ). Herald added a project: LLVM. Herald added a subscriber: llvm-commits. Changed prior to commit: https://reviews.llvm.org/D65856?vs=215065&id=215085#toc Repository: rL LLVM CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/trunk/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ clang-tools-extra/trunk/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,28 @@ ] ]; 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'); }); }); Index: clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ clang-tools-extra/trunk/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,39 @@ foreground: string; } +export class ThemeRuleMatcher { + // The rules for the theme. + private themeRules: TokenColorRule[]; + // A cache for the getBestThemeRule function. + private bestRuleCache: Map = 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:
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
hokein accepted this revision. hokein added a comment. This revision is now accepted and ready to land. thanks, looks good. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom updated this revision to Diff 215065. jvikstrom marked 4 inline comments as done. jvikstrom added a comment. Simplified matching code. Use real scopes for test. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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,28 @@ ] ]; 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'); }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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,39 @@ foreground: string; } +export class ThemeRuleMatcher { + // The rules for the theme. + private themeRules: TokenColorRule[]; + // A cache for the getBestThemeRule function. + private bestRuleCache: Map = 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 c
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
hokein added inline comments. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:128 + // Find the rule wich is the longest prefix of scope. + if (rule.scope.length <= scope.length && + scope.substr(0, rule.scope.length) === rule.scope && jvikstrom wrote: > hokein wrote: > > The algorithm doesn't seems correct to me, if scope.length > > > rule.scope.length, then we drop it. > > > > I think we should > > - calculate the common prefix between two scopes > > - update the bestRule if the length of common prefix is greater than the > > current best length > If the scope's length is less than the rule's length the rule can not be a > prefix. > (Not sure I fully follow with what you mean in the first sentence though) > > > If we check common prefixes we run into this weird case (this is taken from > the Light+ theme): > ``` > variable.css > variable.scss > variable.other.less > variable > ``` > With that kind of matching we have now this means that `variable.other.less` > will match `variable.other` and `variable.other.less` and the variables would > be colored as less variables while they should be `variable.other`. Same goes > for `variable.other.field`. > > And even if `variable.other.less` did not exist `variable.other` would still > match `variable.css` and now be highlighted as css variables. I thought that we are finding longest common prefix, but actually you are using a different one (I checked that "vscode-cpptools" is also using the same matching algorithm). That sounds fair enough, could you add this context to the comment? Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:115 + // The rules for the theme. + private rules: TokenColorRule[]; + // A cache for the getBestThemeRule function. I'd name it `themeRules`. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:133 + 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. I think we could simplify the code like ``` if (scope.startsWith(rule.scope) && rule.scope.length > bestRule.scope.length) { ... } ``` Comment at: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts:40 +const rules = [ + {scope : 'c.b', foreground : '1'}, + {scope : 'a', foreground : '2'}, Even for the test, I'd use the real scopes, e.g. `variable.css`, `variable.other.less`. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts:47 +const tm = new SM.ThemeRuleMatcher(rules); +assert.deepEqual(tm.getBestThemeRule('c.b'), rules[0]); +assert.deepEqual(tm.getBestThemeRule('a.b'), rules[2]); jvikstrom wrote: > hokein wrote: > > I'd use `assert.deepEqual(tm.getBestThemeRule('c.b').scope, 'c.b');` to > > make the code more readable. > > > For the `a` case we are interested in the foreground color as well. Should I > change the others and keep `assert.deepEqual(tm.getBestThemeRule('a'), > rules[1]);` as is or be consistent? `a` case is interesting here - we have duplicates, but actually we won't have duplicates in the theme rules (as we check the scope is being seen when parsing theme file). I think for `a`, just use `assert.deepEqual(tm.getBestThemeRule('a'), rules[1]); // we match the first element if there are duplicates`; for others, use the scope. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom updated this revision to Diff 214845. jvikstrom marked an inline comment as done. jvikstrom added a comment. Added fixme for ranking. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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]); }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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 specif
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom added inline comments. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:128 + // Find the rule wich is the longest prefix of scope. + if (rule.scope.length <= scope.length && + scope.substr(0, rule.scope.length) === rule.scope && hokein wrote: > The algorithm doesn't seems correct to me, if scope.length > > rule.scope.length, then we drop it. > > I think we should > - calculate the common prefix between two scopes > - update the bestRule if the length of common prefix is greater than the > current best length If the scope's length is less than the rule's length the rule can not be a prefix. (Not sure I fully follow with what you mean in the first sentence though) If we check common prefixes we run into this weird case (this is taken from the Light+ theme): ``` variable.css variable.scss variable.other.less variable ``` With that kind of matching we have now this means that `variable.other.less` will match `variable.other` and `variable.other.less` and the variables would be colored as less variables while they should be `variable.other`. Same goes for `variable.other.field`. And even if `variable.other.less` did not exist `variable.other` would still match `variable.css` and now be highlighted as css variables. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts:47 +const tm = new SM.ThemeRuleMatcher(rules); +assert.deepEqual(tm.getBestThemeRule('c.b'), rules[0]); +assert.deepEqual(tm.getBestThemeRule('a.b'), rules[2]); hokein wrote: > I'd use `assert.deepEqual(tm.getBestThemeRule('c.b').scope, 'c.b');` to make > the code more readable. > For the `a` case we are interested in the foreground color as well. Should I change the others and keep `assert.deepEqual(tm.getBestThemeRule('a'), rules[1]);` as is or be consistent? Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom updated this revision to Diff 214844. jvikstrom marked 4 inline comments as done. jvikstrom added a comment. Changed variable name. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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]); }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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,32 @@ 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). + 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 l
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
hokein added inline comments. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:131 +this.rules.forEach((rule) => { + if (rule.scope.length <= scope.length && + scope.substr(0, rule.scope.length) === rule.scope && jvikstrom wrote: > hokein wrote: > > hmm, here comes the question, we need algorithm to find the best match > > rule. not doing it in this patch is fine, please add a FIXME. > > > > could you also document what's the strategy using here? > But isn't the best match for a scope just the rule that is the longest prefix > of the scope (or a perfect match if one exists)? (as that should be the most > specific rule) that depends, given the fact that the theme scope maybe not exactly the same with the scope provided by clangd. The longest-prefix match may not work well on cases like `a.b..c.d` and `a.b.c.d`. But we don't know yet without further experiment, I'd add a FIXME for revisiting the strategy here. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:51 + // The rules for the current theme. + themeRules: ThemeRuleMatcher; fillClientCapabilities(capabilities: vscodelc.ClientCapabilities) { the variable name should be updated as well. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:119 + constructor(rules: TokenColorRule[]) { this.rules = rules; } + // Returns the best rule for a scope. The best rule for a scope is the rule + // that is the longest prefix of the scope (unless a perfect match exists in `The best rule for a scope is the rule ...` sounds like implementation details, should be moved to the function body. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:128 + // Find the rule wich is the longest prefix of scope. + if (rule.scope.length <= scope.length && + scope.substr(0, rule.scope.length) === rule.scope && The algorithm doesn't seems correct to me, if scope.length > rule.scope.length, then we drop it. I think we should - calculate the common prefix between two scopes - update the bestRule if the length of common prefix is greater than the current best length Comment at: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts:47 +const tm = new SM.ThemeRuleMatcher(rules); +assert.deepEqual(tm.getBestThemeRule('c.b'), rules[0]); +assert.deepEqual(tm.getBestThemeRule('a.b'), rules[2]); I'd use `assert.deepEqual(tm.getBestThemeRule('c.b').scope, 'c.b');` to make the code more readable. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom added inline comments. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:131 +this.rules.forEach((rule) => { + if (rule.scope.length <= scope.length && + scope.substr(0, rule.scope.length) === rule.scope && hokein wrote: > hmm, here comes the question, we need algorithm to find the best match rule. > not doing it in this patch is fine, please add a FIXME. > > could you also document what's the strategy using here? But isn't the best match for a scope just the rule that is the longest prefix of the scope (or a perfect match if one exists)? (as that should be the most specific rule) Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom updated this revision to Diff 214834. jvikstrom added a comment. Removed stray edits from loadTheme. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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]); }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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. + themeRules: ThemeRuleMatcher; fillClientCapabilities(capabilities: vscodelc.ClientCapabilities) { // Extend the ClientCapabilities type and add semantic highlighting // capability to the object. @@ -58,6 +60,12 @@ }; } + async loadCurrentTheme() { +this.themeRules = 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,32 @@ 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. 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). + getBestThemeRule(scope: string): TokenColorRule { +if (this.bestRuleCache.has(scope)) + return this.bestRuleCache.get(scope); +let bestRule: TokenColorRule = {scope : '', foreground : ''}; +this.rules.forEach((rule) => { + // Find the rule wich is the longest prefix of scope. + 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 l
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom updated this revision to Diff 214833. jvikstrom marked 4 inline comments as done. jvikstrom added a comment. Address comments. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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]); }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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. + themeRules: ThemeRuleMatcher; fillClientCapabilities(capabilities: vscodelc.ClientCapabilities) { // Extend the ClientCapabilities type and add semantic highlighting // capability to the object. @@ -58,6 +60,12 @@ }; } + async loadCurrentTheme() { +this.themeRules = 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,8 +110,34 @@ 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. 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). + getBestThemeRule(scope: string): TokenColorRule { +if (this.bestRuleCache.has(scope)) + return this.bestRuleCache.get(scope); +let bestRule: TokenColorRule = {scope : '', foreground : ''}; +this.rules.forEach((rule) => { + // Find the rule wich is the longest prefix of scope. + 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
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
hokein added inline comments. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:65 +const name = +vscode.workspace.getConfiguration('workbench').get('colorTheme'); +if (typeof name != 'string') { maybe just `get`, and get rid of the following check. Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:119 +export class ThemeRules { + // The rules for the theme. I'd name it `ThemeRuleMatcher`? Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:131 +this.rules.forEach((rule) => { + if (rule.scope.length <= scope.length && + scope.substr(0, rule.scope.length) === rule.scope && hmm, here comes the question, we need algorithm to find the best match rule. not doing it in this patch is fine, please add a FIXME. could you also document what's the strategy using here? Comment at: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts:143 // Get all token color rules provided by the theme. -function loadTheme(themeName: string): Promise { +async function loadTheme(themeName: string): Promise { const extension = nit: no need to change this method, you could construct the `ThemeRules` from the returned results. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom updated this revision to Diff 214814. jvikstrom added a comment. Added a missing test case. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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.ThemeRules(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]); }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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. + themeRules: ThemeRules; fillClientCapabilities(capabilities: vscodelc.ClientCapabilities) { // Extend the ClientCapabilities type and add semantic highlighting // capability to the object. @@ -58,6 +60,18 @@ }; } + async loadCurrentTheme() { +const name = +vscode.workspace.getConfiguration('workbench').get('colorTheme'); +if (typeof name != 'string') { + console.warn('The current theme name is not a string, is:' + + (typeof name) + ', value: ', + name); + return; +} +this.themeRules = await loadTheme(name); + } + initialize(capabilities: vscodelc.ServerCapabilities, documentSelector: vscodelc.DocumentSelector|undefined) { // The semantic highlighting capability information is in the capabilities @@ -68,6 +82,7 @@ if (!serverCapabilities.semanticHighlighting) return; this.scopeLookupTable = serverCapabilities.semanticHighlighting.scopes; +this.loadCurrentTheme(); } handleNotification(params: SemanticHighlightingParams) {} @@ -101,8 +116,31 @@ foreground: string; } +export class ThemeRules { + // 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) => { + 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 { +async function loadTheme(themeName: st
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom updated this revision to Diff 214813. jvikstrom added a comment. Lazy load the best theme rule for a scope. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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,20 @@ ] ]; 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.ThemeRules(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]); }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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. + themeRules: ThemeRules; fillClientCapabilities(capabilities: vscodelc.ClientCapabilities) { // Extend the ClientCapabilities type and add semantic highlighting // capability to the object. @@ -58,6 +60,18 @@ }; } + async loadCurrentTheme() { +const name = +vscode.workspace.getConfiguration('workbench').get('colorTheme'); +if (typeof name != 'string') { + console.warn('The current theme name is not a string, is:' + + (typeof name) + ', value: ', + name); + return; +} +this.themeRules = await loadTheme(name); + } + initialize(capabilities: vscodelc.ServerCapabilities, documentSelector: vscodelc.DocumentSelector|undefined) { // The semantic highlighting capability information is in the capabilities @@ -68,6 +82,7 @@ if (!serverCapabilities.semanticHighlighting) return; this.scopeLookupTable = serverCapabilities.semanticHighlighting.scopes; +this.loadCurrentTheme(); } handleNotification(params: SemanticHighlightingParams) {} @@ -101,8 +116,31 @@ foreground: string; } +export class ThemeRules { + // 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) => { + 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 { +async function loadTheme(themeName: string): Promise { const extension = vscode
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
hokein added a comment. I think we could make the layering clearer: - we now have a list of theme color rules, and scope names provided by clangd; and we we want to find the best match theme rule for a particular clangd scope; - we could define a function like `getBestThemeRule(clangd_scope_name: string)` - when we render a token, we get the index of the lookup table, find the exact clangd scope name, and call the function to get the corresponding theme rule; - we need to cache the result to avoid re-computing, we could make the function as a class method; Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom updated this revision to Diff 214779. jvikstrom added a comment. Updated patch description. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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 scopes = [ [ 'a.b', 'a.b' ], [ 'a', 'a.b', 'c.b.a' ], [ 'c.b.a' ] ]; +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.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[4]); }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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 scopes given the current theme. + scopeRules: ScopeRules; fillClientCapabilities(capabilities: vscodelc.ClientCapabilities) { // Extend the ClientCapabilities type and add semantic highlighting // capability to the object. @@ -58,6 +60,18 @@ }; } + async loadCurrentTheme() { +const name = +vscode.workspace.getConfiguration('workbench').get('colorTheme'); +if (typeof name != 'string') { + console.warn('The current theme name is not a string, is:' + + (typeof name) + ', value: ', + name); + return; +} +this.scopeRules = await loadTheme(name, this.scopeLookupTable); + } + initialize(capabilities: vscodelc.ServerCapabilities, documentSelector: vscodelc.DocumentSelector|undefined) { // The semantic highlighting capability information is in the capabilities @@ -68,6 +82,7 @@ if (!serverCapabilities.semanticHighlighting) return; this.scopeLookupTable = serverCapabilities.semanticHighlighting.scopes; +this.loadCurrentTheme(); } handleNotification(params: SemanticHighlightingParams) {} @@ -101,8 +116,55 @@ foreground: string; } +export class ScopeRules { + // The TextMate scopes that should be mapped to a color. + private scopeLookupTable: string[][]; + // Contains the current best matching scope for the scope at the corresponding + // index. + private scopeRules: TokenColorRule[]; + // Each list of scopes can only have one rule matching. The actual scope that + // matches for each index should be as close to zero as possible. + private currentScopeRuleIdx: number[]; + constructor(scopeLookupTable: string[][]) { +this.scopeLookupTable = scopeLookupTable; +this.currentScopeRuleIdx = +this.scopeLookupTable.map(() => Number.POSITIVE_INFINITY); +this.scopeRules = +this.scopeLookupTable.map(() => ({scope : '', foreground : '#000'})); + } + + // Associates rule to the scopeLookupTable if possible. For a rule to be + // associated to an array of scopes it must be a prefix of any of the scopes. + // Be the earliest match in the array of scopes and be the most specific + // match. + addRule(rule: Token
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom updated this revision to Diff 214778. jvikstrom added a comment. Updated to use string[][] as scopes. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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 scopes = [ [ 'a.b', 'a.b' ], [ 'a', 'a.b', 'c.b.a' ], [ 'c.b.a' ] ]; +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.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[4]); }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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 scopes given the current theme. + scopeRules: ScopeRules; fillClientCapabilities(capabilities: vscodelc.ClientCapabilities) { // Extend the ClientCapabilities type and add semantic highlighting // capability to the object. @@ -58,6 +60,18 @@ }; } + async loadCurrentTheme() { +const name = +vscode.workspace.getConfiguration('workbench').get('colorTheme'); +if (typeof name != 'string') { + console.warn('The current theme name is not a string, is:' + + (typeof name) + ', value: ', + name); + return; +} +this.scopeRules = await loadTheme(name, this.scopeLookupTable); + } + initialize(capabilities: vscodelc.ServerCapabilities, documentSelector: vscodelc.DocumentSelector|undefined) { // The semantic highlighting capability information is in the capabilities @@ -68,6 +82,7 @@ if (!serverCapabilities.semanticHighlighting) return; this.scopeLookupTable = serverCapabilities.semanticHighlighting.scopes; +this.loadCurrentTheme(); } handleNotification(params: SemanticHighlightingParams) {} @@ -101,8 +116,55 @@ foreground: string; } +export class ScopeRules { + // The TextMate scopes that should be mapped to a color. + private scopeLookupTable: string[][]; + // Contains the current best matching scope for the scope at the corresponding + // index. + private scopeRules: TokenColorRule[]; + // Each list of scopes can only have one rule matching. The actual scope that + // matches for each index should be as close to zero as possible. + private currentScopeRuleIdx: number[]; + constructor(scopeLookupTable: string[][]) { +this.scopeLookupTable = scopeLookupTable; +this.currentScopeRuleIdx = +this.scopeLookupTable.map(() => Number.POSITIVE_INFINITY); +this.scopeRules = +this.scopeLookupTable.map(() => ({scope : '', foreground : '#000'})); + } + + // Associates rule to the scopeLookupTable if possible. For a rule to be + // associated to an array of scopes it must be a prefix of any of the scopes. + // Be the earliest match in the array of scopes and be the most specific + // match. + addRule(r
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom updated this revision to Diff 214774. jvikstrom added a comment. Rebased into master. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D65856/new/ https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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]); + }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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 = Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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]); + }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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(rul
[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope
jvikstrom created this revision. jvikstrom added reviewers: hokein, ilya-biryukov. Herald added subscribers: cfe-commits, kadircet, arphaman, jkorous, MaskRay. Herald added a project: clang. Maps an array of TM scopes to the most specific scope that is added. Needed to have fast access to a rule when doing the actual coloring. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D65856 Files: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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]); }); }); Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ 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 = Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts === --- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ 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', foregro