Path: blob/main/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CallbackIterable, compareBy } from '../../../../base/common/arrays.js';6import { Emitter } from '../../../../base/common/event.js';7import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable } from '../../../../base/common/lifecycle.js';8import { IPosition, Position } from '../../core/position.js';9import { Range } from '../../core/range.js';10import { ILanguageConfigurationService, LanguageConfigurationServiceChangeEvent } from '../../languages/languageConfigurationRegistry.js';11import { ignoreBracketsInToken } from '../../languages/supports.js';12import { LanguageBracketsConfiguration } from '../../languages/supports/languageBracketsConfiguration.js';13import { BracketsUtils, RichEditBracket, RichEditBrackets } from '../../languages/supports/richEditBrackets.js';14import { BracketPairsTree } from './bracketPairsTree/bracketPairsTree.js';15import { TextModel } from '../textModel.js';16import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBracketPairsTextModelPart, IFoundBracket } from '../../textModelBracketPairs.js';17import { IModelContentChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from '../../textModelEvents.js';18import { LineTokens } from '../../tokens/lineTokens.js';1920export class BracketPairsTextModelPart extends Disposable implements IBracketPairsTextModelPart {21private readonly bracketPairsTree = this._register(new MutableDisposable<IReference<BracketPairsTree>>());2223private readonly onDidChangeEmitter = new Emitter<void>();24public readonly onDidChange = this.onDidChangeEmitter.event;2526private get canBuildAST() {27const maxSupportedDocumentLength = /* max lines */ 50_000 * /* average column count */ 100;28return this.textModel.getValueLength() <= maxSupportedDocumentLength;29}3031private bracketsRequested = false;3233public constructor(34private readonly textModel: TextModel,35private readonly languageConfigurationService: ILanguageConfigurationService36) {37super();38}3940//#region TextModel events4142public handleLanguageConfigurationServiceChange(e: LanguageConfigurationServiceChangeEvent): void {43if (!e.languageId || this.bracketPairsTree.value?.object.didLanguageChange(e.languageId)) {44this.bracketPairsTree.clear();45this.updateBracketPairsTree();46}47}4849public handleDidChangeOptions(e: IModelOptionsChangedEvent): void {50this.bracketPairsTree.clear();51this.updateBracketPairsTree();52}5354public handleDidChangeLanguage(e: IModelLanguageChangedEvent): void {55this.bracketPairsTree.clear();56this.updateBracketPairsTree();57}5859public handleDidChangeContent(change: IModelContentChangedEvent) {60this.bracketPairsTree.value?.object.handleContentChanged(change);61}6263public handleDidChangeBackgroundTokenizationState(): void {64this.bracketPairsTree.value?.object.handleDidChangeBackgroundTokenizationState();65}6667public handleDidChangeTokens(e: IModelTokensChangedEvent): void {68this.bracketPairsTree.value?.object.handleDidChangeTokens(e);69}7071//#endregion7273private updateBracketPairsTree() {74if (this.bracketsRequested && this.canBuildAST) {75if (!this.bracketPairsTree.value) {76const store = new DisposableStore();7778this.bracketPairsTree.value = createDisposableRef(79store.add(80new BracketPairsTree(this.textModel, (languageId) => {81return this.languageConfigurationService.getLanguageConfiguration(languageId);82})83),84store85);86store.add(this.bracketPairsTree.value.object.onDidChange(e => this.onDidChangeEmitter.fire(e)));87this.onDidChangeEmitter.fire();88}89} else {90if (this.bracketPairsTree.value) {91this.bracketPairsTree.clear();92// Important: Don't call fire if there was no change!93this.onDidChangeEmitter.fire();94}95}96}9798/**99* Returns all bracket pairs that intersect the given range.100* The result is sorted by the start position.101*/102public getBracketPairsInRange(range: Range): CallbackIterable<BracketPairInfo> {103this.bracketsRequested = true;104this.updateBracketPairsTree();105return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, false) || CallbackIterable.empty;106}107108public getBracketPairsInRangeWithMinIndentation(range: Range): CallbackIterable<BracketPairWithMinIndentationInfo> {109this.bracketsRequested = true;110this.updateBracketPairsTree();111return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, true) || CallbackIterable.empty;112}113114public getBracketsInRange(range: Range, onlyColorizedBrackets: boolean = false): CallbackIterable<BracketInfo> {115this.bracketsRequested = true;116this.updateBracketPairsTree();117return this.bracketPairsTree.value?.object.getBracketsInRange(range, onlyColorizedBrackets) || CallbackIterable.empty;118}119120public findMatchingBracketUp(_bracket: string, _position: IPosition, maxDuration?: number): Range | null {121const position = this.textModel.validatePosition(_position);122const languageId = this.textModel.getLanguageIdAtPosition(position.lineNumber, position.column);123124if (this.canBuildAST) {125const closingBracketInfo = this.languageConfigurationService126.getLanguageConfiguration(languageId)127.bracketsNew.getClosingBracketInfo(_bracket);128129if (!closingBracketInfo) {130return null;131}132133const bracketPair = this.getBracketPairsInRange(Range.fromPositions(_position, _position)).findLast((b) =>134closingBracketInfo.closes(b.openingBracketInfo)135);136137if (bracketPair) {138return bracketPair.openingBracketRange;139}140return null;141} else {142// Fallback to old bracket matching code:143const bracket = _bracket.toLowerCase();144145const bracketsSupport = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;146147if (!bracketsSupport) {148return null;149}150151const data = bracketsSupport.textIsBracket[bracket];152153if (!data) {154return null;155}156157return stripBracketSearchCanceled(this._findMatchingBracketUp(data, position, createTimeBasedContinueBracketSearchPredicate(maxDuration)));158}159}160161public matchBracket(position: IPosition, maxDuration?: number): [Range, Range] | null {162if (this.canBuildAST) {163const bracketPair =164this.getBracketPairsInRange(165Range.fromPositions(position, position)166).filter(167(item) =>168item.closingBracketRange !== undefined &&169(item.openingBracketRange.containsPosition(position) ||170item.closingBracketRange.containsPosition(position))171).findLastMaxBy(172compareBy(173(item) =>174item.openingBracketRange.containsPosition(position)175? item.openingBracketRange176: item.closingBracketRange,177Range.compareRangesUsingStarts178)179);180if (bracketPair) {181return [bracketPair.openingBracketRange, bracketPair.closingBracketRange!];182}183return null;184} else {185// Fallback to old bracket matching code:186const continueSearchPredicate = createTimeBasedContinueBracketSearchPredicate(maxDuration);187return this._matchBracket(this.textModel.validatePosition(position), continueSearchPredicate);188}189}190191private _establishBracketSearchOffsets(position: Position, lineTokens: LineTokens, modeBrackets: RichEditBrackets, tokenIndex: number) {192const tokenCount = lineTokens.getCount();193const currentLanguageId = lineTokens.getLanguageId(tokenIndex);194195// limit search to not go before `maxBracketLength`196let searchStartOffset = Math.max(0, position.column - 1 - modeBrackets.maxBracketLength);197for (let i = tokenIndex - 1; i >= 0; i--) {198const tokenEndOffset = lineTokens.getEndOffset(i);199if (tokenEndOffset <= searchStartOffset) {200break;201}202if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) {203searchStartOffset = tokenEndOffset;204break;205}206}207208// limit search to not go after `maxBracketLength`209let searchEndOffset = Math.min(lineTokens.getLineContent().length, position.column - 1 + modeBrackets.maxBracketLength);210for (let i = tokenIndex + 1; i < tokenCount; i++) {211const tokenStartOffset = lineTokens.getStartOffset(i);212if (tokenStartOffset >= searchEndOffset) {213break;214}215if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) {216searchEndOffset = tokenStartOffset;217break;218}219}220221return { searchStartOffset, searchEndOffset };222}223224private _matchBracket(position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): [Range, Range] | null {225const lineNumber = position.lineNumber;226const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);227const lineText = this.textModel.getLineContent(lineNumber);228229const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);230if (tokenIndex < 0) {231return null;232}233const currentModeBrackets = this.languageConfigurationService.getLanguageConfiguration(lineTokens.getLanguageId(tokenIndex)).brackets;234235// check that the token is not to be ignored236if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) {237238let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, currentModeBrackets, tokenIndex);239240// it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets241// `bestResult` will contain the most right-side result242let bestResult: [Range, Range] | null = null;243while (true) {244const foundBracket = BracketsUtils.findNextBracketInRange(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);245if (!foundBracket) {246// there are no more brackets in this text247break;248}249250// check that we didn't hit a bracket too far away from position251if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {252const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();253const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText], continueSearchPredicate);254if (r) {255if (r instanceof BracketSearchCanceled) {256return null;257}258bestResult = r;259}260}261262searchStartOffset = foundBracket.endColumn - 1;263}264265if (bestResult) {266return bestResult;267}268}269270// If position is in between two tokens, try also looking in the previous token271if (tokenIndex > 0 && lineTokens.getStartOffset(tokenIndex) === position.column - 1) {272const prevTokenIndex = tokenIndex - 1;273const prevModeBrackets = this.languageConfigurationService.getLanguageConfiguration(lineTokens.getLanguageId(prevTokenIndex)).brackets;274275// check that previous token is not to be ignored276if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(prevTokenIndex))) {277278const { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, prevModeBrackets, prevTokenIndex);279280const foundBracket = BracketsUtils.findPrevBracketInRange(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);281282// check that we didn't hit a bracket too far away from position283if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {284const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();285const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText], continueSearchPredicate);286if (r) {287if (r instanceof BracketSearchCanceled) {288return null;289}290return r;291}292}293}294}295296return null;297}298299private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean, continueSearchPredicate: ContinueBracketSearchPredicate): [Range, Range] | null | BracketSearchCanceled {300if (!data) {301return null;302}303304const matched = (305isOpen306? this._findMatchingBracketDown(data, foundBracket.getEndPosition(), continueSearchPredicate)307: this._findMatchingBracketUp(data, foundBracket.getStartPosition(), continueSearchPredicate)308);309310if (!matched) {311return null;312}313314if (matched instanceof BracketSearchCanceled) {315return matched;316}317318return [foundBracket, matched];319}320321private _findMatchingBracketUp(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {322// console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));323324const languageId = bracket.languageId;325const reversedBracketRegex = bracket.reversedRegex;326let count = -1;327328let totalCallCount = 0;329const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {330while (true) {331if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {332return BracketSearchCanceled.INSTANCE;333}334const r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);335if (!r) {336break;337}338339const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();340if (bracket.isOpen(hitText)) {341count++;342} else if (bracket.isClose(hitText)) {343count--;344}345346if (count === 0) {347return r;348}349350searchEndOffset = r.startColumn - 1;351}352353return null;354};355356for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) {357const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);358const tokenCount = lineTokens.getCount();359const lineText = this.textModel.getLineContent(lineNumber);360361let tokenIndex = tokenCount - 1;362let searchStartOffset = lineText.length;363let searchEndOffset = lineText.length;364if (lineNumber === position.lineNumber) {365tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);366searchStartOffset = position.column - 1;367searchEndOffset = position.column - 1;368}369370let prevSearchInToken = true;371for (; tokenIndex >= 0; tokenIndex--) {372const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));373374if (searchInToken) {375// this token should be searched376if (prevSearchInToken) {377// the previous token should be searched, simply extend searchStartOffset378searchStartOffset = lineTokens.getStartOffset(tokenIndex);379} else {380// the previous token should not be searched381searchStartOffset = lineTokens.getStartOffset(tokenIndex);382searchEndOffset = lineTokens.getEndOffset(tokenIndex);383}384} else {385// this token should not be searched386if (prevSearchInToken && searchStartOffset !== searchEndOffset) {387const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);388if (r) {389return r;390}391}392}393394prevSearchInToken = searchInToken;395}396397if (prevSearchInToken && searchStartOffset !== searchEndOffset) {398const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);399if (r) {400return r;401}402}403}404405return null;406}407408private _findMatchingBracketDown(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {409// console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));410411const languageId = bracket.languageId;412const bracketRegex = bracket.forwardRegex;413let count = 1;414415let totalCallCount = 0;416const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {417while (true) {418if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {419return BracketSearchCanceled.INSTANCE;420}421const r = BracketsUtils.findNextBracketInRange(bracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);422if (!r) {423break;424}425426const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();427if (bracket.isOpen(hitText)) {428count++;429} else if (bracket.isClose(hitText)) {430count--;431}432433if (count === 0) {434return r;435}436437searchStartOffset = r.endColumn - 1;438}439440return null;441};442443const lineCount = this.textModel.getLineCount();444for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {445const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);446const tokenCount = lineTokens.getCount();447const lineText = this.textModel.getLineContent(lineNumber);448449let tokenIndex = 0;450let searchStartOffset = 0;451let searchEndOffset = 0;452if (lineNumber === position.lineNumber) {453tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);454searchStartOffset = position.column - 1;455searchEndOffset = position.column - 1;456}457458let prevSearchInToken = true;459for (; tokenIndex < tokenCount; tokenIndex++) {460const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));461462if (searchInToken) {463// this token should be searched464if (prevSearchInToken) {465// the previous token should be searched, simply extend searchEndOffset466searchEndOffset = lineTokens.getEndOffset(tokenIndex);467} else {468// the previous token should not be searched469searchStartOffset = lineTokens.getStartOffset(tokenIndex);470searchEndOffset = lineTokens.getEndOffset(tokenIndex);471}472} else {473// this token should not be searched474if (prevSearchInToken && searchStartOffset !== searchEndOffset) {475const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);476if (r) {477return r;478}479}480}481482prevSearchInToken = searchInToken;483}484485if (prevSearchInToken && searchStartOffset !== searchEndOffset) {486const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);487if (r) {488return r;489}490}491}492493return null;494}495496public findPrevBracket(_position: IPosition): IFoundBracket | null {497const position = this.textModel.validatePosition(_position);498499if (this.canBuildAST) {500this.bracketsRequested = true;501this.updateBracketPairsTree();502return this.bracketPairsTree.value?.object.getFirstBracketBefore(position) || null;503}504505let languageId: string | null = null;506let modeBrackets: RichEditBrackets | null = null;507let bracketConfig: LanguageBracketsConfiguration | null = null;508for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) {509const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);510const tokenCount = lineTokens.getCount();511const lineText = this.textModel.getLineContent(lineNumber);512513let tokenIndex = tokenCount - 1;514let searchStartOffset = lineText.length;515let searchEndOffset = lineText.length;516if (lineNumber === position.lineNumber) {517tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);518searchStartOffset = position.column - 1;519searchEndOffset = position.column - 1;520const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);521if (languageId !== tokenLanguageId) {522languageId = tokenLanguageId;523modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;524bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew;525}526}527528let prevSearchInToken = true;529for (; tokenIndex >= 0; tokenIndex--) {530const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);531532if (languageId !== tokenLanguageId) {533// language id change!534if (modeBrackets && bracketConfig && prevSearchInToken && searchStartOffset !== searchEndOffset) {535const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);536if (r) {537return this._toFoundBracket(bracketConfig, r);538}539prevSearchInToken = false;540}541languageId = tokenLanguageId;542modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;543bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew;544}545546const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));547548if (searchInToken) {549// this token should be searched550if (prevSearchInToken) {551// the previous token should be searched, simply extend searchStartOffset552searchStartOffset = lineTokens.getStartOffset(tokenIndex);553} else {554// the previous token should not be searched555searchStartOffset = lineTokens.getStartOffset(tokenIndex);556searchEndOffset = lineTokens.getEndOffset(tokenIndex);557}558} else {559// this token should not be searched560if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {561const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);562if (r) {563return this._toFoundBracket(bracketConfig, r);564}565}566}567568prevSearchInToken = searchInToken;569}570571if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {572const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);573if (r) {574return this._toFoundBracket(bracketConfig, r);575}576}577}578579return null;580}581582public findNextBracket(_position: IPosition): IFoundBracket | null {583const position = this.textModel.validatePosition(_position);584585if (this.canBuildAST) {586this.bracketsRequested = true;587this.updateBracketPairsTree();588return this.bracketPairsTree.value?.object.getFirstBracketAfter(position) || null;589}590591const lineCount = this.textModel.getLineCount();592593let languageId: string | null = null;594let modeBrackets: RichEditBrackets | null = null;595let bracketConfig: LanguageBracketsConfiguration | null = null;596for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {597const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);598const tokenCount = lineTokens.getCount();599const lineText = this.textModel.getLineContent(lineNumber);600601let tokenIndex = 0;602let searchStartOffset = 0;603let searchEndOffset = 0;604if (lineNumber === position.lineNumber) {605tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);606searchStartOffset = position.column - 1;607searchEndOffset = position.column - 1;608const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);609if (languageId !== tokenLanguageId) {610languageId = tokenLanguageId;611modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;612bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew;613}614}615616let prevSearchInToken = true;617for (; tokenIndex < tokenCount; tokenIndex++) {618const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);619620if (languageId !== tokenLanguageId) {621// language id change!622if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {623const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);624if (r) {625return this._toFoundBracket(bracketConfig, r);626}627prevSearchInToken = false;628}629languageId = tokenLanguageId;630modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;631bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew;632}633634const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));635if (searchInToken) {636// this token should be searched637if (prevSearchInToken) {638// the previous token should be searched, simply extend searchEndOffset639searchEndOffset = lineTokens.getEndOffset(tokenIndex);640} else {641// the previous token should not be searched642searchStartOffset = lineTokens.getStartOffset(tokenIndex);643searchEndOffset = lineTokens.getEndOffset(tokenIndex);644}645} else {646// this token should not be searched647if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {648const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);649if (r) {650return this._toFoundBracket(bracketConfig, r);651}652}653}654655prevSearchInToken = searchInToken;656}657658if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {659const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);660if (r) {661return this._toFoundBracket(bracketConfig, r);662}663}664}665666return null;667}668669public findEnclosingBrackets(_position: IPosition, maxDuration?: number): [Range, Range] | null {670const position = this.textModel.validatePosition(_position);671672if (this.canBuildAST) {673const range = Range.fromPositions(position);674const bracketPair =675this.getBracketPairsInRange(Range.fromPositions(position, position)).findLast(676(item) => item.closingBracketRange !== undefined && item.range.strictContainsRange(range)677);678if (bracketPair) {679return [bracketPair.openingBracketRange, bracketPair.closingBracketRange!];680}681return null;682}683684const continueSearchPredicate = createTimeBasedContinueBracketSearchPredicate(maxDuration);685const lineCount = this.textModel.getLineCount();686const savedCounts = new Map<string, number[]>();687688let counts: number[] = [];689const resetCounts = (languageId: string, modeBrackets: RichEditBrackets | null) => {690if (!savedCounts.has(languageId)) {691const tmp = [];692for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) {693tmp[i] = 0;694}695savedCounts.set(languageId, tmp);696}697counts = savedCounts.get(languageId)!;698};699700let totalCallCount = 0;701const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null | BracketSearchCanceled => {702while (true) {703if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {704return BracketSearchCanceled.INSTANCE;705}706const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);707if (!r) {708break;709}710711const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();712const bracket = modeBrackets.textIsBracket[hitText];713if (bracket) {714if (bracket.isOpen(hitText)) {715counts[bracket.index]++;716} else if (bracket.isClose(hitText)) {717counts[bracket.index]--;718}719720if (counts[bracket.index] === -1) {721return this._matchFoundBracket(r, bracket, false, continueSearchPredicate);722}723}724725searchStartOffset = r.endColumn - 1;726}727return null;728};729730let languageId: string | null = null;731let modeBrackets: RichEditBrackets | null = null;732for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {733const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber);734const tokenCount = lineTokens.getCount();735const lineText = this.textModel.getLineContent(lineNumber);736737let tokenIndex = 0;738let searchStartOffset = 0;739let searchEndOffset = 0;740if (lineNumber === position.lineNumber) {741tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);742searchStartOffset = position.column - 1;743searchEndOffset = position.column - 1;744const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);745if (languageId !== tokenLanguageId) {746languageId = tokenLanguageId;747modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;748resetCounts(languageId, modeBrackets);749}750}751752let prevSearchInToken = true;753for (; tokenIndex < tokenCount; tokenIndex++) {754const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);755756if (languageId !== tokenLanguageId) {757// language id change!758if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {759const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);760if (r) {761return stripBracketSearchCanceled(r);762}763prevSearchInToken = false;764}765languageId = tokenLanguageId;766modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets;767resetCounts(languageId, modeBrackets);768}769770const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));771if (searchInToken) {772// this token should be searched773if (prevSearchInToken) {774// the previous token should be searched, simply extend searchEndOffset775searchEndOffset = lineTokens.getEndOffset(tokenIndex);776} else {777// the previous token should not be searched778searchStartOffset = lineTokens.getStartOffset(tokenIndex);779searchEndOffset = lineTokens.getEndOffset(tokenIndex);780}781} else {782// this token should not be searched783if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {784const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);785if (r) {786return stripBracketSearchCanceled(r);787}788}789}790791prevSearchInToken = searchInToken;792}793794if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {795const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);796if (r) {797return stripBracketSearchCanceled(r);798}799}800}801802return null;803}804805private _toFoundBracket(bracketConfig: LanguageBracketsConfiguration, r: Range): IFoundBracket | null {806if (!r) {807return null;808}809810let text = this.textModel.getValueInRange(r);811text = text.toLowerCase();812813const bracketInfo = bracketConfig.getBracketInfo(text);814if (!bracketInfo) {815return null;816}817818return {819range: r,820bracketInfo821};822}823}824825function createDisposableRef<T>(object: T, disposable?: IDisposable): IReference<T> {826return {827object,828dispose: () => disposable?.dispose(),829};830}831832type ContinueBracketSearchPredicate = (() => boolean);833834function createTimeBasedContinueBracketSearchPredicate(maxDuration: number | undefined): ContinueBracketSearchPredicate {835if (typeof maxDuration === 'undefined') {836return () => true;837} else {838const startTime = Date.now();839return () => {840return (Date.now() - startTime <= maxDuration);841};842}843}844845class BracketSearchCanceled {846public static INSTANCE = new BracketSearchCanceled();847_searchCanceledBrand = undefined;848private constructor() { }849}850851function stripBracketSearchCanceled<T>(result: T | null | BracketSearchCanceled): T | null {852if (result instanceof BracketSearchCanceled) {853return null;854}855return result;856}857858859