Path: blob/main/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos.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 { ArrayQueue } from '../../../../../base/common/arrays.js';6import { TextEditInfo } from './beforeEditPositionMapper.js';7import { Length, lengthAdd, lengthDiffNonNegative, lengthEquals, lengthIsZero, lengthToObj, lengthZero, sumLengths } from './length.js';89export function combineTextEditInfos(textEditInfoFirst: TextEditInfo[], textEditInfoSecond: TextEditInfo[]): TextEditInfo[] {10if (textEditInfoFirst.length === 0) {11return textEditInfoSecond;12}13if (textEditInfoSecond.length === 0) {14return textEditInfoFirst;15}1617// s0: State before any edits18const s0ToS1Map = new ArrayQueue(toLengthMapping(textEditInfoFirst));19// s1: State after first edit, but before second edit20const s1ToS2Map = toLengthMapping(textEditInfoSecond) as (LengthMapping | { lengthBefore: undefined; lengthAfter: undefined; modified: false })[];21s1ToS2Map.push({ modified: false, lengthBefore: undefined, lengthAfter: undefined }); // Copy everything from old to new22// s2: State after both edits2324let curItem: LengthMapping | undefined = s0ToS1Map.dequeue();2526/**27* @param s1Length Use undefined for length "infinity"28*/29function nextS0ToS1MapWithS1LengthOf(s1Length: Length | undefined): LengthMapping[] {30if (s1Length === undefined) {31const arr = s0ToS1Map.takeWhile(v => true) || [];32if (curItem) {33arr.unshift(curItem);34}35return arr;36}3738const result: LengthMapping[] = [];39while (curItem && !lengthIsZero(s1Length)) {40const [item, remainingItem] = curItem.splitAt(s1Length);41result.push(item);42s1Length = lengthDiffNonNegative(item.lengthAfter, s1Length);43curItem = remainingItem ?? s0ToS1Map.dequeue();44}45if (!lengthIsZero(s1Length)) {46result.push(new LengthMapping(false, s1Length, s1Length));47}48return result;49}5051const result: TextEditInfo[] = [];5253function pushEdit(startOffset: Length, endOffset: Length, newLength: Length): void {54if (result.length > 0 && lengthEquals(result[result.length - 1].endOffset, startOffset)) {55const lastResult = result[result.length - 1];56result[result.length - 1] = new TextEditInfo(lastResult.startOffset, endOffset, lengthAdd(lastResult.newLength, newLength));57} else {58result.push({ startOffset, endOffset, newLength });59}60}6162let s0offset = lengthZero;63for (const s1ToS2 of s1ToS2Map) {64const s0ToS1Map = nextS0ToS1MapWithS1LengthOf(s1ToS2.lengthBefore);65if (s1ToS2.modified) {66const s0Length = sumLengths(s0ToS1Map, s => s.lengthBefore);67const s0EndOffset = lengthAdd(s0offset, s0Length);68pushEdit(s0offset, s0EndOffset, s1ToS2.lengthAfter);69s0offset = s0EndOffset;70} else {71for (const s1 of s0ToS1Map) {72const s0startOffset = s0offset;73s0offset = lengthAdd(s0offset, s1.lengthBefore);74if (s1.modified) {75pushEdit(s0startOffset, s0offset, s1.lengthAfter);76}77}78}79}8081return result;82}8384class LengthMapping {85constructor(86/**87* If false, length before and length after equal.88*/89public readonly modified: boolean,90public readonly lengthBefore: Length,91public readonly lengthAfter: Length,92) {93}9495splitAt(lengthAfter: Length): [LengthMapping, LengthMapping | undefined] {96const remainingLengthAfter = lengthDiffNonNegative(lengthAfter, this.lengthAfter);97if (lengthEquals(remainingLengthAfter, lengthZero)) {98return [this, undefined];99} else if (this.modified) {100return [101new LengthMapping(this.modified, this.lengthBefore, lengthAfter),102new LengthMapping(this.modified, lengthZero, remainingLengthAfter)103];104} else {105return [106new LengthMapping(this.modified, lengthAfter, lengthAfter),107new LengthMapping(this.modified, remainingLengthAfter, remainingLengthAfter)108];109}110}111112toString(): string {113return `${this.modified ? 'M' : 'U'}:${lengthToObj(this.lengthBefore)} -> ${lengthToObj(this.lengthAfter)}`;114}115}116117function toLengthMapping(textEditInfos: TextEditInfo[]): LengthMapping[] {118const result: LengthMapping[] = [];119let lastOffset = lengthZero;120for (const textEditInfo of textEditInfos) {121const spaceLength = lengthDiffNonNegative(lastOffset, textEditInfo.startOffset);122if (!lengthIsZero(spaceLength)) {123result.push(new LengthMapping(false, spaceLength, spaceLength));124}125126const lengthBefore = lengthDiffNonNegative(textEditInfo.startOffset, textEditInfo.endOffset);127result.push(new LengthMapping(true, lengthBefore, textEditInfo.newLength));128lastOffset = textEditInfo.endOffset;129}130return result;131}132133134