UNPKG

monaco-editor-core

Version:

A browser based code editor

715 lines (714 loc) • 36.7 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { CallbackIterable, compareBy } from '../../../../base/common/arrays.js'; import { Emitter } from '../../../../base/common/event.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { Range } from '../../core/range.js'; import { ignoreBracketsInToken } from '../../languages/supports.js'; import { BracketsUtils } from '../../languages/supports/richEditBrackets.js'; import { BracketPairsTree } from './bracketPairsTree/bracketPairsTree.js'; export class BracketPairsTextModelPart extends Disposable { get canBuildAST() { const maxSupportedDocumentLength = /* max lines */ 50_000 * /* average column count */ 100; return this.textModel.getValueLength() <= maxSupportedDocumentLength; } constructor(textModel, languageConfigurationService) { super(); this.textModel = textModel; this.languageConfigurationService = languageConfigurationService; this.bracketPairsTree = this._register(new MutableDisposable()); this.onDidChangeEmitter = new Emitter(); this.onDidChange = this.onDidChangeEmitter.event; this.bracketsRequested = false; } //#region TextModel events handleLanguageConfigurationServiceChange(e) { if (!e.languageId || this.bracketPairsTree.value?.object.didLanguageChange(e.languageId)) { this.bracketPairsTree.clear(); this.updateBracketPairsTree(); } } handleDidChangeOptions(e) { this.bracketPairsTree.clear(); this.updateBracketPairsTree(); } handleDidChangeLanguage(e) { this.bracketPairsTree.clear(); this.updateBracketPairsTree(); } handleDidChangeContent(change) { this.bracketPairsTree.value?.object.handleContentChanged(change); } handleDidChangeBackgroundTokenizationState() { this.bracketPairsTree.value?.object.handleDidChangeBackgroundTokenizationState(); } handleDidChangeTokens(e) { this.bracketPairsTree.value?.object.handleDidChangeTokens(e); } //#endregion updateBracketPairsTree() { if (this.bracketsRequested && this.canBuildAST) { if (!this.bracketPairsTree.value) { const store = new DisposableStore(); this.bracketPairsTree.value = createDisposableRef(store.add(new BracketPairsTree(this.textModel, (languageId) => { return this.languageConfigurationService.getLanguageConfiguration(languageId); })), store); store.add(this.bracketPairsTree.value.object.onDidChange(e => this.onDidChangeEmitter.fire(e))); this.onDidChangeEmitter.fire(); } } else { if (this.bracketPairsTree.value) { this.bracketPairsTree.clear(); // Important: Don't call fire if there was no change! this.onDidChangeEmitter.fire(); } } } /** * Returns all bracket pairs that intersect the given range. * The result is sorted by the start position. */ getBracketPairsInRange(range) { this.bracketsRequested = true; this.updateBracketPairsTree(); return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, false) || CallbackIterable.empty; } getBracketPairsInRangeWithMinIndentation(range) { this.bracketsRequested = true; this.updateBracketPairsTree(); return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, true) || CallbackIterable.empty; } getBracketsInRange(range, onlyColorizedBrackets = false) { this.bracketsRequested = true; this.updateBracketPairsTree(); return this.bracketPairsTree.value?.object.getBracketsInRange(range, onlyColorizedBrackets) || CallbackIterable.empty; } findMatchingBracketUp(_bracket, _position, maxDuration) { const position = this.textModel.validatePosition(_position); const languageId = this.textModel.getLanguageIdAtPosition(position.lineNumber, position.column); if (this.canBuildAST) { const closingBracketInfo = this.languageConfigurationService .getLanguageConfiguration(languageId) .bracketsNew.getClosingBracketInfo(_bracket); if (!closingBracketInfo) { return null; } const bracketPair = this.getBracketPairsInRange(Range.fromPositions(_position, _position)).findLast((b) => closingBracketInfo.closes(b.openingBracketInfo)); if (bracketPair) { return bracketPair.openingBracketRange; } return null; } else { // Fallback to old bracket matching code: const bracket = _bracket.toLowerCase(); const bracketsSupport = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets; if (!bracketsSupport) { return null; } const data = bracketsSupport.textIsBracket[bracket]; if (!data) { return null; } return stripBracketSearchCanceled(this._findMatchingBracketUp(data, position, createTimeBasedContinueBracketSearchPredicate(maxDuration))); } } matchBracket(position, maxDuration) { if (this.canBuildAST) { const bracketPair = this.getBracketPairsInRange(Range.fromPositions(position, position)).filter((item) => item.closingBracketRange !== undefined && (item.openingBracketRange.containsPosition(position) || item.closingBracketRange.containsPosition(position))).findLastMaxBy(compareBy((item) => item.openingBracketRange.containsPosition(position) ? item.openingBracketRange : item.closingBracketRange, Range.compareRangesUsingStarts)); if (bracketPair) { return [bracketPair.openingBracketRange, bracketPair.closingBracketRange]; } return null; } else { // Fallback to old bracket matching code: const continueSearchPredicate = createTimeBasedContinueBracketSearchPredicate(maxDuration); return this._matchBracket(this.textModel.validatePosition(position), continueSearchPredicate); } } _establishBracketSearchOffsets(position, lineTokens, modeBrackets, tokenIndex) { const tokenCount = lineTokens.getCount(); const currentLanguageId = lineTokens.getLanguageId(tokenIndex); // limit search to not go before `maxBracketLength` let searchStartOffset = Math.max(0, position.column - 1 - modeBrackets.maxBracketLength); for (let i = tokenIndex - 1; i >= 0; i--) { const tokenEndOffset = lineTokens.getEndOffset(i); if (tokenEndOffset <= searchStartOffset) { break; } if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) { searchStartOffset = tokenEndOffset; break; } } // limit search to not go after `maxBracketLength` let searchEndOffset = Math.min(lineTokens.getLineContent().length, position.column - 1 + modeBrackets.maxBracketLength); for (let i = tokenIndex + 1; i < tokenCount; i++) { const tokenStartOffset = lineTokens.getStartOffset(i); if (tokenStartOffset >= searchEndOffset) { break; } if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) { searchEndOffset = tokenStartOffset; break; } } return { searchStartOffset, searchEndOffset }; } _matchBracket(position, continueSearchPredicate) { const lineNumber = position.lineNumber; const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber); const lineText = this.textModel.getLineContent(lineNumber); const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); if (tokenIndex < 0) { return null; } const currentModeBrackets = this.languageConfigurationService.getLanguageConfiguration(lineTokens.getLanguageId(tokenIndex)).brackets; // check that the token is not to be ignored if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) { let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, currentModeBrackets, tokenIndex); // it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets // `bestResult` will contain the most right-side result let bestResult = null; while (true) { const foundBracket = BracketsUtils.findNextBracketInRange(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (!foundBracket) { // there are no more brackets in this text break; } // check that we didn't hit a bracket too far away from position if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase(); const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText], continueSearchPredicate); if (r) { if (r instanceof BracketSearchCanceled) { return null; } bestResult = r; } } searchStartOffset = foundBracket.endColumn - 1; } if (bestResult) { return bestResult; } } // If position is in between two tokens, try also looking in the previous token if (tokenIndex > 0 && lineTokens.getStartOffset(tokenIndex) === position.column - 1) { const prevTokenIndex = tokenIndex - 1; const prevModeBrackets = this.languageConfigurationService.getLanguageConfiguration(lineTokens.getLanguageId(prevTokenIndex)).brackets; // check that previous token is not to be ignored if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(prevTokenIndex))) { const { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, prevModeBrackets, prevTokenIndex); const foundBracket = BracketsUtils.findPrevBracketInRange(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); // check that we didn't hit a bracket too far away from position if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase(); const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText], continueSearchPredicate); if (r) { if (r instanceof BracketSearchCanceled) { return null; } return r; } } } } return null; } _matchFoundBracket(foundBracket, data, isOpen, continueSearchPredicate) { if (!data) { return null; } const matched = (isOpen ? this._findMatchingBracketDown(data, foundBracket.getEndPosition(), continueSearchPredicate) : this._findMatchingBracketUp(data, foundBracket.getStartPosition(), continueSearchPredicate)); if (!matched) { return null; } if (matched instanceof BracketSearchCanceled) { return matched; } return [foundBracket, matched]; } _findMatchingBracketUp(bracket, position, continueSearchPredicate) { // console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position)); const languageId = bracket.languageId; const reversedBracketRegex = bracket.reversedRegex; let count = -1; let totalCallCount = 0; const searchPrevMatchingBracketInRange = (lineNumber, lineText, searchStartOffset, searchEndOffset) => { while (true) { if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) { return BracketSearchCanceled.INSTANCE; } const r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (!r) { break; } const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase(); if (bracket.isOpen(hitText)) { count++; } else if (bracket.isClose(hitText)) { count--; } if (count === 0) { return r; } searchEndOffset = r.startColumn - 1; } return null; }; for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) { const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this.textModel.getLineContent(lineNumber); let tokenIndex = tokenCount - 1; let searchStartOffset = lineText.length; let searchEndOffset = lineText.length; if (lineNumber === position.lineNumber) { tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); searchStartOffset = position.column - 1; searchEndOffset = position.column - 1; } let prevSearchInToken = true; for (; tokenIndex >= 0; tokenIndex--) { const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); if (searchInToken) { // this token should be searched if (prevSearchInToken) { // the previous token should be searched, simply extend searchStartOffset searchStartOffset = lineTokens.getStartOffset(tokenIndex); } else { // the previous token should not be searched searchStartOffset = lineTokens.getStartOffset(tokenIndex); searchEndOffset = lineTokens.getEndOffset(tokenIndex); } } else { // this token should not be searched if (prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return r; } } } prevSearchInToken = searchInToken; } if (prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return r; } } } return null; } _findMatchingBracketDown(bracket, position, continueSearchPredicate) { // console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position)); const languageId = bracket.languageId; const bracketRegex = bracket.forwardRegex; let count = 1; let totalCallCount = 0; const searchNextMatchingBracketInRange = (lineNumber, lineText, searchStartOffset, searchEndOffset) => { while (true) { if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) { return BracketSearchCanceled.INSTANCE; } const r = BracketsUtils.findNextBracketInRange(bracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (!r) { break; } const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase(); if (bracket.isOpen(hitText)) { count++; } else if (bracket.isClose(hitText)) { count--; } if (count === 0) { return r; } searchStartOffset = r.endColumn - 1; } return null; }; const lineCount = this.textModel.getLineCount(); for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) { const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this.textModel.getLineContent(lineNumber); let tokenIndex = 0; let searchStartOffset = 0; let searchEndOffset = 0; if (lineNumber === position.lineNumber) { tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); searchStartOffset = position.column - 1; searchEndOffset = position.column - 1; } let prevSearchInToken = true; for (; tokenIndex < tokenCount; tokenIndex++) { const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); if (searchInToken) { // this token should be searched if (prevSearchInToken) { // the previous token should be searched, simply extend searchEndOffset searchEndOffset = lineTokens.getEndOffset(tokenIndex); } else { // the previous token should not be searched searchStartOffset = lineTokens.getStartOffset(tokenIndex); searchEndOffset = lineTokens.getEndOffset(tokenIndex); } } else { // this token should not be searched if (prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return r; } } } prevSearchInToken = searchInToken; } if (prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return r; } } } return null; } findPrevBracket(_position) { const position = this.textModel.validatePosition(_position); if (this.canBuildAST) { this.bracketsRequested = true; this.updateBracketPairsTree(); return this.bracketPairsTree.value?.object.getFirstBracketBefore(position) || null; } let languageId = null; let modeBrackets = null; let bracketConfig = null; for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) { const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this.textModel.getLineContent(lineNumber); let tokenIndex = tokenCount - 1; let searchStartOffset = lineText.length; let searchEndOffset = lineText.length; if (lineNumber === position.lineNumber) { tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); searchStartOffset = position.column - 1; searchEndOffset = position.column - 1; const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); if (languageId !== tokenLanguageId) { languageId = tokenLanguageId; modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets; bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew; } } let prevSearchInToken = true; for (; tokenIndex >= 0; tokenIndex--) { const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); if (languageId !== tokenLanguageId) { // language id change! if (modeBrackets && bracketConfig && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return this._toFoundBracket(bracketConfig, r); } prevSearchInToken = false; } languageId = tokenLanguageId; modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets; bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew; } const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); if (searchInToken) { // this token should be searched if (prevSearchInToken) { // the previous token should be searched, simply extend searchStartOffset searchStartOffset = lineTokens.getStartOffset(tokenIndex); } else { // the previous token should not be searched searchStartOffset = lineTokens.getStartOffset(tokenIndex); searchEndOffset = lineTokens.getEndOffset(tokenIndex); } } else { // this token should not be searched if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return this._toFoundBracket(bracketConfig, r); } } } prevSearchInToken = searchInToken; } if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return this._toFoundBracket(bracketConfig, r); } } } return null; } findNextBracket(_position) { const position = this.textModel.validatePosition(_position); if (this.canBuildAST) { this.bracketsRequested = true; this.updateBracketPairsTree(); return this.bracketPairsTree.value?.object.getFirstBracketAfter(position) || null; } const lineCount = this.textModel.getLineCount(); let languageId = null; let modeBrackets = null; let bracketConfig = null; for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) { const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this.textModel.getLineContent(lineNumber); let tokenIndex = 0; let searchStartOffset = 0; let searchEndOffset = 0; if (lineNumber === position.lineNumber) { tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); searchStartOffset = position.column - 1; searchEndOffset = position.column - 1; const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); if (languageId !== tokenLanguageId) { languageId = tokenLanguageId; modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets; bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew; } } let prevSearchInToken = true; for (; tokenIndex < tokenCount; tokenIndex++) { const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); if (languageId !== tokenLanguageId) { // language id change! if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return this._toFoundBracket(bracketConfig, r); } prevSearchInToken = false; } languageId = tokenLanguageId; modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets; bracketConfig = this.languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew; } const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); if (searchInToken) { // this token should be searched if (prevSearchInToken) { // the previous token should be searched, simply extend searchEndOffset searchEndOffset = lineTokens.getEndOffset(tokenIndex); } else { // the previous token should not be searched searchStartOffset = lineTokens.getStartOffset(tokenIndex); searchEndOffset = lineTokens.getEndOffset(tokenIndex); } } else { // this token should not be searched if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return this._toFoundBracket(bracketConfig, r); } } } prevSearchInToken = searchInToken; } if (bracketConfig && modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return this._toFoundBracket(bracketConfig, r); } } } return null; } findEnclosingBrackets(_position, maxDuration) { const position = this.textModel.validatePosition(_position); if (this.canBuildAST) { const range = Range.fromPositions(position); const bracketPair = this.getBracketPairsInRange(Range.fromPositions(position, position)).findLast((item) => item.closingBracketRange !== undefined && item.range.strictContainsRange(range)); if (bracketPair) { return [bracketPair.openingBracketRange, bracketPair.closingBracketRange]; } return null; } const continueSearchPredicate = createTimeBasedContinueBracketSearchPredicate(maxDuration); const lineCount = this.textModel.getLineCount(); const savedCounts = new Map(); let counts = []; const resetCounts = (languageId, modeBrackets) => { if (!savedCounts.has(languageId)) { const tmp = []; for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) { tmp[i] = 0; } savedCounts.set(languageId, tmp); } counts = savedCounts.get(languageId); }; let totalCallCount = 0; const searchInRange = (modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset) => { while (true) { if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) { return BracketSearchCanceled.INSTANCE; } const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (!r) { break; } const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase(); const bracket = modeBrackets.textIsBracket[hitText]; if (bracket) { if (bracket.isOpen(hitText)) { counts[bracket.index]++; } else if (bracket.isClose(hitText)) { counts[bracket.index]--; } if (counts[bracket.index] === -1) { return this._matchFoundBracket(r, bracket, false, continueSearchPredicate); } } searchStartOffset = r.endColumn - 1; } return null; }; let languageId = null; let modeBrackets = null; for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) { const lineTokens = this.textModel.tokenization.getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this.textModel.getLineContent(lineNumber); let tokenIndex = 0; let searchStartOffset = 0; let searchEndOffset = 0; if (lineNumber === position.lineNumber) { tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); searchStartOffset = position.column - 1; searchEndOffset = position.column - 1; const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); if (languageId !== tokenLanguageId) { languageId = tokenLanguageId; modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets; resetCounts(languageId, modeBrackets); } } let prevSearchInToken = true; for (; tokenIndex < tokenCount; tokenIndex++) { const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); if (languageId !== tokenLanguageId) { // language id change! if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return stripBracketSearchCanceled(r); } prevSearchInToken = false; } languageId = tokenLanguageId; modeBrackets = this.languageConfigurationService.getLanguageConfiguration(languageId).brackets; resetCounts(languageId, modeBrackets); } const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); if (searchInToken) { // this token should be searched if (prevSearchInToken) { // the previous token should be searched, simply extend searchEndOffset searchEndOffset = lineTokens.getEndOffset(tokenIndex); } else { // the previous token should not be searched searchStartOffset = lineTokens.getStartOffset(tokenIndex); searchEndOffset = lineTokens.getEndOffset(tokenIndex); } } else { // this token should not be searched if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return stripBracketSearchCanceled(r); } } } prevSearchInToken = searchInToken; } if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { return stripBracketSearchCanceled(r); } } } return null; } _toFoundBracket(bracketConfig, r) { if (!r) { return null; } let text = this.textModel.getValueInRange(r); text = text.toLowerCase(); const bracketInfo = bracketConfig.getBracketInfo(text); if (!bracketInfo) { return null; } return { range: r, bracketInfo }; } } function createDisposableRef(object, disposable) { return { object, dispose: () => disposable?.dispose(), }; } function createTimeBasedContinueBracketSearchPredicate(maxDuration) { if (typeof maxDuration === 'undefined') { return () => true; } else { const startTime = Date.now(); return () => { return (Date.now() - startTime <= maxDuration); }; } } class BracketSearchCanceled { static { this.INSTANCE = new BracketSearchCanceled(); } constructor() { this._searchCanceledBrand = undefined; } } function stripBracketSearchCanceled(result) { if (result instanceof BracketSearchCanceled) { return null; } return result; }