Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { ArrayQueue } from '../../../../../base/common/arrays.js';
7
import { TextEditInfo } from './beforeEditPositionMapper.js';
8
import { Length, lengthAdd, lengthDiffNonNegative, lengthEquals, lengthIsZero, lengthToObj, lengthZero, sumLengths } from './length.js';
9
10
export function combineTextEditInfos(textEditInfoFirst: TextEditInfo[], textEditInfoSecond: TextEditInfo[]): TextEditInfo[] {
11
if (textEditInfoFirst.length === 0) {
12
return textEditInfoSecond;
13
}
14
if (textEditInfoSecond.length === 0) {
15
return textEditInfoFirst;
16
}
17
18
// s0: State before any edits
19
const s0ToS1Map = new ArrayQueue(toLengthMapping(textEditInfoFirst));
20
// s1: State after first edit, but before second edit
21
const s1ToS2Map = toLengthMapping(textEditInfoSecond) as (LengthMapping | { lengthBefore: undefined; lengthAfter: undefined; modified: false })[];
22
s1ToS2Map.push({ modified: false, lengthBefore: undefined, lengthAfter: undefined }); // Copy everything from old to new
23
// s2: State after both edits
24
25
let curItem: LengthMapping | undefined = s0ToS1Map.dequeue();
26
27
/**
28
* @param s1Length Use undefined for length "infinity"
29
*/
30
function nextS0ToS1MapWithS1LengthOf(s1Length: Length | undefined): LengthMapping[] {
31
if (s1Length === undefined) {
32
const arr = s0ToS1Map.takeWhile(v => true) || [];
33
if (curItem) {
34
arr.unshift(curItem);
35
}
36
return arr;
37
}
38
39
const result: LengthMapping[] = [];
40
while (curItem && !lengthIsZero(s1Length)) {
41
const [item, remainingItem] = curItem.splitAt(s1Length);
42
result.push(item);
43
s1Length = lengthDiffNonNegative(item.lengthAfter, s1Length);
44
curItem = remainingItem ?? s0ToS1Map.dequeue();
45
}
46
if (!lengthIsZero(s1Length)) {
47
result.push(new LengthMapping(false, s1Length, s1Length));
48
}
49
return result;
50
}
51
52
const result: TextEditInfo[] = [];
53
54
function pushEdit(startOffset: Length, endOffset: Length, newLength: Length): void {
55
if (result.length > 0 && lengthEquals(result[result.length - 1].endOffset, startOffset)) {
56
const lastResult = result[result.length - 1];
57
result[result.length - 1] = new TextEditInfo(lastResult.startOffset, endOffset, lengthAdd(lastResult.newLength, newLength));
58
} else {
59
result.push({ startOffset, endOffset, newLength });
60
}
61
}
62
63
let s0offset = lengthZero;
64
for (const s1ToS2 of s1ToS2Map) {
65
const s0ToS1Map = nextS0ToS1MapWithS1LengthOf(s1ToS2.lengthBefore);
66
if (s1ToS2.modified) {
67
const s0Length = sumLengths(s0ToS1Map, s => s.lengthBefore);
68
const s0EndOffset = lengthAdd(s0offset, s0Length);
69
pushEdit(s0offset, s0EndOffset, s1ToS2.lengthAfter);
70
s0offset = s0EndOffset;
71
} else {
72
for (const s1 of s0ToS1Map) {
73
const s0startOffset = s0offset;
74
s0offset = lengthAdd(s0offset, s1.lengthBefore);
75
if (s1.modified) {
76
pushEdit(s0startOffset, s0offset, s1.lengthAfter);
77
}
78
}
79
}
80
}
81
82
return result;
83
}
84
85
class LengthMapping {
86
constructor(
87
/**
88
* If false, length before and length after equal.
89
*/
90
public readonly modified: boolean,
91
public readonly lengthBefore: Length,
92
public readonly lengthAfter: Length,
93
) {
94
}
95
96
splitAt(lengthAfter: Length): [LengthMapping, LengthMapping | undefined] {
97
const remainingLengthAfter = lengthDiffNonNegative(lengthAfter, this.lengthAfter);
98
if (lengthEquals(remainingLengthAfter, lengthZero)) {
99
return [this, undefined];
100
} else if (this.modified) {
101
return [
102
new LengthMapping(this.modified, this.lengthBefore, lengthAfter),
103
new LengthMapping(this.modified, lengthZero, remainingLengthAfter)
104
];
105
} else {
106
return [
107
new LengthMapping(this.modified, lengthAfter, lengthAfter),
108
new LengthMapping(this.modified, remainingLengthAfter, remainingLengthAfter)
109
];
110
}
111
}
112
113
toString(): string {
114
return `${this.modified ? 'M' : 'U'}:${lengthToObj(this.lengthBefore)} -> ${lengthToObj(this.lengthAfter)}`;
115
}
116
}
117
118
function toLengthMapping(textEditInfos: TextEditInfo[]): LengthMapping[] {
119
const result: LengthMapping[] = [];
120
let lastOffset = lengthZero;
121
for (const textEditInfo of textEditInfos) {
122
const spaceLength = lengthDiffNonNegative(lastOffset, textEditInfo.startOffset);
123
if (!lengthIsZero(spaceLength)) {
124
result.push(new LengthMapping(false, spaceLength, spaceLength));
125
}
126
127
const lengthBefore = lengthDiffNonNegative(textEditInfo.startOffset, textEditInfo.endOffset);
128
result.push(new LengthMapping(true, lengthBefore, textEditInfo.newLength));
129
lastOffset = textEditInfo.endOffset;
130
}
131
return result;
132
}
133
134