Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/test/common/model/textChange.test.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 assert from 'assert';
7
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
8
import { compressConsecutiveTextChanges, TextChange } from '../../../common/core/textChange.js';
9
10
const GENERATE_TESTS = false;
11
12
interface IGeneratedEdit {
13
offset: number;
14
length: number;
15
text: string;
16
}
17
18
suite('TextChangeCompressor', () => {
19
20
ensureNoDisposablesAreLeakedInTestSuite();
21
22
function getResultingContent(initialContent: string, edits: IGeneratedEdit[]): string {
23
let content = initialContent;
24
for (let i = edits.length - 1; i >= 0; i--) {
25
content = (
26
content.substring(0, edits[i].offset) +
27
edits[i].text +
28
content.substring(edits[i].offset + edits[i].length)
29
);
30
}
31
return content;
32
}
33
34
function getTextChanges(initialContent: string, edits: IGeneratedEdit[]): TextChange[] {
35
let content = initialContent;
36
const changes: TextChange[] = new Array<TextChange>(edits.length);
37
let deltaOffset = 0;
38
39
for (let i = 0; i < edits.length; i++) {
40
const edit = edits[i];
41
42
const position = edit.offset + deltaOffset;
43
const length = edit.length;
44
const text = edit.text;
45
46
const oldText = content.substr(position, length);
47
48
content = (
49
content.substr(0, position) +
50
text +
51
content.substr(position + length)
52
);
53
54
changes[i] = new TextChange(edit.offset, oldText, position, text);
55
56
deltaOffset += text.length - length;
57
}
58
59
return changes;
60
}
61
62
function assertCompression(initialText: string, edit1: IGeneratedEdit[], edit2: IGeneratedEdit[]): void {
63
64
const tmpText = getResultingContent(initialText, edit1);
65
const chg1 = getTextChanges(initialText, edit1);
66
67
const finalText = getResultingContent(tmpText, edit2);
68
const chg2 = getTextChanges(tmpText, edit2);
69
70
const compressedTextChanges = compressConsecutiveTextChanges(chg1, chg2);
71
72
// Check that the compression was correct
73
const compressedDoTextEdits: IGeneratedEdit[] = compressedTextChanges.map((change) => {
74
return {
75
offset: change.oldPosition,
76
length: change.oldLength,
77
text: change.newText
78
};
79
});
80
const actualDoResult = getResultingContent(initialText, compressedDoTextEdits);
81
assert.strictEqual(actualDoResult, finalText);
82
83
const compressedUndoTextEdits: IGeneratedEdit[] = compressedTextChanges.map((change) => {
84
return {
85
offset: change.newPosition,
86
length: change.newLength,
87
text: change.oldText
88
};
89
});
90
const actualUndoResult = getResultingContent(finalText, compressedUndoTextEdits);
91
assert.strictEqual(actualUndoResult, initialText);
92
}
93
94
test('simple 1', () => {
95
assertCompression(
96
'',
97
[{ offset: 0, length: 0, text: 'h' }],
98
[{ offset: 1, length: 0, text: 'e' }]
99
);
100
});
101
102
test('simple 2', () => {
103
assertCompression(
104
'|',
105
[{ offset: 0, length: 0, text: 'h' }],
106
[{ offset: 2, length: 0, text: 'e' }]
107
);
108
});
109
110
test('complex1', () => {
111
assertCompression(
112
'abcdefghij',
113
[
114
{ offset: 0, length: 3, text: 'qh' },
115
{ offset: 5, length: 0, text: '1' },
116
{ offset: 8, length: 2, text: 'X' }
117
],
118
[
119
{ offset: 1, length: 0, text: 'Z' },
120
{ offset: 3, length: 3, text: 'Y' },
121
]
122
);
123
});
124
125
// test('issue #118041', () => {
126
// assertCompression(
127
// '',
128
// [
129
// { offset: 0, length: 1, text: '' },
130
// ],
131
// [
132
// { offset: 1, length: 0, text: 'Z' },
133
// { offset: 3, length: 3, text: 'Y' },
134
// ]
135
// );
136
// })
137
138
test('gen1', () => {
139
assertCompression(
140
'kxm',
141
[{ offset: 0, length: 1, text: 'tod_neu' }],
142
[{ offset: 1, length: 2, text: 'sag_e' }]
143
);
144
});
145
146
test('gen2', () => {
147
assertCompression(
148
'kpb_r_v',
149
[{ offset: 5, length: 2, text: 'a_jvf_l' }],
150
[{ offset: 10, length: 2, text: 'w' }]
151
);
152
});
153
154
test('gen3', () => {
155
assertCompression(
156
'slu_w',
157
[{ offset: 4, length: 1, text: '_wfw' }],
158
[{ offset: 3, length: 5, text: '' }]
159
);
160
});
161
162
test('gen4', () => {
163
assertCompression(
164
'_e',
165
[{ offset: 2, length: 0, text: 'zo_b' }],
166
[{ offset: 1, length: 3, text: 'tra' }]
167
);
168
});
169
170
test('gen5', () => {
171
assertCompression(
172
'ssn_',
173
[{ offset: 0, length: 2, text: 'tat_nwe' }],
174
[{ offset: 2, length: 6, text: 'jm' }]
175
);
176
});
177
178
test('gen6', () => {
179
assertCompression(
180
'kl_nru',
181
[{ offset: 4, length: 1, text: '' }],
182
[{ offset: 1, length: 4, text: '__ut' }]
183
);
184
});
185
186
const _a = 'a'.charCodeAt(0);
187
const _z = 'z'.charCodeAt(0);
188
189
function getRandomInt(min: number, max: number): number {
190
return Math.floor(Math.random() * (max - min + 1)) + min;
191
}
192
193
function getRandomString(minLength: number, maxLength: number): string {
194
const length = getRandomInt(minLength, maxLength);
195
let r = '';
196
for (let i = 0; i < length; i++) {
197
r += String.fromCharCode(getRandomInt(_a, _z));
198
}
199
return r;
200
}
201
202
function getRandomEOL(): string {
203
switch (getRandomInt(1, 3)) {
204
case 1: return '\r';
205
case 2: return '\n';
206
case 3: return '\r\n';
207
}
208
throw new Error(`not possible`);
209
}
210
211
function getRandomBuffer(small: boolean): string {
212
const lineCount = getRandomInt(1, small ? 3 : 10);
213
const lines: string[] = [];
214
for (let i = 0; i < lineCount; i++) {
215
lines.push(getRandomString(0, small ? 3 : 10) + getRandomEOL());
216
}
217
return lines.join('');
218
}
219
220
function getRandomEdits(content: string, min: number = 1, max: number = 5): IGeneratedEdit[] {
221
222
const result: IGeneratedEdit[] = [];
223
let cnt = getRandomInt(min, max);
224
225
let maxOffset = content.length;
226
227
while (cnt > 0 && maxOffset > 0) {
228
229
const offset = getRandomInt(0, maxOffset);
230
const length = getRandomInt(0, maxOffset - offset);
231
const text = getRandomBuffer(true);
232
233
result.push({
234
offset: offset,
235
length: length,
236
text: text
237
});
238
239
maxOffset = offset;
240
cnt--;
241
}
242
243
result.reverse();
244
245
return result;
246
}
247
248
class GeneratedTest {
249
250
private readonly _content: string;
251
private readonly _edits1: IGeneratedEdit[];
252
private readonly _edits2: IGeneratedEdit[];
253
254
constructor() {
255
this._content = getRandomBuffer(false).replace(/\n/g, '_');
256
this._edits1 = getRandomEdits(this._content, 1, 5).map((e) => { return { offset: e.offset, length: e.length, text: e.text.replace(/\n/g, '_') }; });
257
const tmp = getResultingContent(this._content, this._edits1);
258
this._edits2 = getRandomEdits(tmp, 1, 5).map((e) => { return { offset: e.offset, length: e.length, text: e.text.replace(/\n/g, '_') }; });
259
}
260
261
public print(): void {
262
console.log(`assertCompression(${JSON.stringify(this._content)}, ${JSON.stringify(this._edits1)}, ${JSON.stringify(this._edits2)});`);
263
}
264
265
public assert(): void {
266
assertCompression(this._content, this._edits1, this._edits2);
267
}
268
}
269
270
if (GENERATE_TESTS) {
271
let testNumber = 0;
272
while (true) {
273
testNumber++;
274
console.log(`------RUNNING TextChangeCompressor TEST ${testNumber}`);
275
const test = new GeneratedTest();
276
try {
277
test.assert();
278
} catch (err) {
279
console.log(err);
280
test.print();
281
break;
282
}
283
}
284
}
285
});
286
287
suite('TextChange', () => {
288
289
ensureNoDisposablesAreLeakedInTestSuite();
290
291
test('issue #118041: unicode character undo bug', () => {
292
const textChange = new TextChange(428, '', 428, '');
293
const buff = new Uint8Array(textChange.writeSize());
294
textChange.write(buff, 0);
295
const actual: TextChange[] = [];
296
TextChange.read(buff, 0, actual);
297
assert.deepStrictEqual(actual[0], textChange);
298
});
299
300
});
301
302