Path: blob/main/extensions/copilot/src/extension/prompts/node/test/fixtures/bracketPairsTree.ts
13406 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 { Emitter } from 'vs/base/common/event';6import { Disposable } from 'vs/base/common/lifecycle';7import { Range } from 'vs/editor/common/core/range';8import { ITextModel } from 'vs/editor/common/model';9import { BracketInfo, BracketPairWithMinIndentationInfo, IFoundBracket } from 'vs/editor/common/textModelBracketPairs';10import { TextModel } from 'vs/editor/common/model/textModel';11import { IModelContentChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents';12import { ResolvedLanguageConfiguration } from 'vs/editor/common/languages/languageConfigurationRegistry';13import { AstNode, AstNodeKind } from './ast';14import { TextEditInfo } from './beforeEditPositionMapper';15import { LanguageAgnosticBracketTokens } from './brackets';16import { Length, lengthAdd, lengthGreaterThanEqual, lengthLessThan, lengthLessThanEqual, lengthsToRange, lengthZero, positionToLength, toLength } from './length';17import { parseDocument } from './parser';18import { DenseKeyProvider } from './smallImmutableSet';19import { FastTokenizer, TextBufferTokenizer } from './tokenizer';20import { BackgroundTokenizationState } from 'vs/editor/common/tokenizationTextModelPart';21import { Position } from 'vs/editor/common/core/position';22import { CallbackIterable } from 'vs/base/common/arrays';23import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos';24import { ClosingBracketKind, OpeningBracketKind } from 'vs/editor/common/languages/supports/languageBracketsConfiguration';2526export class BracketPairsTree extends Disposable {27private readonly didChangeEmitter = new Emitter<void>();2829/*30There are two trees:31* The initial tree that has no token information and is used for performant initial bracket colorization.32* The tree that used token information to detect bracket pairs.3334To prevent flickering, we only switch from the initial tree to tree with token information35when tokenization completes.36Since the text can be edited while background tokenization is in progress, we need to update both trees.37*/38private initialAstWithoutTokens: AstNode | undefined;39private astWithTokens: AstNode | undefined;4041private readonly denseKeyProvider = new DenseKeyProvider<string>();42private readonly brackets = new LanguageAgnosticBracketTokens(this.denseKeyProvider, this.getLanguageConfiguration);4344public didLanguageChange(languageId: string): boolean {45return this.brackets.didLanguageChange(languageId);46}4748public readonly onDidChange = this.didChangeEmitter.event;49private queuedTextEditsForInitialAstWithoutTokens: TextEditInfo[] = [];50private queuedTextEdits: TextEditInfo[] = [];5152public constructor(53private readonly textModel: TextModel,54private readonly getLanguageConfiguration: (languageId: string) => ResolvedLanguageConfiguration55) {56super();5758if (!textModel.tokenization.hasTokens) {59const brackets = this.brackets.getSingleLanguageBracketTokens(this.textModel.getLanguageId());60const tokenizer = new FastTokenizer(this.textModel.getValue(), brackets);61this.initialAstWithoutTokens = parseDocument(tokenizer, [], undefined, true);62this.astWithTokens = this.initialAstWithoutTokens;63} else if (textModel.tokenization.backgroundTokenizationState === BackgroundTokenizationState.Completed) {64// Skip the initial ast, as there is no flickering.65// Directly create the tree with token information.66this.initialAstWithoutTokens = undefined;67this.astWithTokens = this.parseDocumentFromTextBuffer([], undefined, false);68} else {69// We missed some token changes already, so we cannot use the fast tokenizer + delta increments70this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer([], undefined, true);71this.astWithTokens = this.initialAstWithoutTokens;72}73}7475//#region TextModel events7677public handleDidChangeBackgroundTokenizationState(): void {78if (this.textModel.tokenization.backgroundTokenizationState === BackgroundTokenizationState.Completed) {79const wasUndefined = this.initialAstWithoutTokens === undefined;80// Clear the initial tree as we can use the tree with token information now.81this.initialAstWithoutTokens = undefined;82if (!wasUndefined) {83this.didChangeEmitter.fire();84}85}86}8788public handleDidChangeTokens({ ranges }: IModelTokensChangedEvent): void {89const edits = ranges.map(r =>90new TextEditInfo(91toLength(r.fromLineNumber - 1, 0),92toLength(r.toLineNumber, 0),93toLength(r.toLineNumber - r.fromLineNumber + 1, 0)94)95);9697this.handleEdits(edits, true);9899if (!this.initialAstWithoutTokens) {100this.didChangeEmitter.fire();101}102}103104public handleContentChanged(change: IModelContentChangedEvent) {105const edits = TextEditInfo.fromModelContentChanges(change.changes);106this.handleEdits(edits, false);107}108109private handleEdits(edits: TextEditInfo[], tokenChange: boolean): void {110// Lazily queue the edits and only apply them when the tree is accessed.111const result = combineTextEditInfos(this.queuedTextEdits, edits);112113this.queuedTextEdits = result;114if (this.initialAstWithoutTokens && !tokenChange) {115this.queuedTextEditsForInitialAstWithoutTokens = combineTextEditInfos(this.queuedTextEditsForInitialAstWithoutTokens, edits);116}117}118119//#endregion120121private flushQueue() {122if (this.queuedTextEdits.length > 0) {123this.astWithTokens = this.parseDocumentFromTextBuffer(this.queuedTextEdits, this.astWithTokens, false);124this.queuedTextEdits = [];125}126if (this.queuedTextEditsForInitialAstWithoutTokens.length > 0) {127if (this.initialAstWithoutTokens) {128this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer(this.queuedTextEditsForInitialAstWithoutTokens, this.initialAstWithoutTokens, false);129}130this.queuedTextEditsForInitialAstWithoutTokens = [];131}132}133134/**135* @pure (only if isPure = true)136*/137private parseDocumentFromTextBuffer(edits: TextEditInfo[], previousAst: AstNode | undefined, immutable: boolean): AstNode {138// Is much faster if `isPure = false`.139const isPure = false;140const previousAstClone = isPure ? previousAst?.deepClone() : previousAst;141const tokenizer = new TextBufferTokenizer(this.textModel, this.brackets);142const result = parseDocument(tokenizer, edits, previousAstClone, immutable);143return result;144}145146public getBracketsInRange(range: Range, onlyColorizedBrackets: boolean): CallbackIterable<BracketInfo> {147this.flushQueue();148149const startOffset = toLength(range.startLineNumber - 1, range.startColumn - 1);150const endOffset = toLength(range.endLineNumber - 1, range.endColumn - 1);151return new CallbackIterable(cb => {152const node = this.initialAstWithoutTokens || this.astWithTokens!;153collectBrackets(node, lengthZero, node.length, startOffset, endOffset, cb, 0, 0, new Map(), onlyColorizedBrackets);154});155}156157public getBracketPairsInRange(range: Range, includeMinIndentation: boolean): CallbackIterable<BracketPairWithMinIndentationInfo> {158this.flushQueue();159160const startLength = positionToLength(range.getStartPosition());161const endLength = positionToLength(range.getEndPosition());162163return new CallbackIterable(cb => {164const node = this.initialAstWithoutTokens || this.astWithTokens!;165const context = new CollectBracketPairsContext(cb, includeMinIndentation, this.textModel);166collectBracketPairs(node, lengthZero, node.length, startLength, endLength, context, 0, new Map());167});168}169170public getFirstBracketAfter(position: Position): IFoundBracket | null {171this.flushQueue();172173const node = this.initialAstWithoutTokens || this.astWithTokens!;174return getFirstBracketAfter(node, lengthZero, node.length, positionToLength(position));175}176177public getFirstBracketBefore(position: Position): IFoundBracket | null {178this.flushQueue();179180const node = this.initialAstWithoutTokens || this.astWithTokens!;181return getFirstBracketBefore(node, lengthZero, node.length, positionToLength(position));182}183}184185function getFirstBracketBefore(node: AstNode, nodeOffsetStart: Length, nodeOffsetEnd: Length, position: Length): IFoundBracket | null {186if (node.kind === AstNodeKind.List || node.kind === AstNodeKind.Pair) {187const lengths: { nodeOffsetStart: Length; nodeOffsetEnd: Length }[] = [];188for (const child of node.children) {189nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);190lengths.push({ nodeOffsetStart, nodeOffsetEnd });191nodeOffsetStart = nodeOffsetEnd;192}193for (let i = lengths.length - 1; i >= 0; i--) {194const { nodeOffsetStart, nodeOffsetEnd } = lengths[i];195if (lengthLessThan(nodeOffsetStart, position)) {196const result = getFirstBracketBefore(node.children[i], nodeOffsetStart, nodeOffsetEnd, position);197if (result) {198return result;199}200}201}202return null;203} else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {204return null;205} else if (node.kind === AstNodeKind.Bracket) {206const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);207return {208bracketInfo: node.bracketInfo,209range210};211}212return null;213}214215function getFirstBracketAfter(node: AstNode, nodeOffsetStart: Length, nodeOffsetEnd: Length, position: Length): IFoundBracket | null {216if (node.kind === AstNodeKind.List || node.kind === AstNodeKind.Pair) {217for (const child of node.children) {218nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);219if (lengthLessThan(position, nodeOffsetEnd)) {220const result = getFirstBracketAfter(child, nodeOffsetStart, nodeOffsetEnd, position);221if (result) {222return result;223}224}225nodeOffsetStart = nodeOffsetEnd;226}227return null;228} else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {229return null;230} else if (node.kind === AstNodeKind.Bracket) {231const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);232return {233bracketInfo: node.bracketInfo,234range235};236}237return null;238}239240function collectBrackets(241node: AstNode,242nodeOffsetStart: Length,243nodeOffsetEnd: Length,244startOffset: Length,245endOffset: Length,246push: (item: BracketInfo) => boolean,247level: number,248nestingLevelOfEqualBracketType: number,249levelPerBracketType: Map<string, number>,250onlyColorizedBrackets: boolean,251parentPairIsIncomplete: boolean = false,252): boolean {253if (level > 200) {254return true;255}256257whileLoop:258while (true) {259switch (node.kind) {260case AstNodeKind.List: {261const childCount = node.childrenLength;262for (let i = 0; i < childCount; i++) {263const child = node.getChild(i);264if (!child) {265continue;266}267nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);268if (269lengthLessThanEqual(nodeOffsetStart, endOffset) &&270lengthGreaterThanEqual(nodeOffsetEnd, startOffset)271) {272const childEndsAfterEnd = lengthGreaterThanEqual(nodeOffsetEnd, endOffset);273if (childEndsAfterEnd) {274// No child after this child in the requested window, don't recurse275node = child;276continue whileLoop;277}278279const shouldContinue = collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, push, level, 0, levelPerBracketType, onlyColorizedBrackets);280if (!shouldContinue) {281return false;282}283}284nodeOffsetStart = nodeOffsetEnd;285}286return true;287}288case AstNodeKind.Pair: {289const colorize = !onlyColorizedBrackets || !node.closingBracket || (node.closingBracket.bracketInfo as ClosingBracketKind).closesColorized(node.openingBracket.bracketInfo as OpeningBracketKind);290291let levelPerBracket = 0;292if (levelPerBracketType) {293let existing = levelPerBracketType.get(node.openingBracket.text);294if (existing === undefined) {295existing = 0;296}297levelPerBracket = existing;298if (colorize) {299existing++;300levelPerBracketType.set(node.openingBracket.text, existing);301}302}303304const childCount = node.childrenLength;305for (let i = 0; i < childCount; i++) {306const child = node.getChild(i);307if (!child) {308continue;309}310nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);311if (312lengthLessThanEqual(nodeOffsetStart, endOffset) &&313lengthGreaterThanEqual(nodeOffsetEnd, startOffset)314) {315const childEndsAfterEnd = lengthGreaterThanEqual(nodeOffsetEnd, endOffset);316if (childEndsAfterEnd && child.kind !== AstNodeKind.Bracket) {317// No child after this child in the requested window, don't recurse318// Don't do this for brackets because of unclosed/unopened brackets319node = child;320if (colorize) {321level++;322nestingLevelOfEqualBracketType = levelPerBracket + 1;323} else {324nestingLevelOfEqualBracketType = levelPerBracket;325}326continue whileLoop;327}328329if (colorize || child.kind !== AstNodeKind.Bracket || !node.closingBracket) {330const shouldContinue = collectBrackets(331child,332nodeOffsetStart,333nodeOffsetEnd,334startOffset,335endOffset,336push,337colorize ? level + 1 : level,338colorize ? levelPerBracket + 1 : levelPerBracket,339levelPerBracketType,340onlyColorizedBrackets,341!node.closingBracket,342);343if (!shouldContinue) {344return false;345}346}347}348nodeOffsetStart = nodeOffsetEnd;349}350351levelPerBracketType?.set(node.openingBracket.text, levelPerBracket);352353return true;354}355case AstNodeKind.UnexpectedClosingBracket: {356const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);357return push(new BracketInfo(range, level - 1, 0, true));358}359case AstNodeKind.Bracket: {360const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);361return push(new BracketInfo(range, level - 1, nestingLevelOfEqualBracketType - 1, parentPairIsIncomplete));362}363case AstNodeKind.Text:364return true;365}366}367}368369class CollectBracketPairsContext {370constructor(371public readonly push: (item: BracketPairWithMinIndentationInfo) => boolean,372public readonly includeMinIndentation: boolean,373public readonly textModel: ITextModel,374) {375}376}377378function collectBracketPairs(379node: AstNode,380nodeOffsetStart: Length,381nodeOffsetEnd: Length,382startOffset: Length,383endOffset: Length,384context: CollectBracketPairsContext,385level: number,386levelPerBracketType: Map<string, number>387): boolean {388if (level > 200) {389return true;390}391392let shouldContinue = true;393394if (node.kind === AstNodeKind.Pair) {395let levelPerBracket = 0;396if (levelPerBracketType) {397let existing = levelPerBracketType.get(node.openingBracket.text);398if (existing === undefined) {399existing = 0;400}401levelPerBracket = existing;402existing++;403levelPerBracketType.set(node.openingBracket.text, existing);404}405406const openingBracketEnd = lengthAdd(nodeOffsetStart, node.openingBracket.length);407let minIndentation = -1;408if (context.includeMinIndentation) {409minIndentation = node.computeMinIndentation(410nodeOffsetStart,411context.textModel412);413}414415shouldContinue = context.push(416new BracketPairWithMinIndentationInfo(417lengthsToRange(nodeOffsetStart, nodeOffsetEnd),418lengthsToRange(nodeOffsetStart, openingBracketEnd),419node.closingBracket420? lengthsToRange(421lengthAdd(openingBracketEnd, node.child?.length || lengthZero),422nodeOffsetEnd423)424: undefined,425level,426levelPerBracket,427node,428minIndentation429)430);431432nodeOffsetStart = openingBracketEnd;433if (shouldContinue && node.child) {434const child = node.child;435nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);436if (437lengthLessThanEqual(nodeOffsetStart, endOffset) &&438lengthGreaterThanEqual(nodeOffsetEnd, startOffset)439) {440shouldContinue = collectBracketPairs(441child,442nodeOffsetStart,443nodeOffsetEnd,444startOffset,445endOffset,446context,447level + 1,448levelPerBracketType449);450if (!shouldContinue) {451return false;452}453}454}455456levelPerBracketType?.set(node.openingBracket.text, levelPerBracket);457} else {458let curOffset = nodeOffsetStart;459for (const child of node.children) {460const childOffset = curOffset;461curOffset = lengthAdd(curOffset, child.length);462463if (464lengthLessThanEqual(childOffset, endOffset) &&465lengthLessThanEqual(startOffset, curOffset)466) {467shouldContinue = collectBracketPairs(468child,469childOffset,470curOffset,471startOffset,472endOffset,473context,474level,475levelPerBracketType476);477if (!shouldContinue) {478return false;479}480}481}482}483return shouldContinue;484}485486487