[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-14 Thread Johan Vikström via Phabricator via cfe-commits
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=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

2019-08-14 Thread Haojian Wu via Phabricator via cfe-commits
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

2019-08-14 Thread Johan Vikström via Phabricator via cfe-commits
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 

[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-14 Thread Haojian Wu via Phabricator via cfe-commits
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

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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 

[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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 

[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-13 Thread Haojian Wu via Phabricator via cfe-commits
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

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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 

[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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 

[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-13 Thread Haojian Wu via Phabricator via cfe-commits
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

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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: 

[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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 =
   

[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-13 Thread Haojian Wu via Phabricator via cfe-commits
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

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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: 

[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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.
+  

[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-13 Thread Johan Vikström via Phabricator via cfe-commits
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'}));
+  }
+
+  

[PATCH] D65856: [clangd] Added class for mapping TokenColorRules to their associated clangd TextMate scope

2019-08-07 Thread Johan Vikström via Phabricator via cfe-commits
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',