-
Notifications
You must be signed in to change notification settings - Fork 9
/
main.ts
276 lines (244 loc) · 9 KB
/
main.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { SyncRegistry } from './registry';
import { parseJSONGrammar, parsePLISTGrammar } from './grammarReader';
import { Theme } from './theme';
import { StackElement as StackElementImpl } from './grammar';
import { IGrammarDefinition, IRawGrammar } from './types';
export { IGrammarDefinition, IRawGrammar }
let DEFAULT_OPTIONS: RegistryOptions = {
getGrammarDefinition: (scopeName: string) => null,
getInjections: (scopeName: string) => null
};
/**
* A single theme setting.
*/
export interface IRawThemeSetting {
readonly name?: string;
readonly scope?: string | string[];
readonly settings: {
readonly fontStyle?: string;
readonly foreground?: string;
readonly background?: string;
};
}
/**
* A TextMate theme.
*/
export interface IRawTheme {
readonly name?: string;
readonly settings: IRawThemeSetting[];
}
/**
* A registry helper that can locate grammar file paths given scope names.
*/
export interface RegistryOptions {
theme?: IRawTheme;
getGrammarDefinition(scopeName: string, dependentScope: string): Promise<IGrammarDefinition>;
getInjections?(scopeName: string): string[];
}
/**
* A map from scope name to a language id. Please do not use language id 0.
*/
export interface IEmbeddedLanguagesMap {
[scopeName: string]: number;
}
/**
* A map from selectors to token types.
*/
export interface ITokenTypeMap {
[selector: string]: StandardTokenType;
}
export const enum StandardTokenType {
Other = 0,
Comment = 1,
String = 2,
RegEx = 4
}
export interface IGrammarConfiguration {
embeddedLanguages?: IEmbeddedLanguagesMap;
tokenTypes?: ITokenTypeMap;
}
/**
* The registry that will hold all grammars.
*/
export class Registry {
private readonly _locator: RegistryOptions;
private readonly _syncRegistry: SyncRegistry;
private readonly installationQueue: Map<string, Promise<IGrammar>>;
constructor(locator: RegistryOptions = DEFAULT_OPTIONS) {
this._locator = locator;
this._syncRegistry = new SyncRegistry(Theme.createFromRawTheme(locator.theme));
this.installationQueue = new Map();
}
/**
* Change the theme. Once called, no previous `ruleStack` should be used anymore.
*/
public setTheme(theme: IRawTheme): void {
this._syncRegistry.setTheme(Theme.createFromRawTheme(theme));
}
/**
* Returns a lookup array for color ids.
*/
public getColorMap(): string[] {
return this._syncRegistry.getColorMap();
}
/**
* Load the grammar for `scopeName` and all referenced included grammars asynchronously.
* Please do not use language id 0.
*/
public loadGrammarWithEmbeddedLanguages(initialScopeName: string, initialLanguage: number, embeddedLanguages: IEmbeddedLanguagesMap): Promise<IGrammar> {
return this.loadGrammarWithConfiguration(initialScopeName, initialLanguage, { embeddedLanguages });
}
/**
* Load the grammar for `scopeName` and all referenced included grammars asynchronously.
* Please do not use language id 0.
*/
public async loadGrammarWithConfiguration(initialScopeName: string, initialLanguage: number, configuration: IGrammarConfiguration): Promise<IGrammar> {
await this._loadGrammar(initialScopeName);
return this.grammarForScopeName(initialScopeName, initialLanguage, configuration.embeddedLanguages, configuration.tokenTypes);
}
/**
* Load the grammar for `scopeName` and all referenced included grammars asynchronously.
*/
public async loadGrammar(initialScopeName: string): Promise<IGrammar> {
return this._loadGrammar(initialScopeName);
}
private async _loadGrammar(initialScopeName: string, dependentScope: string = null): Promise<IGrammar> {
// already installed
if (this._syncRegistry.lookup(initialScopeName)) {
return this.grammarForScopeName(initialScopeName);
}
// installation in progress
if (this.installationQueue.has(initialScopeName)) {
return this.installationQueue.get(initialScopeName);
}
// start installation process
const prom = new Promise<IGrammar>(async (resolve, reject) => {
let grammarDefinition = await this._locator.getGrammarDefinition(initialScopeName, dependentScope);
if (!grammarDefinition) {
throw new Error(`A tmGrammar load was requested but registry host failed to provide grammar definition`);
}
if ((grammarDefinition.format !== 'json' && grammarDefinition.format !== 'plist') ||
(grammarDefinition.format === 'json' && typeof grammarDefinition.content !== 'object' && typeof grammarDefinition.content !== 'string') ||
(grammarDefinition.format === 'plist' && typeof grammarDefinition.content !== 'string')
) {
throw new TypeError('Grammar definition must be an object, either `{ content: string | object, format: "json" }` OR `{ content: string, format: "plist" }`)');
}
const rawGrammar: IRawGrammar = grammarDefinition.format === 'json'
? typeof grammarDefinition.content === 'string'
? parseJSONGrammar(grammarDefinition.content, 'c://fakepath/grammar.json')
: grammarDefinition.content as IRawGrammar
: parsePLISTGrammar(grammarDefinition.content as string, 'c://fakepath/grammar.plist');
let injections = (typeof this._locator.getInjections === 'function') && this._locator.getInjections(initialScopeName);
(rawGrammar as any).scopeName = initialScopeName;
let deps = this._syncRegistry.addGrammar(rawGrammar, injections);
await Promise.all(deps.map(async (scopeNameD) => {
try {
return this._loadGrammar(scopeNameD, initialScopeName);
} catch (error) {
throw new Error(`While trying to load tmGrammar with scopeId: '${initialScopeName}', it's dependency (scopeId: ${scopeNameD}) loading errored: ${error.message}`);
}
}));
resolve(this.grammarForScopeName(initialScopeName));
});
this.installationQueue.set(initialScopeName, prom);
await prom;
this.installationQueue.delete(initialScopeName);
return prom;
}
/**
* Get the grammar for `scopeName`. The grammar must first be created via `loadGrammar` or `loadGrammarFromPathSync`.
*/
public grammarForScopeName(scopeName: string, initialLanguage: number = 0, embeddedLanguages: IEmbeddedLanguagesMap = null, tokenTypes: ITokenTypeMap = null): IGrammar {
return this._syncRegistry.grammarForScopeName(scopeName, initialLanguage, embeddedLanguages, tokenTypes);
}
}
/**
* A grammar
*/
export interface IGrammar {
/**
* Tokenize `lineText` using previous line state `prevState`.
*/
tokenizeLine(lineText: string, prevState: StackElement): ITokenizeLineResult;
/**
* Tokenize `lineText` using previous line state `prevState`.
* The result contains the tokens in binary format, resolved with the following information:
* - language
* - token type (regex, string, comment, other)
* - font style
* - foreground color
* - background color
* e.g. for getting the languageId: `(metadata & MetadataConsts.LANGUAGEID_MASK) >>> MetadataConsts.LANGUAGEID_OFFSET`
*/
tokenizeLine2(lineText: string, prevState: StackElement): ITokenizeLineResult2;
}
export interface ITokenizeLineResult {
readonly tokens: IToken[];
/**
* The `prevState` to be passed on to the next line tokenization.
*/
readonly ruleStack: StackElement;
}
/**
* Helpers to manage the "collapsed" metadata of an entire StackElement stack.
* The following assumptions have been made:
* - languageId < 256 => needs 8 bits
* - unique color count < 512 => needs 9 bits
*
* The binary format is:
* - -------------------------------------------
* 3322 2222 2222 1111 1111 1100 0000 0000
* 1098 7654 3210 9876 5432 1098 7654 3210
* - -------------------------------------------
* xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
* bbbb bbbb bfff ffff ffFF FTTT LLLL LLLL
* - -------------------------------------------
* - L = LanguageId (8 bits)
* - T = StandardTokenType (3 bits)
* - F = FontStyle (3 bits)
* - f = foreground color (9 bits)
* - b = background color (9 bits)
*/
export const enum MetadataConsts {
LANGUAGEID_MASK = 0b00000000000000000000000011111111,
TOKEN_TYPE_MASK = 0b00000000000000000000011100000000,
FONT_STYLE_MASK = 0b00000000000000000011100000000000,
FOREGROUND_MASK = 0b00000000011111111100000000000000,
BACKGROUND_MASK = 0b11111111100000000000000000000000,
LANGUAGEID_OFFSET = 0,
TOKEN_TYPE_OFFSET = 8,
FONT_STYLE_OFFSET = 11,
FOREGROUND_OFFSET = 14,
BACKGROUND_OFFSET = 23
}
export interface ITokenizeLineResult2 {
/**
* The tokens in binary format. Each token occupies two array indices. For token i:
* - at offset 2*i => startIndex
* - at offset 2*i + 1 => metadata
*
*/
readonly tokens: Uint32Array;
/**
* The `prevState` to be passed on to the next line tokenization.
*/
readonly ruleStack: StackElement;
}
export interface IToken {
startIndex: number;
readonly endIndex: number;
readonly scopes: string[];
}
/**
* **IMPORTANT** - Immutable!
*/
export interface StackElement {
_stackElementBrand: void;
readonly depth: number;
clone(): StackElement;
equals(other: StackElement): boolean;
}
export const INITIAL: StackElement = StackElementImpl.NULL;