Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.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 { VSBuffer } from '../../../../../base/common/buffer.js';
8
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
9
import { Mimes } from '../../../../../base/common/mime.js';
10
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
11
import { Position } from '../../../../../editor/common/core/position.js';
12
import { ILanguageService } from '../../../../../editor/common/languages/language.js';
13
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
14
import { IUndoRedoService } from '../../../../../platform/undoRedo/common/undoRedo.js';
15
import { NotebookTextModel } from '../../common/model/notebookTextModel.js';
16
import { CellEditType, CellKind, ICellEditOperation, MOVE_CURSOR_1_LINE_COMMAND, NotebookTextModelChangedEvent, NotebookTextModelWillAddRemoveEvent, SelectionStateType } from '../../common/notebookCommon.js';
17
import { setupInstantiationService, TestCell, valueBytesFromString, withTestNotebook } from './testNotebookEditor.js';
18
19
suite('NotebookTextModel', () => {
20
let disposables: DisposableStore;
21
let instantiationService: TestInstantiationService;
22
let languageService: ILanguageService;
23
24
ensureNoDisposablesAreLeakedInTestSuite();
25
26
suiteSetup(() => {
27
disposables = new DisposableStore();
28
instantiationService = setupInstantiationService(disposables);
29
languageService = instantiationService.get(ILanguageService);
30
instantiationService.spy(IUndoRedoService, 'pushElement');
31
});
32
33
suiteTeardown(() => disposables.dispose());
34
35
test('insert', async function () {
36
await withTestNotebook(
37
[
38
['var a = 1;', 'javascript', CellKind.Code, [], {}],
39
['var b = 2;', 'javascript', CellKind.Code, [], {}],
40
['var c = 3;', 'javascript', CellKind.Code, [], {}],
41
['var d = 4;', 'javascript', CellKind.Code, [], {}]
42
],
43
(editor, _viewModel, ds) => {
44
const textModel = editor.textModel;
45
textModel.applyEdits([
46
{ editType: CellEditType.Replace, index: 1, count: 0, cells: [ds.add(new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService))] },
47
{ editType: CellEditType.Replace, index: 3, count: 0, cells: [ds.add(new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], languageService))] },
48
], true, undefined, () => undefined, undefined, true);
49
50
assert.strictEqual(textModel.cells.length, 6);
51
52
assert.strictEqual(textModel.cells[1].getValue(), 'var e = 5;');
53
assert.strictEqual(textModel.cells[4].getValue(), 'var f = 6;');
54
}
55
);
56
});
57
58
test('multiple inserts at same position', async function () {
59
await withTestNotebook(
60
[
61
['var a = 1;', 'javascript', CellKind.Code, [], {}],
62
['var b = 2;', 'javascript', CellKind.Code, [], {}],
63
['var c = 3;', 'javascript', CellKind.Code, [], {}],
64
['var d = 4;', 'javascript', CellKind.Code, [], {}]
65
],
66
(editor, _viewModel, ds) => {
67
const textModel = editor.textModel;
68
textModel.applyEdits([
69
{ editType: CellEditType.Replace, index: 1, count: 0, cells: [ds.add(new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService))] },
70
{ editType: CellEditType.Replace, index: 1, count: 0, cells: [ds.add(new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], languageService))] },
71
], true, undefined, () => undefined, undefined, true);
72
73
assert.strictEqual(textModel.cells.length, 6);
74
75
assert.strictEqual(textModel.cells[1].getValue(), 'var e = 5;');
76
assert.strictEqual(textModel.cells[2].getValue(), 'var f = 6;');
77
}
78
);
79
});
80
81
test('delete', async function () {
82
await withTestNotebook(
83
[
84
['var a = 1;', 'javascript', CellKind.Code, [], {}],
85
['var b = 2;', 'javascript', CellKind.Code, [], {}],
86
['var c = 3;', 'javascript', CellKind.Code, [], {}],
87
['var d = 4;', 'javascript', CellKind.Code, [], {}]
88
],
89
(editor) => {
90
const textModel = editor.textModel;
91
textModel.applyEdits([
92
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [] },
93
{ editType: CellEditType.Replace, index: 3, count: 1, cells: [] },
94
], true, undefined, () => undefined, undefined, true);
95
96
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
97
assert.strictEqual(textModel.cells[1].getValue(), 'var c = 3;');
98
}
99
);
100
});
101
102
test('delete + insert', async function () {
103
await withTestNotebook(
104
[
105
['var a = 1;', 'javascript', CellKind.Code, [], {}],
106
['var b = 2;', 'javascript', CellKind.Code, [], {}],
107
['var c = 3;', 'javascript', CellKind.Code, [], {}],
108
['var d = 4;', 'javascript', CellKind.Code, [], {}]
109
],
110
(editor, _viewModel, ds) => {
111
const textModel = editor.textModel;
112
textModel.applyEdits([
113
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [] },
114
{ editType: CellEditType.Replace, index: 3, count: 0, cells: [ds.add(new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService))] },
115
], true, undefined, () => undefined, undefined, true);
116
assert.strictEqual(textModel.cells.length, 4);
117
118
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
119
assert.strictEqual(textModel.cells[2].getValue(), 'var e = 5;');
120
}
121
);
122
});
123
124
test('delete + insert at same position', async function () {
125
await withTestNotebook(
126
[
127
['var a = 1;', 'javascript', CellKind.Code, [], {}],
128
['var b = 2;', 'javascript', CellKind.Code, [], {}],
129
['var c = 3;', 'javascript', CellKind.Code, [], {}],
130
['var d = 4;', 'javascript', CellKind.Code, [], {}]
131
],
132
(editor, _viewModel, ds) => {
133
const textModel = editor.textModel;
134
textModel.applyEdits([
135
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [] },
136
{ editType: CellEditType.Replace, index: 1, count: 0, cells: [ds.add(new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService))] },
137
], true, undefined, () => undefined, undefined, true);
138
139
assert.strictEqual(textModel.cells.length, 4);
140
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
141
assert.strictEqual(textModel.cells[1].getValue(), 'var e = 5;');
142
assert.strictEqual(textModel.cells[2].getValue(), 'var c = 3;');
143
}
144
);
145
});
146
147
test('(replace) delete + insert at same position', async function () {
148
await withTestNotebook(
149
[
150
['var a = 1;', 'javascript', CellKind.Code, [], {}],
151
['var b = 2;', 'javascript', CellKind.Code, [], {}],
152
['var c = 3;', 'javascript', CellKind.Code, [], {}],
153
['var d = 4;', 'javascript', CellKind.Code, [], {}]
154
],
155
(editor, _viewModel, ds) => {
156
const textModel = editor.textModel;
157
textModel.applyEdits([
158
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [ds.add(new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService))] },
159
], true, undefined, () => undefined, undefined, true);
160
161
assert.strictEqual(textModel.cells.length, 4);
162
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
163
assert.strictEqual(textModel.cells[1].getValue(), 'var e = 5;');
164
assert.strictEqual(textModel.cells[2].getValue(), 'var c = 3;');
165
}
166
);
167
});
168
169
test('output', async function () {
170
await withTestNotebook(
171
[
172
['var a = 1;', 'javascript', CellKind.Code, [], {}],
173
],
174
(editor) => {
175
const textModel = editor.textModel;
176
177
// invalid index 1
178
assert.throws(() => {
179
textModel.applyEdits([{
180
index: Number.MAX_VALUE,
181
editType: CellEditType.Output,
182
outputs: []
183
}], true, undefined, () => undefined, undefined, true);
184
});
185
186
// invalid index 2
187
assert.throws(() => {
188
textModel.applyEdits([{
189
index: -1,
190
editType: CellEditType.Output,
191
outputs: []
192
}], true, undefined, () => undefined, undefined, true);
193
});
194
195
textModel.applyEdits([{
196
index: 0,
197
editType: CellEditType.Output,
198
outputs: [{
199
outputId: 'someId',
200
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('_Hello_') }]
201
}]
202
}], true, undefined, () => undefined, undefined, true);
203
204
assert.strictEqual(textModel.cells.length, 1);
205
assert.strictEqual(textModel.cells[0].outputs.length, 1);
206
207
// append
208
textModel.applyEdits([{
209
index: 0,
210
editType: CellEditType.Output,
211
append: true,
212
outputs: [{
213
outputId: 'someId2',
214
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('_Hello2_') }]
215
}]
216
}], true, undefined, () => undefined, undefined, true);
217
218
assert.strictEqual(textModel.cells.length, 1);
219
assert.strictEqual(textModel.cells[0].outputs.length, 2);
220
let [first, second] = textModel.cells[0].outputs;
221
assert.strictEqual(first.outputId, 'someId');
222
assert.strictEqual(second.outputId, 'someId2');
223
224
// replace all
225
textModel.applyEdits([{
226
index: 0,
227
editType: CellEditType.Output,
228
outputs: [{
229
outputId: 'someId3',
230
outputs: [{ mime: Mimes.text, data: valueBytesFromString('Last, replaced output') }]
231
}]
232
}], true, undefined, () => undefined, undefined, true);
233
234
assert.strictEqual(textModel.cells.length, 1);
235
assert.strictEqual(textModel.cells[0].outputs.length, 1);
236
[first] = textModel.cells[0].outputs;
237
assert.strictEqual(first.outputId, 'someId3');
238
}
239
);
240
});
241
242
test('multiple append output in one position', async function () {
243
await withTestNotebook(
244
[
245
['var a = 1;', 'javascript', CellKind.Code, [], {}],
246
],
247
(editor) => {
248
const textModel = editor.textModel;
249
250
// append
251
textModel.applyEdits([
252
{
253
index: 0,
254
editType: CellEditType.Output,
255
append: true,
256
outputs: [{
257
outputId: 'append1',
258
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('append 1') }]
259
}]
260
},
261
{
262
index: 0,
263
editType: CellEditType.Output,
264
append: true,
265
outputs: [{
266
outputId: 'append2',
267
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('append 2') }]
268
}]
269
}
270
], true, undefined, () => undefined, undefined, true);
271
272
assert.strictEqual(textModel.cells.length, 1);
273
assert.strictEqual(textModel.cells[0].outputs.length, 2);
274
const [first, second] = textModel.cells[0].outputs;
275
assert.strictEqual(first.outputId, 'append1');
276
assert.strictEqual(second.outputId, 'append2');
277
}
278
);
279
});
280
281
test('append to output created in same batch', async function () {
282
await withTestNotebook(
283
[
284
['var a = 1;', 'javascript', CellKind.Code, [], {}],
285
],
286
(editor) => {
287
const textModel = editor.textModel;
288
289
textModel.applyEdits([
290
{
291
index: 0,
292
editType: CellEditType.Output,
293
append: true,
294
outputs: [{
295
outputId: 'append1',
296
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('append 1') }]
297
}]
298
},
299
{
300
editType: CellEditType.OutputItems,
301
append: true,
302
outputId: 'append1',
303
items: [{
304
mime: Mimes.markdown, data: valueBytesFromString('append 2')
305
}]
306
}
307
], true, undefined, () => undefined, undefined, true);
308
309
assert.strictEqual(textModel.cells.length, 1);
310
assert.strictEqual(textModel.cells[0].outputs.length, 1, 'has 1 output');
311
const [first] = textModel.cells[0].outputs;
312
assert.strictEqual(first.outputId, 'append1');
313
assert.strictEqual(first.outputs.length, 2, 'has 2 items');
314
}
315
);
316
});
317
318
const stdOutMime = 'application/vnd.code.notebook.stdout';
319
const stdErrMime = 'application/vnd.code.notebook.stderr';
320
321
test('appending streaming outputs', async function () {
322
await withTestNotebook(
323
[
324
['var a = 1;', 'javascript', CellKind.Code, [], {}],
325
],
326
(editor) => {
327
const textModel = editor.textModel;
328
329
textModel.applyEdits([
330
{
331
index: 0,
332
editType: CellEditType.Output,
333
append: true,
334
outputs: [{
335
outputId: 'append1',
336
outputs: [{ mime: stdOutMime, data: valueBytesFromString('append 1') }]
337
}]
338
}], true, undefined, () => undefined, undefined, true);
339
const [output] = textModel.cells[0].outputs;
340
assert.strictEqual(output.versionId, 0, 'initial output version should be 0');
341
342
textModel.applyEdits([
343
{
344
editType: CellEditType.OutputItems,
345
append: true,
346
outputId: 'append1',
347
items: [
348
{ mime: stdOutMime, data: valueBytesFromString('append 2') },
349
{ mime: stdOutMime, data: valueBytesFromString('append 3') }
350
]
351
}], true, undefined, () => undefined, undefined, true);
352
assert.strictEqual(output.versionId, 1, 'version should bump per append');
353
354
textModel.applyEdits([
355
{
356
editType: CellEditType.OutputItems,
357
append: true,
358
outputId: 'append1',
359
items: [
360
{ mime: stdOutMime, data: valueBytesFromString('append 4') },
361
{ mime: stdOutMime, data: valueBytesFromString('append 5') }
362
]
363
}], true, undefined, () => undefined, undefined, true);
364
assert.strictEqual(output.versionId, 2, 'version should bump per append');
365
366
assert.strictEqual(textModel.cells.length, 1);
367
assert.strictEqual(textModel.cells[0].outputs.length, 1, 'has 1 output');
368
assert.strictEqual(output.outputId, 'append1');
369
assert.strictEqual(output.outputs.length, 1, 'outputs are compressed');
370
assert.strictEqual(output.outputs[0].data.toString(), 'append 1append 2append 3append 4append 5');
371
assert.strictEqual(output.appendedSinceVersion(0, stdOutMime)?.toString(), 'append 2append 3append 4append 5');
372
assert.strictEqual(output.appendedSinceVersion(1, stdOutMime)?.toString(), 'append 4append 5');
373
assert.strictEqual(output.appendedSinceVersion(2, stdOutMime), undefined);
374
assert.strictEqual(output.appendedSinceVersion(2, stdErrMime), undefined);
375
}
376
);
377
});
378
379
test('replacing streaming outputs', async function () {
380
await withTestNotebook(
381
[
382
['var a = 1;', 'javascript', CellKind.Code, [], {}],
383
],
384
(editor) => {
385
const textModel = editor.textModel;
386
387
textModel.applyEdits([
388
{
389
index: 0,
390
editType: CellEditType.Output,
391
append: true,
392
outputs: [{
393
outputId: 'append1',
394
outputs: [{ mime: stdOutMime, data: valueBytesFromString('append 1') }]
395
}]
396
}], true, undefined, () => undefined, undefined, true);
397
const [output] = textModel.cells[0].outputs;
398
assert.strictEqual(output.versionId, 0, 'initial output version should be 0');
399
400
textModel.applyEdits([
401
{
402
editType: CellEditType.OutputItems,
403
append: true,
404
outputId: 'append1',
405
items: [{
406
mime: stdOutMime, data: valueBytesFromString('append 2')
407
}]
408
}], true, undefined, () => undefined, undefined, true);
409
assert.strictEqual(output.versionId, 1, 'version should bump per append');
410
411
textModel.applyEdits([
412
{
413
editType: CellEditType.OutputItems,
414
append: false,
415
outputId: 'append1',
416
items: [{
417
mime: stdOutMime, data: valueBytesFromString('replace 3')
418
}]
419
}], true, undefined, () => undefined, undefined, true);
420
assert.strictEqual(output.versionId, 2, 'version should bump per replace');
421
422
textModel.applyEdits([
423
{
424
editType: CellEditType.OutputItems,
425
append: true,
426
outputId: 'append1',
427
items: [{
428
mime: stdOutMime, data: valueBytesFromString('append 4')
429
}]
430
}], true, undefined, () => undefined, undefined, true);
431
assert.strictEqual(output.versionId, 3, 'version should bump per append');
432
433
assert.strictEqual(output.outputs[0].data.toString(), 'replace 3append 4');
434
assert.strictEqual(output.appendedSinceVersion(0, stdOutMime), undefined,
435
'replacing output should clear out previous versioned output buffers');
436
assert.strictEqual(output.appendedSinceVersion(1, stdOutMime), undefined,
437
'replacing output should clear out previous versioned output buffers');
438
assert.strictEqual(output.appendedSinceVersion(2, stdOutMime)?.toString(), 'append 4');
439
}
440
);
441
});
442
443
test('appending streaming outputs with move cursor compression', async function () {
444
445
await withTestNotebook(
446
[
447
['var a = 1;', 'javascript', CellKind.Code, [], {}],
448
],
449
(editor) => {
450
const textModel = editor.textModel;
451
452
textModel.applyEdits([
453
{
454
index: 0,
455
editType: CellEditType.Output,
456
append: true,
457
outputs: [{
458
outputId: 'append1',
459
outputs: [
460
{ mime: stdOutMime, data: valueBytesFromString('append 1') },
461
{ mime: stdOutMime, data: valueBytesFromString('\nappend 1') }]
462
}]
463
}], true, undefined, () => undefined, undefined, true);
464
const [output] = textModel.cells[0].outputs;
465
assert.strictEqual(output.versionId, 0, 'initial output version should be 0');
466
467
textModel.applyEdits([
468
{
469
editType: CellEditType.OutputItems,
470
append: true,
471
outputId: 'append1',
472
items: [{
473
mime: stdOutMime, data: valueBytesFromString(MOVE_CURSOR_1_LINE_COMMAND + '\nappend 2')
474
}]
475
}], true, undefined, () => undefined, undefined, true);
476
assert.strictEqual(output.versionId, 1, 'version should bump per append');
477
478
assert.strictEqual(output.outputs[0].data.toString(), 'append 1\nappend 2');
479
assert.strictEqual(output.appendedSinceVersion(0, stdOutMime), undefined,
480
'compressing outputs should clear out previous versioned output buffers');
481
}
482
);
483
});
484
485
test('appending streaming outputs with carraige return compression', async function () {
486
487
await withTestNotebook(
488
[
489
['var a = 1;', 'javascript', CellKind.Code, [], {}],
490
],
491
(editor) => {
492
const textModel = editor.textModel;
493
494
textModel.applyEdits([
495
{
496
index: 0,
497
editType: CellEditType.Output,
498
append: true,
499
outputs: [{
500
outputId: 'append1',
501
outputs: [
502
{ mime: stdOutMime, data: valueBytesFromString('append 1') },
503
{ mime: stdOutMime, data: valueBytesFromString('\nappend 1') }]
504
}]
505
}], true, undefined, () => undefined, undefined, true);
506
const [output] = textModel.cells[0].outputs;
507
assert.strictEqual(output.versionId, 0, 'initial output version should be 0');
508
509
textModel.applyEdits([
510
{
511
editType: CellEditType.OutputItems,
512
append: true,
513
outputId: 'append1',
514
items: [{
515
mime: stdOutMime, data: valueBytesFromString('\rappend 2')
516
}]
517
}], true, undefined, () => undefined, undefined, true);
518
assert.strictEqual(output.versionId, 1, 'version should bump per append');
519
520
assert.strictEqual(output.outputs[0].data.toString(), 'append 1\nappend 2');
521
assert.strictEqual(output.appendedSinceVersion(0, stdOutMime), undefined,
522
'compressing outputs should clear out previous versioned output buffers');
523
}
524
);
525
});
526
527
test('appending multiple different mime streaming outputs', async function () {
528
await withTestNotebook(
529
[
530
['var a = 1;', 'javascript', CellKind.Code, [], {}],
531
],
532
(editor) => {
533
const textModel = editor.textModel;
534
535
textModel.applyEdits([
536
{
537
index: 0,
538
editType: CellEditType.Output,
539
append: true,
540
outputs: [{
541
outputId: 'append1',
542
outputs: [
543
{ mime: stdOutMime, data: valueBytesFromString('stdout 1') },
544
{ mime: stdErrMime, data: valueBytesFromString('stderr 1') }
545
]
546
}]
547
}], true, undefined, () => undefined, undefined, true);
548
const [output] = textModel.cells[0].outputs;
549
assert.strictEqual(output.versionId, 0, 'initial output version should be 0');
550
551
textModel.applyEdits([
552
{
553
editType: CellEditType.OutputItems,
554
append: true,
555
outputId: 'append1',
556
items: [
557
{ mime: stdOutMime, data: valueBytesFromString('stdout 2') },
558
{ mime: stdErrMime, data: valueBytesFromString('stderr 2') }
559
]
560
}], true, undefined, () => undefined, undefined, true);
561
assert.strictEqual(output.versionId, 1, 'version should bump per replace');
562
563
assert.strictEqual(output.appendedSinceVersion(0, stdErrMime)?.toString(), 'stderr 2');
564
assert.strictEqual(output.appendedSinceVersion(0, stdOutMime)?.toString(), 'stdout 2');
565
}
566
);
567
});
568
569
test('metadata', async function () {
570
await withTestNotebook(
571
[
572
['var a = 1;', 'javascript', CellKind.Code, [], {}],
573
],
574
(editor) => {
575
const textModel = editor.textModel;
576
577
// invalid index 1
578
assert.throws(() => {
579
textModel.applyEdits([{
580
index: Number.MAX_VALUE,
581
editType: CellEditType.Metadata,
582
metadata: {}
583
}], true, undefined, () => undefined, undefined, true);
584
});
585
586
// invalid index 2
587
assert.throws(() => {
588
textModel.applyEdits([{
589
index: -1,
590
editType: CellEditType.Metadata,
591
metadata: {}
592
}], true, undefined, () => undefined, undefined, true);
593
});
594
595
textModel.applyEdits([{
596
index: 0,
597
editType: CellEditType.Metadata,
598
metadata: { customProperty: 15 },
599
}], true, undefined, () => undefined, undefined, true);
600
601
textModel.applyEdits([{
602
index: 0,
603
editType: CellEditType.Metadata,
604
metadata: {},
605
}], true, undefined, () => undefined, undefined, true);
606
607
assert.strictEqual(textModel.cells.length, 1);
608
assert.strictEqual(textModel.cells[0].metadata.customProperty, undefined);
609
}
610
);
611
});
612
613
test('partial metadata', async function () {
614
await withTestNotebook(
615
[
616
['var a = 1;', 'javascript', CellKind.Code, [], {}],
617
],
618
(editor) => {
619
const textModel = editor.textModel;
620
621
textModel.applyEdits([{
622
index: 0,
623
editType: CellEditType.PartialMetadata,
624
metadata: { customProperty: 15 },
625
}], true, undefined, () => undefined, undefined, true);
626
627
textModel.applyEdits([{
628
index: 0,
629
editType: CellEditType.PartialMetadata,
630
metadata: {},
631
}], true, undefined, () => undefined, undefined, true);
632
633
assert.strictEqual(textModel.cells.length, 1);
634
assert.strictEqual(textModel.cells[0].metadata.customProperty, 15);
635
}
636
);
637
});
638
639
test('multiple inserts in one edit', async function () {
640
await withTestNotebook(
641
[
642
['var a = 1;', 'javascript', CellKind.Code, [], {}],
643
['var b = 2;', 'javascript', CellKind.Code, [], {}],
644
['var c = 3;', 'javascript', CellKind.Code, [], {}],
645
['var d = 4;', 'javascript', CellKind.Code, [], {}]
646
],
647
(editor, _viewModel, ds) => {
648
const textModel = editor.textModel;
649
let changeEvent: NotebookTextModelChangedEvent | undefined = undefined;
650
const eventListener = textModel.onDidChangeContent(e => {
651
changeEvent = e;
652
});
653
const willChangeEvents: NotebookTextModelWillAddRemoveEvent[] = [];
654
const willChangeListener = textModel.onWillAddRemoveCells(e => {
655
willChangeEvents.push(e);
656
});
657
const version = textModel.versionId;
658
659
textModel.applyEdits([
660
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [] },
661
{ editType: CellEditType.Replace, index: 1, count: 0, cells: [ds.add(new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService))] },
662
], true, undefined, () => ({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }), undefined, true);
663
664
assert.strictEqual(textModel.cells.length, 4);
665
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
666
assert.strictEqual(textModel.cells[1].getValue(), 'var e = 5;');
667
assert.strictEqual(textModel.cells[2].getValue(), 'var c = 3;');
668
669
assert.notStrictEqual(changeEvent, undefined);
670
assert.strictEqual(changeEvent!.rawEvents.length, 2);
671
assert.deepStrictEqual(changeEvent!.endSelectionState?.selections, [{ start: 0, end: 1 }]);
672
assert.strictEqual(willChangeEvents.length, 2);
673
assert.strictEqual(textModel.versionId, version + 1);
674
eventListener.dispose();
675
willChangeListener.dispose();
676
}
677
);
678
});
679
680
test('insert and metadata change in one edit', async function () {
681
await withTestNotebook(
682
[
683
['var a = 1;', 'javascript', CellKind.Code, [], {}],
684
['var b = 2;', 'javascript', CellKind.Code, [], {}],
685
['var c = 3;', 'javascript', CellKind.Code, [], {}],
686
['var d = 4;', 'javascript', CellKind.Code, [], {}]
687
],
688
(editor) => {
689
const textModel = editor.textModel;
690
let changeEvent: NotebookTextModelChangedEvent | undefined = undefined;
691
const eventListener = textModel.onDidChangeContent(e => {
692
changeEvent = e;
693
});
694
const willChangeEvents: NotebookTextModelWillAddRemoveEvent[] = [];
695
const willChangeListener = textModel.onWillAddRemoveCells(e => {
696
willChangeEvents.push(e);
697
});
698
699
const version = textModel.versionId;
700
701
textModel.applyEdits([
702
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [] },
703
{
704
index: 0,
705
editType: CellEditType.Metadata,
706
metadata: {},
707
}
708
], true, undefined, () => ({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }), undefined, true);
709
710
assert.notStrictEqual(changeEvent, undefined);
711
assert.strictEqual(changeEvent!.rawEvents.length, 2);
712
assert.deepStrictEqual(changeEvent!.endSelectionState?.selections, [{ start: 0, end: 1 }]);
713
assert.strictEqual(willChangeEvents.length, 1);
714
assert.strictEqual(textModel.versionId, version + 1);
715
eventListener.dispose();
716
willChangeListener.dispose();
717
}
718
);
719
});
720
721
722
test('Updating appending/updating output in Notebooks does not work as expected #117273', async function () {
723
await withTestNotebook([
724
['var a = 1;', 'javascript', CellKind.Code, [], {}]
725
], (editor) => {
726
const model = editor.textModel;
727
728
assert.strictEqual(model.cells.length, 1);
729
assert.strictEqual(model.cells[0].outputs.length, 0);
730
731
const success1 = model.applyEdits(
732
[{
733
editType: CellEditType.Output, index: 0, outputs: [
734
{ outputId: 'out1', outputs: [{ mime: 'application/x.notebook.stream', data: VSBuffer.wrap(new Uint8Array([1])) }] }
735
],
736
append: false
737
}], true, undefined, () => undefined, undefined, false
738
);
739
740
assert.ok(success1);
741
assert.strictEqual(model.cells[0].outputs.length, 1);
742
743
const success2 = model.applyEdits(
744
[{
745
editType: CellEditType.Output, index: 0, outputs: [
746
{ outputId: 'out2', outputs: [{ mime: 'application/x.notebook.stream', data: VSBuffer.wrap(new Uint8Array([1])) }] }
747
],
748
append: true
749
}], true, undefined, () => undefined, undefined, false
750
);
751
752
assert.ok(success2);
753
assert.strictEqual(model.cells[0].outputs.length, 2);
754
});
755
});
756
757
test('Clearing output of an empty notebook makes it dirty #119608', async function () {
758
await withTestNotebook([
759
['var a = 1;', 'javascript', CellKind.Code, [], {}],
760
['var b = 2;', 'javascript', CellKind.Code, [], {}]
761
], (editor, _, ds) => {
762
const model = editor.textModel;
763
764
let event: NotebookTextModelChangedEvent | undefined;
765
766
ds.add(model.onDidChangeContent(e => { event = e; }));
767
768
{
769
// 1: add ouput -> event
770
const success = model.applyEdits(
771
[{
772
editType: CellEditType.Output, index: 0, outputs: [
773
{ outputId: 'out1', outputs: [{ mime: 'application/x.notebook.stream', data: VSBuffer.wrap(new Uint8Array([1])) }] }
774
],
775
append: false
776
}], true, undefined, () => undefined, undefined, false
777
);
778
779
assert.ok(success);
780
assert.strictEqual(model.cells[0].outputs.length, 1);
781
assert.ok(event);
782
}
783
784
{
785
// 2: clear all output w/ output -> event
786
event = undefined;
787
const success = model.applyEdits(
788
[{
789
editType: CellEditType.Output,
790
index: 0,
791
outputs: [],
792
append: false
793
}, {
794
editType: CellEditType.Output,
795
index: 1,
796
outputs: [],
797
append: false
798
}], true, undefined, () => undefined, undefined, false
799
);
800
assert.ok(success);
801
assert.ok(event);
802
}
803
804
{
805
// 2: clear all output wo/ output -> NO event
806
event = undefined;
807
const success = model.applyEdits(
808
[{
809
editType: CellEditType.Output,
810
index: 0,
811
outputs: [],
812
append: false
813
}, {
814
editType: CellEditType.Output,
815
index: 1,
816
outputs: [],
817
append: false
818
}], true, undefined, () => undefined, undefined, false
819
);
820
821
assert.ok(success);
822
assert.ok(event === undefined);
823
}
824
});
825
});
826
827
test('Cell metadata/output change should update version id and alternative id #121807', async function () {
828
await withTestNotebook([
829
['var a = 1;', 'javascript', CellKind.Code, [], {}],
830
['var b = 2;', 'javascript', CellKind.Code, [], {}]
831
], async (editor, viewModel) => {
832
assert.strictEqual(editor.textModel.versionId, 0);
833
const firstAltVersion = '0_0,1;1,1';
834
assert.strictEqual(editor.textModel.alternativeVersionId, firstAltVersion);
835
editor.textModel.applyEdits([
836
{
837
index: 0,
838
editType: CellEditType.Metadata,
839
metadata: {
840
inputCollapsed: true
841
}
842
}
843
], true, undefined, () => undefined, undefined, true);
844
assert.strictEqual(editor.textModel.versionId, 1);
845
assert.notStrictEqual(editor.textModel.alternativeVersionId, firstAltVersion);
846
const secondAltVersion = '1_0,1;1,1';
847
assert.strictEqual(editor.textModel.alternativeVersionId, secondAltVersion);
848
849
await viewModel.undo();
850
assert.strictEqual(editor.textModel.versionId, 2);
851
assert.strictEqual(editor.textModel.alternativeVersionId, firstAltVersion);
852
853
await viewModel.redo();
854
assert.strictEqual(editor.textModel.versionId, 3);
855
assert.notStrictEqual(editor.textModel.alternativeVersionId, firstAltVersion);
856
assert.strictEqual(editor.textModel.alternativeVersionId, secondAltVersion);
857
858
editor.textModel.applyEdits([
859
{
860
index: 1,
861
editType: CellEditType.Metadata,
862
metadata: {
863
inputCollapsed: true
864
}
865
}
866
], true, undefined, () => undefined, undefined, true);
867
assert.strictEqual(editor.textModel.versionId, 4);
868
assert.strictEqual(editor.textModel.alternativeVersionId, '4_0,1;1,1');
869
870
await viewModel.undo();
871
assert.strictEqual(editor.textModel.versionId, 5);
872
assert.strictEqual(editor.textModel.alternativeVersionId, secondAltVersion);
873
874
});
875
});
876
877
test('metadata changes on newly added cells should combine their undo operations', async function () {
878
await withTestNotebook([
879
['var a = 1;', 'javascript', CellKind.Code, [], {}]
880
], async (editor, viewModel, ds) => {
881
const textModel = editor.textModel;
882
editor.textModel.applyEdits([
883
{
884
editType: CellEditType.Replace, index: 1, count: 0, cells: [
885
ds.add(new TestCell(textModel.viewType, 1, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)),
886
ds.add(new TestCell(textModel.viewType, 2, 'var f = 6;', 'javascript', CellKind.Code, [], languageService))
887
]
888
},
889
], true, undefined, () => undefined, undefined, true);
890
891
assert.strictEqual(textModel.cells.length, 3);
892
893
editor.textModel.applyEdits([
894
{ editType: CellEditType.Metadata, index: 1, metadata: { id: '123' } },
895
], true, undefined, () => undefined, undefined, true);
896
897
assert.strictEqual(textModel.cells[1].metadata.id, '123');
898
899
await viewModel.undo();
900
901
assert.strictEqual(textModel.cells.length, 1);
902
903
await viewModel.redo();
904
905
assert.strictEqual(textModel.cells.length, 3);
906
});
907
});
908
909
test('changes with non-metadata edit should not combine their undo operations', async function () {
910
await withTestNotebook([
911
['var a = 1;', 'javascript', CellKind.Code, [], {}]
912
], async (editor, viewModel, ds) => {
913
const textModel = editor.textModel;
914
editor.textModel.applyEdits([
915
{
916
editType: CellEditType.Replace, index: 1, count: 0, cells: [
917
ds.add(new TestCell(textModel.viewType, 1, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)),
918
ds.add(new TestCell(textModel.viewType, 2, 'var f = 6;', 'javascript', CellKind.Code, [], languageService))
919
]
920
},
921
], true, undefined, () => undefined, undefined, true);
922
923
assert.strictEqual(textModel.cells.length, 3);
924
925
editor.textModel.applyEdits([
926
{ editType: CellEditType.Metadata, index: 1, metadata: { id: '123' } },
927
{
928
editType: CellEditType.Output, handle: 0, append: true, outputs: [{
929
outputId: 'newOutput',
930
outputs: [{ mime: Mimes.text, data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }]
931
}]
932
}
933
], true, undefined, () => undefined, undefined, true);
934
935
assert.strictEqual(textModel.cells[1].metadata.id, '123');
936
937
await viewModel.undo();
938
939
assert.strictEqual(textModel.cells.length, 3);
940
941
await viewModel.undo();
942
943
assert.strictEqual(textModel.cells.length, 1);
944
});
945
});
946
947
test('Destructive sorting in _doApplyEdits #121994', async function () {
948
await withTestNotebook([
949
['var a = 1;', 'javascript', CellKind.Code, [{ outputId: 'i42', outputs: [{ mime: 'm/ime', data: valueBytesFromString('test') }] }], {}]
950
], async (editor) => {
951
952
const notebook = editor.textModel;
953
954
assert.strictEqual(notebook.cells[0].outputs.length, 1);
955
assert.strictEqual(notebook.cells[0].outputs[0].outputs.length, 1);
956
assert.deepStrictEqual(notebook.cells[0].outputs[0].outputs[0].data, valueBytesFromString('test'));
957
958
const edits: ICellEditOperation[] = [
959
{
960
editType: CellEditType.Output, handle: 0, outputs: []
961
},
962
{
963
editType: CellEditType.Output, handle: 0, append: true, outputs: [{
964
outputId: 'newOutput',
965
outputs: [{ mime: Mimes.text, data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }]
966
}]
967
}
968
];
969
970
editor.textModel.applyEdits(edits, true, undefined, () => undefined, undefined, true);
971
972
assert.strictEqual(notebook.cells[0].outputs.length, 1);
973
assert.strictEqual(notebook.cells[0].outputs[0].outputs.length, 2);
974
});
975
});
976
977
test('Destructive sorting in _doApplyEdits #121994. cell splice between output changes', async function () {
978
await withTestNotebook([
979
['var a = 1;', 'javascript', CellKind.Code, [{ outputId: 'i42', outputs: [{ mime: 'm/ime', data: valueBytesFromString('test') }] }], {}],
980
['var b = 2;', 'javascript', CellKind.Code, [{ outputId: 'i43', outputs: [{ mime: 'm/ime', data: valueBytesFromString('test') }] }], {}],
981
['var c = 3;', 'javascript', CellKind.Code, [{ outputId: 'i44', outputs: [{ mime: 'm/ime', data: valueBytesFromString('test') }] }], {}]
982
], async (editor) => {
983
const notebook = editor.textModel;
984
985
const edits: ICellEditOperation[] = [
986
{
987
editType: CellEditType.Output, index: 0, outputs: []
988
},
989
{
990
editType: CellEditType.Replace, index: 1, count: 1, cells: []
991
},
992
{
993
editType: CellEditType.Output, index: 2, append: true, outputs: [{
994
outputId: 'newOutput',
995
outputs: [{ mime: Mimes.text, data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }]
996
}]
997
}
998
];
999
1000
editor.textModel.applyEdits(edits, true, undefined, () => undefined, undefined, true);
1001
1002
assert.strictEqual(notebook.cells.length, 2);
1003
assert.strictEqual(notebook.cells[0].outputs.length, 0);
1004
assert.strictEqual(notebook.cells[1].outputs.length, 2);
1005
assert.strictEqual(notebook.cells[1].outputs[0].outputId, 'i44');
1006
assert.strictEqual(notebook.cells[1].outputs[1].outputId, 'newOutput');
1007
});
1008
});
1009
1010
test('Destructive sorting in _doApplyEdits #121994. cell splice between output changes 2', async function () {
1011
await withTestNotebook([
1012
['var a = 1;', 'javascript', CellKind.Code, [{ outputId: 'i42', outputs: [{ mime: 'm/ime', data: valueBytesFromString('test') }] }], {}],
1013
['var b = 2;', 'javascript', CellKind.Code, [{ outputId: 'i43', outputs: [{ mime: 'm/ime', data: valueBytesFromString('test') }] }], {}],
1014
['var c = 3;', 'javascript', CellKind.Code, [{ outputId: 'i44', outputs: [{ mime: 'm/ime', data: valueBytesFromString('test') }] }], {}]
1015
], async (editor) => {
1016
const notebook = editor.textModel;
1017
1018
const edits: ICellEditOperation[] = [
1019
{
1020
editType: CellEditType.Output, index: 1, append: true, outputs: [{
1021
outputId: 'newOutput',
1022
outputs: [{ mime: Mimes.text, data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }]
1023
}]
1024
},
1025
{
1026
editType: CellEditType.Replace, index: 1, count: 1, cells: []
1027
},
1028
{
1029
editType: CellEditType.Output, index: 1, append: true, outputs: [{
1030
outputId: 'newOutput2',
1031
outputs: [{ mime: Mimes.text, data: valueBytesFromString('cba') }, { mime: 'application/foo', data: valueBytesFromString('cba') }]
1032
}]
1033
}
1034
];
1035
1036
editor.textModel.applyEdits(edits, true, undefined, () => undefined, undefined, true);
1037
1038
assert.strictEqual(notebook.cells.length, 2);
1039
assert.strictEqual(notebook.cells[0].outputs.length, 1);
1040
assert.strictEqual(notebook.cells[1].outputs.length, 1);
1041
assert.strictEqual(notebook.cells[1].outputs[0].outputId, 'i44');
1042
});
1043
});
1044
1045
test('Output edits splice', async function () {
1046
await withTestNotebook([
1047
['var a = 1;', 'javascript', CellKind.Code, [], {}]
1048
], (editor) => {
1049
const model = editor.textModel;
1050
1051
assert.strictEqual(model.cells.length, 1);
1052
assert.strictEqual(model.cells[0].outputs.length, 0);
1053
1054
const success1 = model.applyEdits(
1055
[{
1056
editType: CellEditType.Output, index: 0, outputs: [
1057
{ outputId: 'out1', outputs: [{ mime: 'application/x.notebook.stream', data: valueBytesFromString('1') }] },
1058
{ outputId: 'out2', outputs: [{ mime: 'application/x.notebook.stream', data: valueBytesFromString('2') }] },
1059
{ outputId: 'out3', outputs: [{ mime: 'application/x.notebook.stream', data: valueBytesFromString('3') }] },
1060
{ outputId: 'out4', outputs: [{ mime: 'application/x.notebook.stream', data: valueBytesFromString('4') }] }
1061
],
1062
append: false
1063
}], true, undefined, () => undefined, undefined, false
1064
);
1065
1066
assert.ok(success1);
1067
assert.strictEqual(model.cells[0].outputs.length, 4);
1068
1069
const success2 = model.applyEdits(
1070
[{
1071
editType: CellEditType.Output, index: 0, outputs: [
1072
{ outputId: 'out1', outputs: [{ mime: 'application/x.notebook.stream', data: valueBytesFromString('1') }] },
1073
{ outputId: 'out5', outputs: [{ mime: 'application/x.notebook.stream', data: valueBytesFromString('5') }] },
1074
{ outputId: 'out3', outputs: [{ mime: 'application/x.notebook.stream', data: valueBytesFromString('3') }] },
1075
{ outputId: 'out6', outputs: [{ mime: 'application/x.notebook.stream', data: valueBytesFromString('6') }] }
1076
],
1077
append: false
1078
}], true, undefined, () => undefined, undefined, false
1079
);
1080
1081
assert.ok(success2);
1082
assert.strictEqual(model.cells[0].outputs.length, 4);
1083
assert.strictEqual(model.cells[0].outputs[0].outputId, 'out1');
1084
assert.strictEqual(model.cells[0].outputs[1].outputId, 'out5');
1085
assert.strictEqual(model.cells[0].outputs[2].outputId, 'out3');
1086
assert.strictEqual(model.cells[0].outputs[3].outputId, 'out6');
1087
});
1088
});
1089
1090
test('computeEdits no insert', async function () {
1091
await withTestNotebook([
1092
['var a = 1;', 'javascript', CellKind.Code, [], {}]
1093
], (editor) => {
1094
const model = editor.textModel;
1095
const edits = NotebookTextModel.computeEdits(model, [
1096
{ source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined }
1097
]);
1098
1099
assert.deepStrictEqual(edits, [
1100
{ editType: CellEditType.Metadata, index: 0, metadata: {} }
1101
]);
1102
});
1103
});
1104
1105
test('computeEdits cell content changed', async function () {
1106
await withTestNotebook([
1107
['var a = 1;', 'javascript', CellKind.Code, [], {}]
1108
], (editor) => {
1109
const model = editor.textModel;
1110
const cells = [
1111
{ source: 'var a = 2;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined }
1112
];
1113
const edits = NotebookTextModel.computeEdits(model, cells);
1114
1115
assert.deepStrictEqual(edits, [
1116
{ editType: CellEditType.Replace, index: 0, count: 1, cells },
1117
]);
1118
});
1119
});
1120
1121
test('computeEdits last cell content changed', async function () {
1122
await withTestNotebook([
1123
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1124
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1125
], (editor) => {
1126
const model = editor.textModel;
1127
const cells = [
1128
{ source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined },
1129
{ source: 'var b = 2;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined }
1130
];
1131
const edits = NotebookTextModel.computeEdits(model, cells);
1132
1133
assert.deepStrictEqual(edits, [
1134
{ editType: CellEditType.Metadata, index: 0, metadata: {} },
1135
{ editType: CellEditType.Replace, index: 1, count: 1, cells: cells.slice(1) },
1136
]);
1137
});
1138
});
1139
test('computeEdits first cell content changed', async function () {
1140
await withTestNotebook([
1141
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1142
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1143
], (editor) => {
1144
const model = editor.textModel;
1145
const cells = [
1146
{ source: 'var a = 2;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined },
1147
{ source: 'var b = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined }
1148
];
1149
const edits = NotebookTextModel.computeEdits(model, cells);
1150
1151
assert.deepStrictEqual(edits, [
1152
{ editType: CellEditType.Replace, index: 0, count: 1, cells: cells.slice(0, 1) },
1153
{ editType: CellEditType.Metadata, index: 1, metadata: {} },
1154
]);
1155
});
1156
});
1157
1158
test('computeEdits middle cell content changed', async function () {
1159
await withTestNotebook([
1160
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1161
['var b = 1;', 'javascript', CellKind.Code, [], {}],
1162
['var c = 1;', 'javascript', CellKind.Code, [], {}],
1163
], (editor) => {
1164
const model = editor.textModel;
1165
const cells = [
1166
{ source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined },
1167
{ source: 'var b = 2;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined },
1168
{ source: 'var c = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined }
1169
];
1170
const edits = NotebookTextModel.computeEdits(model, cells);
1171
1172
assert.deepStrictEqual(edits, [
1173
{ editType: CellEditType.Metadata, index: 0, metadata: {} },
1174
{ editType: CellEditType.Replace, index: 1, count: 1, cells: cells.slice(1, 2) },
1175
{ editType: CellEditType.Metadata, index: 2, metadata: {} },
1176
]);
1177
});
1178
});
1179
1180
test('computeEdits cell metadata changed', async function () {
1181
await withTestNotebook([
1182
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1183
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1184
], (editor) => {
1185
const model = editor.textModel;
1186
const cells = [
1187
{ source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: { name: 'foo' } },
1188
{ source: 'var b = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined }
1189
];
1190
const edits = NotebookTextModel.computeEdits(model, cells);
1191
1192
assert.deepStrictEqual(edits, [
1193
{ editType: CellEditType.Metadata, index: 0, metadata: { name: 'foo' } },
1194
{ editType: CellEditType.Metadata, index: 1, metadata: {} },
1195
]);
1196
});
1197
});
1198
1199
test('computeEdits cell language changed', async function () {
1200
await withTestNotebook([
1201
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1202
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1203
], (editor) => {
1204
const model = editor.textModel;
1205
const cells = [
1206
{ source: 'var a = 1;', language: 'typescript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined },
1207
{ source: 'var b = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined }
1208
];
1209
const edits = NotebookTextModel.computeEdits(model, cells);
1210
1211
assert.deepStrictEqual(edits, [
1212
{ editType: CellEditType.Replace, index: 0, count: 1, cells: cells.slice(0, 1) },
1213
{ editType: CellEditType.Metadata, index: 1, metadata: {} },
1214
]);
1215
});
1216
});
1217
1218
test('computeEdits cell kind changed', async function () {
1219
await withTestNotebook([
1220
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1221
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1222
], (editor) => {
1223
const model = editor.textModel;
1224
const cells = [
1225
{ source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined },
1226
{ source: 'var b = 1;', language: 'javascript', cellKind: CellKind.Markup, mime: undefined, outputs: [], metadata: undefined }
1227
];
1228
const edits = NotebookTextModel.computeEdits(model, cells);
1229
1230
assert.deepStrictEqual(edits, [
1231
{ editType: CellEditType.Metadata, index: 0, metadata: {} },
1232
{ editType: CellEditType.Replace, index: 1, count: 1, cells: cells.slice(1) },
1233
]);
1234
});
1235
});
1236
1237
test('computeEdits cell metadata & content changed', async function () {
1238
await withTestNotebook([
1239
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1240
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1241
], (editor) => {
1242
const model = editor.textModel;
1243
const cells = [
1244
{ source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: { name: 'foo' } },
1245
{ source: 'var b = 2;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: { name: 'bar' } }
1246
];
1247
const edits = NotebookTextModel.computeEdits(model, cells);
1248
1249
assert.deepStrictEqual(edits, [
1250
{ editType: CellEditType.Metadata, index: 0, metadata: { name: 'foo' } },
1251
{ editType: CellEditType.Replace, index: 1, count: 1, cells: cells.slice(1) }
1252
]);
1253
});
1254
});
1255
1256
test('computeEdits cell content changed while executing', async function () {
1257
await withTestNotebook([
1258
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1259
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1260
], (editor) => {
1261
const model = editor.textModel;
1262
const cells = [
1263
{ source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: {} },
1264
{ source: 'var b = 2;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: {} }
1265
];
1266
const edits = NotebookTextModel.computeEdits(model, cells, [model.cells[1].handle]);
1267
1268
assert.deepStrictEqual(edits, [
1269
{ editType: CellEditType.Metadata, index: 0, metadata: {} },
1270
{ editType: CellEditType.Replace, index: 1, count: 1, cells: cells.slice(1) }
1271
]);
1272
});
1273
});
1274
1275
test('computeEdits cell internal metadata changed', async function () {
1276
await withTestNotebook([
1277
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1278
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1279
], (editor) => {
1280
const model = editor.textModel;
1281
const cells = [
1282
{ source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined, internalMetadata: { executionOrder: 1 } },
1283
{ source: 'var b = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined }
1284
];
1285
const edits = NotebookTextModel.computeEdits(model, cells);
1286
1287
assert.deepStrictEqual(edits, [
1288
{ editType: CellEditType.Replace, index: 0, count: 1, cells: cells.slice(0, 1) },
1289
{ editType: CellEditType.Metadata, index: 1, metadata: {} },
1290
]);
1291
});
1292
});
1293
1294
test('computeEdits cell internal metadata changed while executing', async function () {
1295
await withTestNotebook([
1296
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1297
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1298
], (editor) => {
1299
const model = editor.textModel;
1300
const cells = [
1301
{ source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: {} },
1302
{ source: 'var b = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: {}, internalMetadata: { executionOrder: 1 } }
1303
];
1304
const edits = NotebookTextModel.computeEdits(model, cells, [model.cells[1].handle]);
1305
1306
assert.deepStrictEqual(edits, [
1307
{ editType: CellEditType.Metadata, index: 0, metadata: {} },
1308
{ editType: CellEditType.Metadata, index: 1, metadata: {} },
1309
]);
1310
});
1311
});
1312
1313
test('computeEdits cell insertion', async function () {
1314
await withTestNotebook([
1315
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1316
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1317
], (editor) => {
1318
const model = editor.textModel;
1319
const cells = [
1320
{ source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined, },
1321
{ source: 'var c = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: undefined, },
1322
{ source: 'var b = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: { foo: 'bar' } }
1323
];
1324
const edits = NotebookTextModel.computeEdits(model, cells);
1325
1326
assert.deepStrictEqual(edits, [
1327
{ editType: CellEditType.Metadata, index: 0, metadata: {} },
1328
{ editType: CellEditType.Replace, index: 1, count: 0, cells: cells.slice(1, 2) },
1329
{ editType: CellEditType.Metadata, index: 1, metadata: { foo: 'bar' } },
1330
]);
1331
1332
model.applyEdits(edits, true, undefined, () => undefined, undefined, true);
1333
assert.equal(model.cells.length, 3);
1334
assert.equal(model.cells[1].getValue(), 'var c = 1;');
1335
assert.equal(model.cells[2].getValue(), 'var b = 1;');
1336
assert.deepStrictEqual(model.cells[2].metadata, { foo: 'bar' });
1337
});
1338
});
1339
1340
test('computeEdits output changed', async function () {
1341
await withTestNotebook([
1342
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1343
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1344
], (editor) => {
1345
const model = editor.textModel;
1346
const cells = [
1347
{
1348
source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [{
1349
outputId: 'someId',
1350
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('_World_') }]
1351
}], metadata: undefined,
1352
},
1353
{ source: 'var b = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: { foo: 'bar' } }
1354
];
1355
const edits = NotebookTextModel.computeEdits(model, cells);
1356
1357
assert.deepStrictEqual(edits, [
1358
{ editType: CellEditType.Metadata, index: 0, metadata: {} },
1359
{
1360
editType: CellEditType.Output, index: 0, outputs: [{
1361
outputId: 'someId',
1362
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('_World_') }]
1363
}], append: false
1364
},
1365
{ editType: CellEditType.Metadata, index: 1, metadata: { foo: 'bar' } },
1366
]);
1367
1368
model.applyEdits(edits, true, undefined, () => undefined, undefined, true);
1369
assert.equal(model.cells.length, 2);
1370
assert.strictEqual(model.cells[0].outputs.length, 1);
1371
assert.equal(model.cells[0].outputs[0].outputId, 'someId');
1372
assert.equal(model.cells[0].outputs[0].outputs[0].data.toString(), '_World_');
1373
});
1374
});
1375
1376
test('computeEdits output items changed', async function () {
1377
await withTestNotebook([
1378
['var a = 1;', 'javascript', CellKind.Code, [{
1379
outputId: 'someId',
1380
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('_Hello_') }]
1381
}], {}],
1382
['var b = 1;', 'javascript', CellKind.Code, [], {}]
1383
], (editor) => {
1384
const model = editor.textModel;
1385
const cells = [
1386
{
1387
source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [{
1388
outputId: 'someId',
1389
outputs: [{ mime: Mimes.markdown, data: valueBytesFromString('_World_') }]
1390
}], metadata: undefined,
1391
},
1392
{ source: 'var b = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: { foo: 'bar' } }
1393
];
1394
const edits = NotebookTextModel.computeEdits(model, cells);
1395
1396
assert.deepStrictEqual(edits, [
1397
{ editType: CellEditType.Metadata, index: 0, metadata: {} },
1398
{ editType: CellEditType.OutputItems, outputId: 'someId', items: [{ mime: Mimes.markdown, data: valueBytesFromString('_World_') }], append: false },
1399
{ editType: CellEditType.Metadata, index: 1, metadata: { foo: 'bar' } },
1400
]);
1401
1402
model.applyEdits(edits, true, undefined, () => undefined, undefined, true);
1403
assert.equal(model.cells.length, 2);
1404
assert.strictEqual(model.cells[0].outputs.length, 1);
1405
assert.equal(model.cells[0].outputs[0].outputId, 'someId');
1406
assert.equal(model.cells[0].outputs[0].outputs[0].data.toString(), '_World_');
1407
});
1408
});
1409
test('Append multiple text/plain output items', async function () {
1410
await withTestNotebook([
1411
['var a = 1;', 'javascript', CellKind.Code, [{
1412
outputId: '1',
1413
outputs: [{ mime: 'text/plain', data: valueBytesFromString('foo') }]
1414
}], {}]
1415
], (editor) => {
1416
const model = editor.textModel;
1417
const edits: ICellEditOperation[] = [
1418
{
1419
editType: CellEditType.OutputItems,
1420
outputId: '1',
1421
append: true,
1422
items: [{ mime: 'text/plain', data: VSBuffer.fromString('bar') }, { mime: 'text/plain', data: VSBuffer.fromString('baz') }]
1423
}
1424
];
1425
model.applyEdits(edits, true, undefined, () => undefined, undefined, true);
1426
assert.equal(model.cells.length, 1);
1427
assert.equal(model.cells[0].outputs.length, 1);
1428
assert.equal(model.cells[0].outputs[0].outputs.length, 3);
1429
assert.equal(model.cells[0].outputs[0].outputs[0].mime, 'text/plain');
1430
assert.equal(model.cells[0].outputs[0].outputs[0].data.toString(), 'foo');
1431
assert.equal(model.cells[0].outputs[0].outputs[1].mime, 'text/plain');
1432
assert.equal(model.cells[0].outputs[0].outputs[1].data.toString(), 'bar');
1433
assert.equal(model.cells[0].outputs[0].outputs[2].mime, 'text/plain');
1434
assert.equal(model.cells[0].outputs[0].outputs[2].data.toString(), 'baz');
1435
});
1436
});
1437
test('Append multiple stdout stream output items to an output with another mime', async function () {
1438
await withTestNotebook([
1439
['var a = 1;', 'javascript', CellKind.Code, [{
1440
outputId: '1',
1441
outputs: [{ mime: 'text/plain', data: valueBytesFromString('foo') }]
1442
}], {}]
1443
], (editor) => {
1444
const model = editor.textModel;
1445
const edits: ICellEditOperation[] = [
1446
{
1447
editType: CellEditType.OutputItems,
1448
outputId: '1',
1449
append: true,
1450
items: [{ mime: 'application/vnd.code.notebook.stdout', data: VSBuffer.fromString('bar') }, { mime: 'application/vnd.code.notebook.stdout', data: VSBuffer.fromString('baz') }]
1451
}
1452
];
1453
model.applyEdits(edits, true, undefined, () => undefined, undefined, true);
1454
assert.equal(model.cells.length, 1);
1455
assert.equal(model.cells[0].outputs.length, 1);
1456
assert.equal(model.cells[0].outputs[0].outputs.length, 3);
1457
assert.equal(model.cells[0].outputs[0].outputs[0].mime, 'text/plain');
1458
assert.equal(model.cells[0].outputs[0].outputs[0].data.toString(), 'foo');
1459
assert.equal(model.cells[0].outputs[0].outputs[1].mime, 'application/vnd.code.notebook.stdout');
1460
assert.equal(model.cells[0].outputs[0].outputs[1].data.toString(), 'bar');
1461
assert.equal(model.cells[0].outputs[0].outputs[2].mime, 'application/vnd.code.notebook.stdout');
1462
assert.equal(model.cells[0].outputs[0].outputs[2].data.toString(), 'baz');
1463
});
1464
});
1465
test('Compress multiple stdout stream output items', async function () {
1466
await withTestNotebook([
1467
['var a = 1;', 'javascript', CellKind.Code, [{
1468
outputId: '1',
1469
outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('foo') }]
1470
}], {}]
1471
], (editor) => {
1472
const model = editor.textModel;
1473
const edits: ICellEditOperation[] = [
1474
{
1475
editType: CellEditType.OutputItems,
1476
outputId: '1',
1477
append: true,
1478
items: [{ mime: 'application/vnd.code.notebook.stdout', data: VSBuffer.fromString('bar') }, { mime: 'application/vnd.code.notebook.stdout', data: VSBuffer.fromString('baz') }]
1479
}
1480
];
1481
model.applyEdits(edits, true, undefined, () => undefined, undefined, true);
1482
assert.equal(model.cells.length, 1);
1483
assert.equal(model.cells[0].outputs.length, 1);
1484
assert.equal(model.cells[0].outputs[0].outputs.length, 1);
1485
assert.equal(model.cells[0].outputs[0].outputs[0].mime, 'application/vnd.code.notebook.stdout');
1486
assert.equal(model.cells[0].outputs[0].outputs[0].data.toString(), 'foobarbaz');
1487
});
1488
1489
});
1490
test('Compress multiple stderr stream output items', async function () {
1491
await withTestNotebook([
1492
['var a = 1;', 'javascript', CellKind.Code, [{
1493
outputId: '1',
1494
outputs: [{ mime: 'application/vnd.code.notebook.stderr', data: valueBytesFromString('foo') }]
1495
}], {}]
1496
], (editor) => {
1497
const model = editor.textModel;
1498
const edits: ICellEditOperation[] = [
1499
{
1500
editType: CellEditType.OutputItems,
1501
outputId: '1',
1502
append: true,
1503
items: [{ mime: 'application/vnd.code.notebook.stderr', data: VSBuffer.fromString('bar') }, { mime: 'application/vnd.code.notebook.stderr', data: VSBuffer.fromString('baz') }]
1504
}
1505
];
1506
model.applyEdits(edits, true, undefined, () => undefined, undefined, true);
1507
assert.equal(model.cells.length, 1);
1508
assert.equal(model.cells[0].outputs.length, 1);
1509
assert.equal(model.cells[0].outputs[0].outputs.length, 1);
1510
assert.equal(model.cells[0].outputs[0].outputs[0].mime, 'application/vnd.code.notebook.stderr');
1511
assert.equal(model.cells[0].outputs[0].outputs[0].data.toString(), 'foobarbaz');
1512
});
1513
1514
});
1515
1516
test('findNextMatch', async function () {
1517
await withTestNotebook(
1518
[
1519
['var a = 1;', 'javascript', CellKind.Code, [], {}],
1520
['var b = 2;', 'javascript', CellKind.Code, [], {}],
1521
['var c = 3;', 'javascript', CellKind.Code, [], {}],
1522
['var d = 4;', 'javascript', CellKind.Code, [], {}]
1523
],
1524
(editor, viewModel) => {
1525
const notebookModel = viewModel.notebookDocument;
1526
1527
// Test case 1: Find 'var' starting from the first cell
1528
let findMatch = notebookModel.findNextMatch('var', { cellIndex: 0, position: new Position(1, 1) }, false, false, null);
1529
assert.ok(findMatch);
1530
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1531
assert.strictEqual(findMatch!.match.range.startColumn, 1);
1532
1533
// Test case 2: Find 'b' starting from the second cell
1534
findMatch = notebookModel.findNextMatch('b', { cellIndex: 1, position: new Position(1, 1) }, false, false, null);
1535
assert.ok(findMatch);
1536
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1537
assert.strictEqual(findMatch!.match.range.startColumn, 5);
1538
1539
// Test case 3: Find 'c' starting from the third cell
1540
findMatch = notebookModel.findNextMatch('c', { cellIndex: 2, position: new Position(1, 1) }, false, false, null);
1541
assert.ok(findMatch);
1542
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1543
assert.strictEqual(findMatch!.match.range.startColumn, 5);
1544
1545
// Test case 4: Find 'd' starting from the fourth cell
1546
findMatch = notebookModel.findNextMatch('d', { cellIndex: 3, position: new Position(1, 1) }, false, false, null);
1547
assert.ok(findMatch);
1548
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1549
assert.strictEqual(findMatch!.match.range.startColumn, 5);
1550
1551
// Test case 5: No match found
1552
findMatch = notebookModel.findNextMatch('e', { cellIndex: 0, position: new Position(1, 1) }, false, false, null);
1553
assert.strictEqual(findMatch, null);
1554
}
1555
);
1556
});
1557
1558
test('findNextMatch 2', async function () {
1559
await withTestNotebook(
1560
[
1561
['var a = 1; var a = 2;', 'javascript', CellKind.Code, [], {}],
1562
['var b = 2;', 'javascript', CellKind.Code, [], {}],
1563
['var c = 3;', 'javascript', CellKind.Code, [], {}],
1564
['var d = 4;', 'javascript', CellKind.Code, [], {}]
1565
],
1566
(editor, viewModel) => {
1567
const notebookModel = viewModel.notebookDocument;
1568
1569
// Test case 1: Find 'var' starting from the first cell
1570
let findMatch = notebookModel.findNextMatch('var', { cellIndex: 0, position: new Position(1, 1) }, false, false, null);
1571
assert.ok(findMatch);
1572
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1573
assert.strictEqual(findMatch!.match.range.startColumn, 1);
1574
1575
// Test case 2: Find 'b' starting from the second cell
1576
findMatch = notebookModel.findNextMatch('b', { cellIndex: 1, position: new Position(1, 1) }, false, false, null);
1577
assert.ok(findMatch);
1578
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1579
assert.strictEqual(findMatch!.match.range.startColumn, 5);
1580
1581
// Test case 3: Find 'c' starting from the third cell
1582
findMatch = notebookModel.findNextMatch('c', { cellIndex: 2, position: new Position(1, 1) }, false, false, null);
1583
assert.ok(findMatch);
1584
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1585
assert.strictEqual(findMatch!.match.range.startColumn, 5);
1586
1587
// Test case 4: Find 'd' starting from the fourth cell
1588
findMatch = notebookModel.findNextMatch('d', { cellIndex: 3, position: new Position(1, 1) }, false, false, null);
1589
assert.ok(findMatch);
1590
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1591
assert.strictEqual(findMatch!.match.range.startColumn, 5);
1592
1593
// Test case 5: No match found
1594
findMatch = notebookModel.findNextMatch('e', { cellIndex: 0, position: new Position(1, 1) }, false, false, null);
1595
assert.strictEqual(findMatch, null);
1596
1597
// Test case 6: Same keywords in the same cell
1598
findMatch = notebookModel.findNextMatch('var', { cellIndex: 0, position: new Position(1, 1) }, false, false, null);
1599
assert.ok(findMatch);
1600
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1601
assert.strictEqual(findMatch!.match.range.startColumn, 1);
1602
1603
findMatch = notebookModel.findNextMatch('var', { cellIndex: 0, position: new Position(1, 5) }, false, false, null);
1604
assert.ok(findMatch);
1605
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1606
assert.strictEqual(findMatch!.match.range.startColumn, 12);
1607
1608
// Test case 7: Search from the middle of a cell with keyword before and after
1609
findMatch = notebookModel.findNextMatch('a', { cellIndex: 0, position: new Position(1, 10) }, false, false, null);
1610
assert.ok(findMatch);
1611
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1612
assert.strictEqual(findMatch!.match.range.startColumn, 13);
1613
1614
// Test case 8: Search from a cell and next match is in another cell below
1615
findMatch = notebookModel.findNextMatch('var', { cellIndex: 0, position: new Position(1, 20) }, false, false, null);
1616
assert.ok(findMatch);
1617
assert.strictEqual(findMatch!.match.range.startLineNumber, 1);
1618
assert.strictEqual(findMatch!.match.range.startColumn, 1);
1619
// assert.strictEqual(match!.cellIndex, 1);
1620
}
1621
);
1622
});
1623
});
1624
1625