Path: blob/main/extensions/copilot/src/extension/prompts/node/test/fixtures/bracketPairsTree.summarized.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) {…}189for (let i = lengths.length - 1; i >= 0; i--) {…}190} else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {…} else if (node.kind === AstNodeKind.Bracket) {…}191return null;192}193194function getFirstBracketAfter(node: AstNode, nodeOffsetStart: Length, nodeOffsetEnd: Length, position: Length): IFoundBracket | null {195if (node.kind === AstNodeKind.List || node.kind === AstNodeKind.Pair) {…} else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {…} else if (node.kind === AstNodeKind.Bracket) {…}196}197198class CollectBracketPairsContext {199constructor(200public readonly push: (item: BracketPairWithMinIndentationInfo) => boolean,201public readonly includeMinIndentation: boolean,202public readonly textModel: ITextModel,203) {204}205}206207208209