Skip to content

Commit 3d84119

Browse files
committed
Introduce TMScopeRegistry (microsoft#14136)
1 parent f070c3a commit 3d84119

2 files changed

Lines changed: 113 additions & 1 deletion

File tree

src/vs/editor/node/textMate/TMSyntax.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import * as nls from 'vs/nls';
88
import { onUnexpectedError } from 'vs/base/common/errors';
99
import * as paths from 'vs/base/common/paths';
10+
import * as strings from 'vs/base/common/strings';
1011
import { IExtensionMessageCollector, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry';
1112
import { ILineTokens, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/modes';
1213
import { TMState } from 'vs/editor/common/modes/TMState';
@@ -56,6 +57,60 @@ let grammarsExtPoint = ExtensionsRegistry.registerExtensionPoint<ITMSyntaxExtens
5657
}
5758
});
5859

60+
export class TMScopeRegistry {
61+
62+
private _scopeNameToFilePath: { [scopeName: string]: string; };
63+
private _scopeNameToLanguage: { [scopeName: string]: string; };
64+
private _cachedScopeRegex: RegExp;
65+
66+
constructor() {
67+
this._scopeNameToFilePath = Object.create(null);
68+
this._scopeNameToLanguage = Object.create(null);
69+
this._cachedScopeRegex = null;
70+
}
71+
72+
public register(language: string, scopeName: string, filePath: string): void {
73+
this._scopeNameToFilePath[scopeName] = filePath;
74+
this._scopeNameToLanguage[scopeName] = language;
75+
this._cachedScopeRegex = null;
76+
}
77+
78+
public getFilePath(scopeName: string): string {
79+
return this._scopeNameToFilePath[scopeName] || null;
80+
}
81+
82+
private _getScopeRegex(): RegExp {
83+
if (!this._cachedScopeRegex) {
84+
let escapedScopes = Object.keys(this._scopeNameToLanguage).map((scopeName) => strings.escapeRegExpCharacters(scopeName));
85+
if (escapedScopes.length === 0) {
86+
// no scopes registered
87+
return null;
88+
}
89+
this._cachedScopeRegex = new RegExp(`^((${escapedScopes.join(')|(')}))`, '');
90+
}
91+
return this._cachedScopeRegex;
92+
}
93+
94+
/**
95+
* Given a produced TM scope, return the language that token describes or null if unknown.
96+
* e.g. source.html => html, source.css.embedded.html => css, punctuation.definition.tag.html => null
97+
*/
98+
public scopeToLanguage(scope: string): string {
99+
let regex = this._getScopeRegex();
100+
if (!regex) {
101+
// no scopes registered
102+
return null;
103+
}
104+
let m = scope.match(regex);
105+
if (!m) {
106+
// no scopes matched
107+
return null;
108+
}
109+
110+
return this._scopeNameToLanguage[m[1]] || null;
111+
}
112+
}
113+
59114
export class MainProcessTextMateSyntax {
60115
private _grammarRegistry: Registry;
61116
private _modeService: IModeService;

src/vs/editor/test/node/textMate/TMSyntax.test.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,64 @@
55
'use strict';
66

77
import * as assert from 'assert';
8-
import { decodeTextMateToken, DecodeMap } from 'vs/editor/node/textMate/TMSyntax';
8+
import { decodeTextMateToken, DecodeMap, TMScopeRegistry } from 'vs/editor/node/textMate/TMSyntax';
9+
10+
suite('TextMate.TMScopeRegistry', () => {
11+
12+
test('getFilePath', () => {
13+
let manager = new TMScopeRegistry();
14+
15+
manager.register('a', 'source.a', './grammar/a.tmLanguage');
16+
assert.equal(manager.getFilePath('source.a'), './grammar/a.tmLanguage');
17+
assert.equal(manager.getFilePath('a'), null);
18+
assert.equal(manager.getFilePath('source.b'), null);
19+
assert.equal(manager.getFilePath('b'), null);
20+
21+
manager.register('b', 'source.b', './grammar/b.tmLanguage');
22+
assert.equal(manager.getFilePath('source.a'), './grammar/a.tmLanguage');
23+
assert.equal(manager.getFilePath('a'), null);
24+
assert.equal(manager.getFilePath('source.b'), './grammar/b.tmLanguage');
25+
assert.equal(manager.getFilePath('b'), null);
26+
27+
manager.register('a', 'source.a', './grammar/ax.tmLanguage');
28+
assert.equal(manager.getFilePath('source.a'), './grammar/ax.tmLanguage');
29+
assert.equal(manager.getFilePath('a'), null);
30+
assert.equal(manager.getFilePath('source.b'), './grammar/b.tmLanguage');
31+
assert.equal(manager.getFilePath('b'), null);
32+
});
33+
34+
test('scopeToLanguage', () => {
35+
let manager = new TMScopeRegistry();
36+
37+
assert.equal(manager.scopeToLanguage('source.html'), null);
38+
39+
manager.register('html', 'source.html', null);
40+
manager.register('css', 'source.css', null);
41+
manager.register('javascript', 'source.js', null);
42+
manager.register('python', 'source.python', null);
43+
manager.register('smarty', 'source.smarty', null);
44+
45+
// exact matches
46+
assert.equal(manager.scopeToLanguage('source.html'), 'html');
47+
assert.equal(manager.scopeToLanguage('source.css'), 'css');
48+
assert.equal(manager.scopeToLanguage('source.js'), 'javascript');
49+
assert.equal(manager.scopeToLanguage('source.python'), 'python');
50+
assert.equal(manager.scopeToLanguage('source.smarty'), 'smarty');
51+
52+
// prefix matches
53+
assert.equal(manager.scopeToLanguage('source.css.embedded.html'), 'css');
54+
assert.equal(manager.scopeToLanguage('source.js.embedded.html'), 'javascript');
55+
assert.equal(manager.scopeToLanguage('source.python.embedded.html'), 'python');
56+
assert.equal(manager.scopeToLanguage('source.smarty.embedded.html'), 'smarty');
57+
58+
// misses
59+
assert.equal(manager.scopeToLanguage('source.ts'), null);
60+
assert.equal(manager.scopeToLanguage('asource.css'), null);
61+
assert.equal(manager.scopeToLanguage('a.source.css'), null);
62+
assert.equal(manager.scopeToLanguage('source_css'), null);
63+
});
64+
65+
});
966

1067
suite('textMate', () => {
1168

0 commit comments

Comments
 (0)