Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/languages/languageConfiguration.ts
3294 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CharCode } from '../../../base/common/charCode.js';
7
import { StandardTokenType } from '../encodedTokenAttributes.js';
8
import { ScopedLineTokens } from './supports.js';
9
10
/**
11
* Configuration for line comments.
12
*/
13
export interface LineCommentConfig {
14
/**
15
* The line comment token, like `//`
16
*/
17
comment: string;
18
/**
19
* Whether the comment token should not be indented and placed at the first column.
20
* Defaults to false.
21
*/
22
noIndent?: boolean;
23
}
24
25
/**
26
* Describes how comments for a language work.
27
*/
28
export interface CommentRule {
29
/**
30
* The line comment token, like `// this is a comment`.
31
* Can be a string or an object with comment and optional noIndent properties.
32
*/
33
lineComment?: string | LineCommentConfig | null;
34
/**
35
* The block comment character pair, like `/* block comment */`
36
*/
37
blockComment?: CharacterPair | null;
38
}
39
40
/**
41
* The language configuration interface defines the contract between extensions and
42
* various editor features, like automatic bracket insertion, automatic indentation etc.
43
*/
44
export interface LanguageConfiguration {
45
/**
46
* The language's comment settings.
47
*/
48
comments?: CommentRule;
49
/**
50
* The language's brackets.
51
* This configuration implicitly affects pressing Enter around these brackets.
52
*/
53
brackets?: CharacterPair[];
54
/**
55
* The language's word definition.
56
* If the language supports Unicode identifiers (e.g. JavaScript), it is preferable
57
* to provide a word definition that uses exclusion of known separators.
58
* e.g.: A regex that matches anything except known separators (and dot is allowed to occur in a floating point number):
59
* /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g
60
*/
61
wordPattern?: RegExp;
62
/**
63
* The language's indentation settings.
64
*/
65
indentationRules?: IndentationRule;
66
/**
67
* The language's rules to be evaluated when pressing Enter.
68
*/
69
onEnterRules?: OnEnterRule[];
70
/**
71
* The language's auto closing pairs. The 'close' character is automatically inserted with the
72
* 'open' character is typed. If not set, the configured brackets will be used.
73
*/
74
autoClosingPairs?: IAutoClosingPairConditional[];
75
/**
76
* The language's surrounding pairs. When the 'open' character is typed on a selection, the
77
* selected string is surrounded by the open and close characters. If not set, the autoclosing pairs
78
* settings will be used.
79
*/
80
surroundingPairs?: IAutoClosingPair[];
81
/**
82
* Defines a list of bracket pairs that are colorized depending on their nesting level.
83
* If not set, the configured brackets will be used.
84
*/
85
colorizedBracketPairs?: CharacterPair[];
86
/**
87
* Defines what characters must be after the cursor for bracket or quote autoclosing to occur when using the \'languageDefined\' autoclosing setting.
88
*
89
* This is typically the set of characters which can not start an expression, such as whitespace, closing brackets, non-unary operators, etc.
90
*/
91
autoCloseBefore?: string;
92
93
/**
94
* The language's folding rules.
95
*/
96
folding?: FoldingRules;
97
98
/**
99
* **Deprecated** Do not use.
100
*
101
* @deprecated Will be replaced by a better API soon.
102
*/
103
__electricCharacterSupport?: {
104
docComment?: IDocComment;
105
};
106
}
107
108
/**
109
* @internal
110
*/
111
type OrUndefined<T> = { [P in keyof T]: T[P] | undefined };
112
113
/**
114
* @internal
115
*/
116
export type ExplicitLanguageConfiguration = OrUndefined<Required<LanguageConfiguration>>;
117
118
/**
119
* Describes indentation rules for a language.
120
*/
121
export interface IndentationRule {
122
/**
123
* If a line matches this pattern, then all the lines after it should be unindented once (until another rule matches).
124
*/
125
decreaseIndentPattern: RegExp;
126
/**
127
* If a line matches this pattern, then all the lines after it should be indented once (until another rule matches).
128
*/
129
increaseIndentPattern: RegExp;
130
/**
131
* If a line matches this pattern, then **only the next line** after it should be indented once.
132
*/
133
indentNextLinePattern?: RegExp | null;
134
/**
135
* If a line matches this pattern, then its indentation should not be changed and it should not be evaluated against the other rules.
136
*/
137
unIndentedLinePattern?: RegExp | null;
138
139
}
140
141
/**
142
* Describes language specific folding markers such as '#region' and '#endregion'.
143
* The start and end regexes will be tested against the contents of all lines and must be designed efficiently:
144
* - the regex should start with '^'
145
* - regexp flags (i, g) are ignored
146
*/
147
export interface FoldingMarkers {
148
start: RegExp;
149
end: RegExp;
150
}
151
152
/**
153
* Describes folding rules for a language.
154
*/
155
export interface FoldingRules {
156
/**
157
* Used by the indentation based strategy to decide whether empty lines belong to the previous or the next block.
158
* A language adheres to the off-side rule if blocks in that language are expressed by their indentation.
159
* See [wikipedia](https://en.wikipedia.org/wiki/Off-side_rule) for more information.
160
* If not set, `false` is used and empty lines belong to the previous block.
161
*/
162
offSide?: boolean;
163
164
/**
165
* Region markers used by the language.
166
*/
167
markers?: FoldingMarkers;
168
}
169
170
/**
171
* Describes a rule to be evaluated when pressing Enter.
172
*/
173
export interface OnEnterRule {
174
/**
175
* This rule will only execute if the text before the cursor matches this regular expression.
176
*/
177
beforeText: RegExp;
178
/**
179
* This rule will only execute if the text after the cursor matches this regular expression.
180
*/
181
afterText?: RegExp;
182
/**
183
* This rule will only execute if the text above the this line matches this regular expression.
184
*/
185
previousLineText?: RegExp;
186
/**
187
* The action to execute.
188
*/
189
action: EnterAction;
190
}
191
192
/**
193
* Definition of documentation comments (e.g. Javadoc/JSdoc)
194
*/
195
export interface IDocComment {
196
/**
197
* The string that starts a doc comment (e.g. '/**')
198
*/
199
open: string;
200
/**
201
* The string that appears on the last line and closes the doc comment (e.g. ' * /').
202
*/
203
close?: string;
204
}
205
206
/**
207
* A tuple of two characters, like a pair of
208
* opening and closing brackets.
209
*/
210
export type CharacterPair = [string, string];
211
212
export interface IAutoClosingPair {
213
open: string;
214
close: string;
215
}
216
217
export interface IAutoClosingPairConditional extends IAutoClosingPair {
218
notIn?: string[];
219
}
220
221
/**
222
* Describes what to do with the indentation when pressing Enter.
223
*/
224
export enum IndentAction {
225
/**
226
* Insert new line and copy the previous line's indentation.
227
*/
228
None = 0,
229
/**
230
* Insert new line and indent once (relative to the previous line's indentation).
231
*/
232
Indent = 1,
233
/**
234
* Insert two new lines:
235
* - the first one indented which will hold the cursor
236
* - the second one at the same indentation level
237
*/
238
IndentOutdent = 2,
239
/**
240
* Insert new line and outdent once (relative to the previous line's indentation).
241
*/
242
Outdent = 3
243
}
244
245
/**
246
* Describes what to do when pressing Enter.
247
*/
248
export interface EnterAction {
249
/**
250
* Describe what to do with the indentation.
251
*/
252
indentAction: IndentAction;
253
/**
254
* Describes text to be appended after the new line and after the indentation.
255
*/
256
appendText?: string;
257
/**
258
* Describes the number of characters to remove from the new line's indentation.
259
*/
260
removeText?: number;
261
}
262
263
/**
264
* @internal
265
*/
266
export interface CompleteEnterAction {
267
/**
268
* Describe what to do with the indentation.
269
*/
270
indentAction: IndentAction;
271
/**
272
* Describes text to be appended after the new line and after the indentation.
273
*/
274
appendText: string;
275
/**
276
* Describes the number of characters to remove from the new line's indentation.
277
*/
278
removeText: number;
279
/**
280
* The line's indentation minus removeText
281
*/
282
indentation: string;
283
}
284
285
/**
286
* @internal
287
*/
288
export class StandardAutoClosingPairConditional {
289
290
readonly open: string;
291
readonly close: string;
292
private readonly _inString: boolean;
293
private readonly _inComment: boolean;
294
private readonly _inRegEx: boolean;
295
private _neutralCharacter: string | null = null;
296
private _neutralCharacterSearched: boolean = false;
297
298
constructor(source: IAutoClosingPairConditional) {
299
this.open = source.open;
300
this.close = source.close;
301
302
// initially allowed in all tokens
303
this._inString = true;
304
this._inComment = true;
305
this._inRegEx = true;
306
307
if (Array.isArray(source.notIn)) {
308
for (let i = 0, len = source.notIn.length; i < len; i++) {
309
const notIn: string = source.notIn[i];
310
switch (notIn) {
311
case 'string':
312
this._inString = false;
313
break;
314
case 'comment':
315
this._inComment = false;
316
break;
317
case 'regex':
318
this._inRegEx = false;
319
break;
320
}
321
}
322
}
323
}
324
325
public isOK(standardToken: StandardTokenType): boolean {
326
switch (standardToken) {
327
case StandardTokenType.Other:
328
return true;
329
case StandardTokenType.Comment:
330
return this._inComment;
331
case StandardTokenType.String:
332
return this._inString;
333
case StandardTokenType.RegEx:
334
return this._inRegEx;
335
}
336
}
337
338
public shouldAutoClose(context: ScopedLineTokens, column: number): boolean {
339
// Always complete on empty line
340
if (context.getTokenCount() === 0) {
341
return true;
342
}
343
344
const tokenIndex = context.findTokenIndexAtOffset(column - 2);
345
const standardTokenType = context.getStandardTokenType(tokenIndex);
346
return this.isOK(standardTokenType);
347
}
348
349
private _findNeutralCharacterInRange(fromCharCode: number, toCharCode: number): string | null {
350
for (let charCode = fromCharCode; charCode <= toCharCode; charCode++) {
351
const character = String.fromCharCode(charCode);
352
if (!this.open.includes(character) && !this.close.includes(character)) {
353
return character;
354
}
355
}
356
return null;
357
}
358
359
/**
360
* Find a character in the range [0-9a-zA-Z] that does not appear in the open or close
361
*/
362
public findNeutralCharacter(): string | null {
363
if (!this._neutralCharacterSearched) {
364
this._neutralCharacterSearched = true;
365
if (!this._neutralCharacter) {
366
this._neutralCharacter = this._findNeutralCharacterInRange(CharCode.Digit0, CharCode.Digit9);
367
}
368
if (!this._neutralCharacter) {
369
this._neutralCharacter = this._findNeutralCharacterInRange(CharCode.a, CharCode.z);
370
}
371
if (!this._neutralCharacter) {
372
this._neutralCharacter = this._findNeutralCharacterInRange(CharCode.A, CharCode.Z);
373
}
374
}
375
return this._neutralCharacter;
376
}
377
}
378
379
/**
380
* @internal
381
*/
382
export class AutoClosingPairs {
383
// it is useful to be able to get pairs using either end of open and close
384
385
/** Key is first character of open */
386
public readonly autoClosingPairsOpenByStart: Map<string, StandardAutoClosingPairConditional[]>;
387
/** Key is last character of open */
388
public readonly autoClosingPairsOpenByEnd: Map<string, StandardAutoClosingPairConditional[]>;
389
/** Key is first character of close */
390
public readonly autoClosingPairsCloseByStart: Map<string, StandardAutoClosingPairConditional[]>;
391
/** Key is last character of close */
392
public readonly autoClosingPairsCloseByEnd: Map<string, StandardAutoClosingPairConditional[]>;
393
/** Key is close. Only has pairs that are a single character */
394
public readonly autoClosingPairsCloseSingleChar: Map<string, StandardAutoClosingPairConditional[]>;
395
396
constructor(autoClosingPairs: StandardAutoClosingPairConditional[]) {
397
this.autoClosingPairsOpenByStart = new Map<string, StandardAutoClosingPairConditional[]>();
398
this.autoClosingPairsOpenByEnd = new Map<string, StandardAutoClosingPairConditional[]>();
399
this.autoClosingPairsCloseByStart = new Map<string, StandardAutoClosingPairConditional[]>();
400
this.autoClosingPairsCloseByEnd = new Map<string, StandardAutoClosingPairConditional[]>();
401
this.autoClosingPairsCloseSingleChar = new Map<string, StandardAutoClosingPairConditional[]>();
402
for (const pair of autoClosingPairs) {
403
appendEntry(this.autoClosingPairsOpenByStart, pair.open.charAt(0), pair);
404
appendEntry(this.autoClosingPairsOpenByEnd, pair.open.charAt(pair.open.length - 1), pair);
405
appendEntry(this.autoClosingPairsCloseByStart, pair.close.charAt(0), pair);
406
appendEntry(this.autoClosingPairsCloseByEnd, pair.close.charAt(pair.close.length - 1), pair);
407
if (pair.close.length === 1 && pair.open.length === 1) {
408
appendEntry(this.autoClosingPairsCloseSingleChar, pair.close, pair);
409
}
410
}
411
}
412
}
413
414
function appendEntry<K, V>(target: Map<K, V[]>, key: K, value: V): void {
415
if (target.has(key)) {
416
target.get(key)!.push(value);
417
} else {
418
target.set(key, [value]);
419
}
420
}
421
422