src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts

857 LOC · 186 covered · 671 uncovered · 55 ranges · 1708 concepts · 19 introducers · 861 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- textModel.ts ×190
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 { CallbackIterable, compareBy } from '../../../../base/common/arrays.js';
7 > import { Emitter } from '../../../../base/common/event.js';
8 > import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable } from '../../../../base/common/lifecycle.js';
9 > import { IPosition, Position } from '../../core/position.js';
10 > import { Range } from '../../core/range.js';
11 > import { ILanguageConfigurationService, LanguageConfigurationServiceChangeEvent } from '../../languages/languageConfigurationRegistry.js';
12 > import { ignoreBracketsInToken } from '../../languages/supports.js';
13 > import { LanguageBracketsConfiguration } from '../../languages/supports/languageBracketsConfiguration.js';
14 > import { BracketsUtils, RichEditBracket, RichEditBrackets } from '../../languages/supports/richEditBrackets.js';
15 > import { BracketPairsTree } from './bracketPairsTree/bracketPairsTree.js';
16 > import { TextModel } from '../textModel.js';
17 > import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBracketPairsTextModelPart, IFoundBracket } from '../../textModelBracketPairs.js';
18 > import { IModelContentChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from '../../textModelEvents.js';
19 > import { LineTokens } from '../../tokens/lineTokens.js';
20 >
21 > export class BracketPairsTextModelPart extends Disposable implements IBracketPairsTextModelPart {
22 > private readonly bracketPairsTree = this._register(new MutableDisposable<IReference<BracketPairsTree>>());
23 >
24 > private readonly onDidChangeEmitter = this._register(new Emitter<void>());
25 > public readonly onDidChange = this.onDidChangeEmitter.event;
26 >
27 > private get canBuildAST() {
28 > const maxSupportedDocumentLength = /* max lines */ 50_000 * /* average column count */ 100;
29 > return this.textModel.getValueLength() <= maxSupportedDocumentLength;
30 > }
31 >
32 > private bracketsRequested = false;
33 >
34 > public constructor(
35 > private readonly textModel: TextModel, textModel.ts ×20
36 > private readonly languageConfigurationService: ILanguageConfigurationService
37 > ) {
38 > super();
39 > }
41 > //#region TextModel events
42 >
43 > public handleLanguageConfigurationServiceChange(e: LanguageConfigurationServiceChangeEvent): void {
44 if (!e.languageId || this.bracketPairsTree.value?.object.didLanguageChange(e.languageId)) {
45 this.bracketPairsTree.clear();
46 this.updateBracketPairsTree();
47 }
48 }
50 > public handleDidChangeOptions(e: IModelOptionsChangedEvent): void {
51 > this.bracketPairsTree.clear(); model.ts ×1
52 > this.updateBracketPairsTree();
53 > }
55 > public handleDidChangeLanguage(e: IModelLanguageChangedEvent): void {
56 > this.bracketPairsTree.clear(); textModel.ts ×3
57 > this.updateBracketPairsTree();
58 > }
60 > public handleDidChangeContent(change: IModelContentChangedEvent) {
61 > this.bracketPairsTree.value?.object.handleContentChanged(change); textModel.ts ×9
62 > }
64 > public handleDidChangeBackgroundTokenizationState(): void {
65 > this.bracketPairsTree.value?.object.handleDidChangeBackgroundTokenizationState(); textModelTokens.ts ×4
66 > }
68 > public handleDidChangeTokens(e: IModelTokensChangedEvent): void {
69 > this.bracketPairsTree.value?.object.handleDidChangeTokens(e); bracketPairsImpl.ts ×1
70 > }
72 > //#endregion
73 >
74 > private updateBracketPairsTree() {
75 > if (this.bracketsRequested && this.canBuildAST) { bracketPairsImpl.ts ×3
76 > if (!this.bracketPairsTree.value) { parser.ts ×14
77 > const store = new DisposableStore();
78 >
79 > this.bracketPairsTree.value = createDisposableRef(
80 > store.add(
81 > new BracketPairsTree(this.textModel, (languageId) => {
82 > return this.languageConfigurationService.getLanguageConfiguration(languageId); bracketPairsImpl.ts ×1
83 > }) parser.ts ×14
84 > ),
85 > store
86 > );
87 > store.add(this.bracketPairsTree.value.object.onDidChange(e => this.onDidChangeEmitter.fire(e)));
88 > this.onDidChangeEmitter.fire();
89 > }
90 > } else { bracketPairsImpl.ts ×3
91 > if (this.bracketPairsTree.value) { bracketPairsImpl.ts ×2
92 this.bracketPairsTree.clear();
93 // Important: Don't call fire if there was no change!
94 this.onDidChangeEmitter.fire();
95 }
99 > /**
100 > * Returns all bracket pairs that intersect the given range.
101 > * The result is sorted by the start position.
102 > */
103 > public getBracketPairsInRange(range: Range): CallbackIterable<BracketPairInfo> {
104 > this.bracketsRequested = true; bracketPairsTree.ts ×6
105 > this.updateBracketPairsTree();
106 > return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, false) || CallbackIterable.empty;
107 > }
109 > public getBracketPairsInRangeWithMinIndentation(range: Range): CallbackIterable<BracketPairWithMinIndentationInfo> {
110 this.bracketsRequested = true;
111 this.updateBracketPairsTree();
112 return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, true) || CallbackIterable.empty;
113 }
115 > public getBracketsInRange(range: Range, onlyColorizedBrackets: boolean = false): CallbackIterable<BracketInfo> {
116 > this.bracketsRequested = true; bracketPairsTree.ts ×7
117 > this.updateBracketPairsTree();
118 > return this.bracketPairsTree.value?.object.getBracketsInRange(range, onlyColorizedBrackets) || CallbackIterable.empty;
119 > }
121 > public findMatchingBracketUp(_bracket: string, _position: IPosition, maxDuration?: number): Range | null {
122 const position = this.textModel.validatePosition(_position);
123 const languageId = this.textModel.getLanguageIdAtPosition(position.lineNumber, position.column);
124
125 if (this.canBuildAST) {
126 const closingBracketInfo = this.languageConfigurationService
127 .getLanguageConfiguration(languageId)
128 .bracketsNew.getClosingBracketInfo(_bracket);
129
130 if (!closingBracketInfo) {
131 return null;
132 }
133
134 const bracketPair = this.getBracketPairsInRange(Range.fromPositions(_position, _position)).findLast((b) =>
135 closingBracketInfo.closes(b.openingBracketInfo)
136 );
137
138 if (bracketPair) {
139 return bracketPair.openingBracketRange;
140 }
141 return null;
142 } else {
143 // Fallback to old bracket matching code:
144 const bracket = _bracket.toLowerCase();
145
146 const bracketsSupport = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
147
148 if (!bracketsSupport) {
149 return null;
150 }
151
152 const data = bracketsSupport.textIsBracket[bracket];
153
154 if (!data) {
155 return null;
156 }
157
158 return stripBracketSearchCanceled(this._findMatchingBracketUp(data, position, createTimeBasedContinueBracketSearchPredicate(maxDuration)));
159 }
160 }
162 > public matchBracket(position: IPosition, maxDuration?: number): [Range, Range] | null {
163 > if (this.canBuildAST) { bracketPairsImpl.ts ×4
164 > const bracketPair =
165 > this.getBracketPairsInRange(
166 > Range.fromPositions(position, position)
167 > ).filter(
168 > (item) =>
169 > item.closingBracketRange !== undefined && bracketPairsImpl.ts ×2
170 > (item.openingBracketRange.containsPosition(position) ||
171 > item.closingBracketRange.containsPosition(position)) bracketPairsImpl.ts ×1
172 > ).findLastMaxBy( bracketPairsImpl.ts ×4
173 > compareBy(
174 > (item) =>
175 > item.openingBracketRange.containsPosition(position) arrays.ts ×1
176 > ? item.openingBracketRange
177 > : item.closingBracketRange,
178 > Range.compareRangesUsingStarts bracketPairsImpl.ts ×4
179 > )
180 > );
181 > if (bracketPair) {
182 > return [bracketPair.openingBracketRange, bracketPair.closingBracketRange!]; bracketPairsImpl.ts ×2
183 > }
184 > return null; bracketPairsImpl.ts ×1
185 > } else {
186 // Fallback to old bracket matching code:
187 const continueSearchPredicate = createTimeBasedContinueBracketSearchPredicate(maxDuration);
188 return this._matchBracket(this.textModel.validatePosition(position), continueSearchPredicate);
189 }
192 > private _establishBracketSearchOffsets(position: Position, lineTokens: LineTokens, modeBrackets: RichEditBrackets, tokenIndex: number) {
193 const tokenCount = lineTokens.getCount();
194 const currentLanguageId = lineTokens.getLanguageId(tokenIndex);
195
196 // limit search to not go before `maxBracketLength`
197 let searchStartOffset = Math.max(0, position.column - 1 - modeBrackets.maxBracketLength);
198 for (let i = tokenIndex - 1; i >= 0; i--) {
199 const tokenEndOffset = lineTokens.getEndOffset(i);
200 if (tokenEndOffset <= searchStartOffset) {
201 break;
202 }
203 if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) {
204 searchStartOffset = tokenEndOffset;
205 break;
206 }
207 }
208
209 // limit search to not go after `maxBracketLength`
210 let searchEndOffset = Math.min(lineTokens.getLineContent().length, position.column - 1 + modeBrackets.maxBracketLength);
211 for (let i = tokenIndex + 1; i < tokenCount; i++) {
212 const tokenStartOffset = lineTokens.getStartOffset(i);
213 if (tokenStartOffset >= searchEndOffset) {
214 break;
215 }
216 if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) {
217 searchEndOffset = tokenStartOffset;
218 break;
219 }
220 }
221
222 return { searchStartOffset, searchEndOffset };
223 }
225 > private _matchBracket(position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): [Range, Range] | null {
226 const lineNumber = position.lineNumber;
227 const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);
228 const lineText = this.textModel.getLineContent(lineNumber);
229
230 const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
231 if (tokenIndex < 0) {
232 return null;
233 }
234 const currentModeBrackets = this.languageConfigurationService.getLanguageConfiguration(lineTokens.getLanguageId(tokenIndex)).brackets;
235
236 // check that the token is not to be ignored
237 if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) {
238
239 let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, currentModeBrackets, tokenIndex);
240
241 // it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets
242 // `bestResult` will contain the most right-side result
243 let bestResult: [Range, Range] | null = null;
244 while (true) {
245 const foundBracket = BracketsUtils.findNextBracketInRange(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
246 if (!foundBracket) {
247 // there are no more brackets in this text
248 break;
249 }
250
251 // check that we didn't hit a bracket too far away from position
252 if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
253 const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
254 const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText], continueSearchPredicate);
255 if (r) {
256 if (r instanceof BracketSearchCanceled) {
257 return null;
258 }
259 bestResult = r;
260 }
261 }
262
263 searchStartOffset = foundBracket.endColumn - 1;
264 }
265
266 if (bestResult) {
267 return bestResult;
268 }
269 }
270
271 // If position is in between two tokens, try also looking in the previous token
272 if (tokenIndex > 0 && lineTokens.getStartOffset(tokenIndex) === position.column - 1) {
273 const prevTokenIndex = tokenIndex - 1;
274 const prevModeBrackets = this.languageConfigurationService.getLanguageConfiguration(lineTokens.getLanguageId(prevTokenIndex)).brackets;
275
276 // check that previous token is not to be ignored
277 if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(prevTokenIndex))) {
278
279 const { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, prevModeBrackets, prevTokenIndex);
280
281 const foundBracket = BracketsUtils.findPrevBracketInRange(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
282
283 // check that we didn't hit a bracket too far away from position
284 if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
285 const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
286 const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText], continueSearchPredicate);
287 if (r) {
288 if (r instanceof BracketSearchCanceled) {
289 return null;
290 }
291 return r;
292 }
293 }
294 }
295 }
296
297 return null;
298 }
300 > private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean, continueSearchPredicate: ContinueBracketSearchPredicate): [Range, Range] | null | BracketSearchCanceled {
301 if (!data) {
302 return null;
303 }
304
305 const matched = (
306 isOpen
307 ? this._findMatchingBracketDown(data, foundBracket.getEndPosition(), continueSearchPredicate)
308 : this._findMatchingBracketUp(data, foundBracket.getStartPosition(), continueSearchPredicate)
309 );
310
311 if (!matched) {
312 return null;
313 }
314
315 if (matched instanceof BracketSearchCanceled) {
316 return matched;
317 }
318
319 return [foundBracket, matched];
320 }
322 > private _findMatchingBracketUp(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {
323 // console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));
324
325 const languageId = bracket.languageId;
326 const reversedBracketRegex = bracket.reversedRegex;
327 let count = -1;
328
329 let totalCallCount = 0;
330 const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {
331 while (true) {
332 if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
333 return BracketSearchCanceled.INSTANCE;
334 }
335 const r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
336 if (!r) {
337 break;
338 }
339
340 const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
341 if (bracket.isOpen(hitText)) {
342 count++;
343 } else if (bracket.isClose(hitText)) {
344 count--;
345 }
346
347 if (count === 0) {
348 return r;
349 }
350
351 searchEndOffset = r.startColumn - 1;
352 }
353
354 return null;
355 };
356
357 for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) {
358 const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);
359 const tokenCount = lineTokens.getCount();
360 const lineText = this.textModel.getLineContent(lineNumber);
361
362 let tokenIndex = tokenCount - 1;
363 let searchStartOffset = lineText.length;
364 let searchEndOffset = lineText.length;
365 if (lineNumber === position.lineNumber) {
366 tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
367 searchStartOffset = position.column - 1;
368 searchEndOffset = position.column - 1;
369 }
370
371 let prevSearchInToken = true;
372 for (; tokenIndex >= 0; tokenIndex--) {
373 const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
374
375 if (searchInToken) {
376 // this token should be searched
377 if (prevSearchInToken) {
378 // the previous token should be searched, simply extend searchStartOffset
379 searchStartOffset = lineTokens.getStartOffset(tokenIndex);
380 } else {
381 // the previous token should not be searched
382 searchStartOffset = lineTokens.getStartOffset(tokenIndex);
383 searchEndOffset = lineTokens.getEndOffset(tokenIndex);
384 }
385 } else {
386 // this token should not be searched
387 if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
388 const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
389 if (r) {
390 return r;
391 }
392 }
393 }
394
395 prevSearchInToken = searchInToken;
396 }
397
398 if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
399 const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
400 if (r) {
401 return r;
402 }
403 }
404 }
405
406 return null;
407 }
409 > private _findMatchingBracketDown(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {
410 // console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));
411
412 const languageId = bracket.languageId;
413 const bracketRegex = bracket.forwardRegex;
414 let count = 1;
415
416 let totalCallCount = 0;
417 const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {
418 while (true) {
419 if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
420 return BracketSearchCanceled.INSTANCE;
421 }
422 const r = BracketsUtils.findNextBracketInRange(bracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
423 if (!r) {
424 break;
425 }
426
427 const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
428 if (bracket.isOpen(hitText)) {
429 count++;
430 } else if (bracket.isClose(hitText)) {
431 count--;
432 }
433
434 if (count === 0) {
435 return r;
436 }
437
438 searchStartOffset = r.endColumn - 1;
439 }
440
441 return null;
442 };
443
444 const lineCount = this.textModel.getLineCount();
445 for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
446 const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);
447 const tokenCount = lineTokens.getCount();
448 const lineText = this.textModel.getLineContent(lineNumber);
449
450 let tokenIndex = 0;
451 let searchStartOffset = 0;
452 let searchEndOffset = 0;
453 if (lineNumber === position.lineNumber) {
454 tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
455 searchStartOffset = position.column - 1;
456 searchEndOffset = position.column - 1;
457 }
458
459 let prevSearchInToken = true;
460 for (; tokenIndex < tokenCount; tokenIndex++) {
461 const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
462
463 if (searchInToken) {
464 // this token should be searched
465 if (prevSearchInToken) {
466 // the previous token should be searched, simply extend searchEndOffset
467 searchEndOffset = lineTokens.getEndOffset(tokenIndex);
468 } else {
469 // the previous token should not be searched
470 searchStartOffset = lineTokens.getStartOffset(tokenIndex);
471 searchEndOffset = lineTokens.getEndOffset(tokenIndex);
472 }
473 } else {
474 // this token should not be searched
475 if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
476 const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
477 if (r) {
478 return r;
479 }
480 }
481 }
482
483 prevSearchInToken = searchInToken;
484 }
485
486 if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
487 const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
488 if (r) {
489 return r;
490 }
491 }
492 }
493
494 return null;
495 }
497 > public findPrevBracket(_position: IPosition): IFoundBracket | null {
498 > const position = this.textModel.validatePosition(_position); bracketPairsTree.ts ×6
499 >
500 > if (this.canBuildAST) {
501 > this.bracketsRequested = true;
502 > this.updateBracketPairsTree();
503 > return this.bracketPairsTree.value?.object.getFirstBracketBefore(position) || null;
504 > }
505
506 let languageId: string | null = null;
507 let modeBrackets: RichEditBrackets | null = null;
508 let bracketConfig: LanguageBracketsConfiguration | null = null;
509 for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) {
510 const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);
511 const tokenCount = lineTokens.getCount();
512 const lineText = this.textModel.getLineContent(lineNumber);
513
514 let tokenIndex = tokenCount - 1;
515 let searchStartOffset = lineText.length;
516 let searchEndOffset = lineText.length;
517 if (lineNumber === position.lineNumber) {
518 tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
519 searchStartOffset = position.column - 1;
520 searchEndOffset = position.column - 1;
521 const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
522 if (languageId !== tokenLanguageId) {
523 languageId = tokenLanguageId;
524 modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
525 bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew;
526 }
527 }
528
529 let prevSearchInToken = true;
530 for (; tokenIndex >= 0; tokenIndex--) {
531 const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
532
533 if (languageId !== tokenLanguageId) {
534 // language id change!
535 if (modeBrackets && bracketConfig && prevSearchInToken && searchStartOffset !== searchEndOffset) {
536 const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
537 if (r) {
538 return this._toFoundBracket(bracketConfig, r);
539 }
540 prevSearchInToken = false;
541 }
542 languageId = tokenLanguageId;
543 modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
544 bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew;
545 }
546
547 const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
548
549 if (searchInToken) {
550 // this token should be searched
551 if (prevSearchInToken) {
552 // the previous token should be searched, simply extend searchStartOffset
553 searchStartOffset = lineTokens.getStartOffset(tokenIndex);
554 } else {
555 // the previous token should not be searched
556 searchStartOffset = lineTokens.getStartOffset(tokenIndex);
557 searchEndOffset = lineTokens.getEndOffset(tokenIndex);
558 }
559 } else {
560 // this token should not be searched
561 if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
562 const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
563 if (r) {
564 return this._toFoundBracket(bracketConfig, r);
565 }
566 }
567 }
568
569 prevSearchInToken = searchInToken;
570 }
571
572 if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
573 const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
574 if (r) {
575 return this._toFoundBracket(bracketConfig, r);
576 }
577 }
578 }
579
580 return null;
583 > public findNextBracket(_position: IPosition): IFoundBracket | null {
584 > const position = this.textModel.validatePosition(_position); bracketPairsTree.ts ×6
585 >
586 > if (this.canBuildAST) {
587 > this.bracketsRequested = true;
588 > this.updateBracketPairsTree();
589 > return this.bracketPairsTree.value?.object.getFirstBracketAfter(position) || null;
590 > }
591
592 const lineCount = this.textModel.getLineCount();
593
594 let languageId: string | null = null;
595 let modeBrackets: RichEditBrackets | null = null;
596 let bracketConfig: LanguageBracketsConfiguration | null = null;
597 for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
598 const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);
599 const tokenCount = lineTokens.getCount();
600 const lineText = this.textModel.getLineContent(lineNumber);
601
602 let tokenIndex = 0;
603 let searchStartOffset = 0;
604 let searchEndOffset = 0;
605 if (lineNumber === position.lineNumber) {
606 tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
607 searchStartOffset = position.column - 1;
608 searchEndOffset = position.column - 1;
609 const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
610 if (languageId !== tokenLanguageId) {
611 languageId = tokenLanguageId;
612 modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
613 bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew;
614 }
615 }
616
617 let prevSearchInToken = true;
618 for (; tokenIndex < tokenCount; tokenIndex++) {
619 const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
620
621 if (languageId !== tokenLanguageId) {
622 // language id change!
623 if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
624 const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
625 if (r) {
626 return this._toFoundBracket(bracketConfig, r);
627 }
628 prevSearchInToken = false;
629 }
630 languageId = tokenLanguageId;
631 modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
632 bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew;
633 }
634
635 const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
636 if (searchInToken) {
637 // this token should be searched
638 if (prevSearchInToken) {
639 // the previous token should be searched, simply extend searchEndOffset
640 searchEndOffset = lineTokens.getEndOffset(tokenIndex);
641 } else {
642 // the previous token should not be searched
643 searchStartOffset = lineTokens.getStartOffset(tokenIndex);
644 searchEndOffset = lineTokens.getEndOffset(tokenIndex);
645 }
646 } else {
647 // this token should not be searched
648 if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
649 const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
650 if (r) {
651 return this._toFoundBracket(bracketConfig, r);
652 }
653 }
654 }
655
656 prevSearchInToken = searchInToken;
657 }
658
659 if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
660 const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
661 if (r) {
662 return this._toFoundBracket(bracketConfig, r);
663 }
664 }
665 }
666
667 return null;
670 > public findEnclosingBrackets(_position: IPosition, maxDuration?: number): [Range, Range] | null {
671 const position = this.textModel.validatePosition(_position);
672
673 if (this.canBuildAST) {
674 const range = Range.fromPositions(position);
675 const bracketPair =
676 this.getBracketPairsInRange(Range.fromPositions(position, position)).findLast(
677 (item) => item.closingBracketRange !== undefined && item.range.strictContainsRange(range)
678 );
679 if (bracketPair) {
680 return [bracketPair.openingBracketRange, bracketPair.closingBracketRange!];
681 }
682 return null;
683 }
684
685 const continueSearchPredicate = createTimeBasedContinueBracketSearchPredicate(maxDuration);
686 const lineCount = this.textModel.getLineCount();
687 const savedCounts = new Map<string, number[]>();
688
689 let counts: number[] = [];
690 const resetCounts = (languageId: string, modeBrackets: RichEditBrackets | null) => {
691 if (!savedCounts.has(languageId)) {
692 const tmp = [];
693 for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) {
694 tmp[i] = 0;
695 }
696 savedCounts.set(languageId, tmp);
697 }
698 counts = savedCounts.get(languageId)!;
699 };
700
701 let totalCallCount = 0;
702 const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null | BracketSearchCanceled => {
703 while (true) {
704 if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
705 return BracketSearchCanceled.INSTANCE;
706 }
707 const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
708 if (!r) {
709 break;
710 }
711
712 const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
713 const bracket = modeBrackets.textIsBracket[hitText];
714 if (bracket) {
715 if (bracket.isOpen(hitText)) {
716 counts[bracket.index]++;
717 } else if (bracket.isClose(hitText)) {
718 counts[bracket.index]--;
719 }
720
721 if (counts[bracket.index] === -1) {
722 return this._matchFoundBracket(r, bracket, false, continueSearchPredicate);
723 }
724 }
725
726 searchStartOffset = r.endColumn - 1;
727 }
728 return null;
729 };
730
731 let languageId: string | null = null;
732 let modeBrackets: RichEditBrackets | null = null;
733 for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
734 const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);
735 const tokenCount = lineTokens.getCount();
736 const lineText = this.textModel.getLineContent(lineNumber);
737
738 let tokenIndex = 0;
739 let searchStartOffset = 0;
740 let searchEndOffset = 0;
741 if (lineNumber === position.lineNumber) {
742 tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
743 searchStartOffset = position.column - 1;
744 searchEndOffset = position.column - 1;
745 const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
746 if (languageId !== tokenLanguageId) {
747 languageId = tokenLanguageId;
748 modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
749 resetCounts(languageId, modeBrackets);
750 }
751 }
752
753 let prevSearchInToken = true;
754 for (; tokenIndex < tokenCount; tokenIndex++) {
755 const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
756
757 if (languageId !== tokenLanguageId) {
758 // language id change!
759 if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
760 const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
761 if (r) {
762 return stripBracketSearchCanceled(r);
763 }
764 prevSearchInToken = false;
765 }
766 languageId = tokenLanguageId;
767 modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;
768 resetCounts(languageId, modeBrackets);
769 }
770
771 const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
772 if (searchInToken) {
773 // this token should be searched
774 if (prevSearchInToken) {
775 // the previous token should be searched, simply extend searchEndOffset
776 searchEndOffset = lineTokens.getEndOffset(tokenIndex);
777 } else {
778 // the previous token should not be searched
779 searchStartOffset = lineTokens.getStartOffset(tokenIndex);
780 searchEndOffset = lineTokens.getEndOffset(tokenIndex);
781 }
782 } else {
783 // this token should not be searched
784 if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
785 const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
786 if (r) {
787 return stripBracketSearchCanceled(r);
788 }
789 }
790 }
791
792 prevSearchInToken = searchInToken;
793 }
794
795 if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
796 const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
797 if (r) {
798 return stripBracketSearchCanceled(r);
799 }
800 }
801 }
802
803 return null;
804 }
806 > private _toFoundBracket(bracketConfig: LanguageBracketsConfiguration, r: Range): IFoundBracket | null {
807 if (!r) {
808 return null;
809 }
810
811 let text = this.textModel.getValueInRange(r);
812 text = text.toLowerCase();
813
814 const bracketInfo = bracketConfig.getBracketInfo(text);
815 if (!bracketInfo) {
816 return null;
817 }
818
819 return {
820 range: r,
821 bracketInfo
822 };
823 }
825 >
826 > function createDisposableRef<T>(object: T, disposable?: IDisposable): IReference<T> { parser.ts ×14
827 > return {
828 > object,
829 > dispose: () => disposable?.dispose(),
830 > };
831 > }
833 > type ContinueBracketSearchPredicate = (() => boolean);
834 >
835 function createTimeBasedContinueBracketSearchPredicate(maxDuration: number | undefined): ContinueBracketSearchPredicate {
836 if (typeof maxDuration === 'undefined') {
837 return () => true;
838 } else {
839 const startTime = Date.now();
840 return () => {
841 return (Date.now() - startTime <= maxDuration);
842 };
843 }
844 }
846 > class BracketSearchCanceled {
847 > public static INSTANCE = new BracketSearchCanceled();
848 > _searchCanceledBrand = undefined;
849 > private constructor() { }
850 > }
851 >
852 function stripBracketSearchCanceled<T>(result: T | null | BracketSearchCanceled): T | null {
853 if (result instanceof BracketSearchCanceled) {
854 return null;
855 }
856 return result;
857 }