Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.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 { ILanguageService } from '../../../../../editor/common/languages/language.js';
8
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
9
import { FoldingModel, updateFoldingStateAtIndex } from '../../browser/viewModel/foldingModel.js';
10
import { runDeleteAction } from '../../browser/controller/cellOperations.js';
11
import { NotebookCellSelectionCollection } from '../../browser/viewModel/cellSelectionCollection.js';
12
import { CellEditType, CellKind, SelectionStateType } from '../../common/notebookCommon.js';
13
import { createNotebookCellList, setupInstantiationService, TestCell, withTestNotebook } from './testNotebookEditor.js';
14
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
15
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
16
17
suite('NotebookSelection', () => {
18
const store = ensureNoDisposablesAreLeakedInTestSuite();
19
20
test('focus is never empty', function () {
21
const selectionCollection = new NotebookCellSelectionCollection();
22
assert.deepStrictEqual(selectionCollection.focus, { start: 0, end: 0 });
23
24
selectionCollection.setState(null, [], true, 'model');
25
assert.deepStrictEqual(selectionCollection.focus, { start: 0, end: 0 });
26
selectionCollection.dispose();
27
});
28
29
test('selection is never empty', function () {
30
const selectionCollection = new NotebookCellSelectionCollection();
31
assert.deepStrictEqual(selectionCollection.selections, [{ start: 0, end: 0 }]);
32
33
selectionCollection.setState(null, [], true, 'model');
34
assert.deepStrictEqual(selectionCollection.selections, [{ start: 0, end: 0 }]);
35
selectionCollection.dispose();
36
});
37
38
test('selections does not change when setting to empty', function () {
39
const selectionCollection = new NotebookCellSelectionCollection();
40
let changed = false;
41
store.add(selectionCollection.onDidChangeSelection(() => {
42
changed = true;
43
}));
44
45
selectionCollection.setState(null, [], false, 'model');
46
assert.strictEqual(changed, false);
47
selectionCollection.setState({ start: 0, end: 0 }, [], false, 'model');
48
assert.strictEqual(changed, false);
49
selectionCollection.setState({ start: 0, end: 0 }, [{ start: 0, end: 0 }], false, 'model');
50
assert.strictEqual(changed, false);
51
selectionCollection.setState(null, [], false, 'model');
52
assert.strictEqual(changed, false);
53
selectionCollection.dispose();
54
});
55
56
test('event fires when selection or focus changes', function () {
57
const selectionCollection = new NotebookCellSelectionCollection();
58
let eventCount = 0;
59
store.add(selectionCollection.onDidChangeSelection(() => {
60
eventCount++;
61
}));
62
63
// Change focus
64
selectionCollection.setState({ start: 1, end: 1 }, [{ start: 1, end: 2 }], false, 'model');
65
assert.strictEqual(eventCount, 1);
66
67
// Change selections
68
selectionCollection.setState({ start: 1, end: 1 }, [{ start: 1, end: 2 }, { start: 2, end: 3 }], false, 'model');
69
assert.strictEqual(eventCount, 2);
70
71
// no change
72
selectionCollection.setState({ start: 1, end: 1 }, [{ start: 1, end: 2 }, { start: 2, end: 3 }], false, 'model');
73
assert.strictEqual(eventCount, 2);
74
75
// change to empty focus
76
selectionCollection.setState({ start: 0, end: 0 }, [{ start: 4, end: 5 }], false, 'model');
77
assert.strictEqual(eventCount, 3);
78
79
// change to empty selections
80
selectionCollection.setState({ start: 0, end: 0 }, [], false, 'model');
81
assert.strictEqual(eventCount, 4);
82
83
selectionCollection.dispose();
84
});
85
86
});
87
88
suite('NotebookCellList focus/selection', () => {
89
let disposables: DisposableStore;
90
let instantiationService: TestInstantiationService;
91
let languageService: ILanguageService;
92
93
teardown(() => {
94
disposables.dispose();
95
});
96
97
ensureNoDisposablesAreLeakedInTestSuite();
98
99
setup(() => {
100
disposables = new DisposableStore();
101
instantiationService = setupInstantiationService(disposables);
102
languageService = instantiationService.get(ILanguageService);
103
});
104
105
106
test('notebook cell list setFocus', async function () {
107
await withTestNotebook(
108
[
109
['var a = 1;', 'javascript', CellKind.Code, [], {}],
110
['var b = 2;', 'javascript', CellKind.Code, [], {}]
111
],
112
(editor, viewModel, ds) => {
113
const cellList = createNotebookCellList(instantiationService, ds);
114
cellList.attachViewModel(viewModel);
115
116
assert.strictEqual(cellList.length, 2);
117
cellList.setFocus([0]);
118
assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 });
119
120
cellList.setFocus([1]);
121
assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 });
122
cellList.detachViewModel();
123
});
124
});
125
126
test('notebook cell list setSelections', async function () {
127
await withTestNotebook(
128
[
129
['var a = 1;', 'javascript', CellKind.Code, [], {}],
130
['var b = 2;', 'javascript', CellKind.Code, [], {}]
131
],
132
(editor, viewModel, ds) => {
133
const cellList = createNotebookCellList(instantiationService, ds);
134
cellList.attachViewModel(viewModel);
135
136
assert.strictEqual(cellList.length, 2);
137
cellList.setSelection([0]);
138
// the only selection is also the focus
139
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]);
140
141
// set selection does not modify focus
142
cellList.setSelection([1]);
143
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]);
144
});
145
});
146
147
test('notebook cell list setFocus2', async function () {
148
await withTestNotebook(
149
[
150
['var a = 1;', 'javascript', CellKind.Code, [], {}],
151
['var b = 2;', 'javascript', CellKind.Code, [], {}]
152
],
153
(editor, viewModel, ds) => {
154
const cellList = createNotebookCellList(instantiationService, ds);
155
cellList.attachViewModel(viewModel);
156
157
assert.strictEqual(cellList.length, 2);
158
cellList.setFocus([0]);
159
assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 });
160
161
cellList.setFocus([1]);
162
assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 });
163
164
cellList.setSelection([1]);
165
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]);
166
cellList.detachViewModel();
167
});
168
});
169
170
171
test('notebook cell list focus/selection from UI', async function () {
172
await withTestNotebook(
173
[
174
['# header a', 'markdown', CellKind.Markup, [], {}],
175
['var b = 1;', 'javascript', CellKind.Code, [], {}],
176
['# header b', 'markdown', CellKind.Markup, [], {}],
177
['var b = 2;', 'javascript', CellKind.Code, [], {}],
178
['# header c', 'markdown', CellKind.Markup, [], {}]
179
],
180
(editor, viewModel, ds) => {
181
const cellList = createNotebookCellList(instantiationService, ds);
182
cellList.attachViewModel(viewModel);
183
assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 });
184
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]);
185
186
// arrow down, move both focus and selections
187
cellList.setFocus([1], new KeyboardEvent('keydown'), undefined);
188
cellList.setSelection([1], new KeyboardEvent('keydown'), undefined);
189
assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 });
190
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]);
191
192
// shift+arrow down, expands selection
193
cellList.setFocus([2], new KeyboardEvent('keydown'), undefined);
194
cellList.setSelection([1, 2]);
195
assert.deepStrictEqual(viewModel.getFocus(), { start: 2, end: 3 });
196
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 3 }]);
197
198
// arrow down, will move focus but not expand selection
199
cellList.setFocus([3], new KeyboardEvent('keydown'), undefined);
200
assert.deepStrictEqual(viewModel.getFocus(), { start: 3, end: 4 });
201
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 3 }]);
202
});
203
});
204
205
206
test('notebook cell list focus/selection with folding regions', async function () {
207
await withTestNotebook(
208
[
209
['# header a', 'markdown', CellKind.Markup, [], {}],
210
['var b = 1;', 'javascript', CellKind.Code, [], {}],
211
['# header b', 'markdown', CellKind.Markup, [], {}],
212
['var b = 2;', 'javascript', CellKind.Code, [], {}],
213
['# header c', 'markdown', CellKind.Markup, [], {}]
214
],
215
(editor, viewModel, ds) => {
216
const foldingModel = ds.add(new FoldingModel());
217
foldingModel.attachViewModel(viewModel);
218
219
const cellList = createNotebookCellList(instantiationService, ds);
220
cellList.attachViewModel(viewModel);
221
assert.strictEqual(cellList.length, 5);
222
assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 });
223
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]);
224
cellList.setFocus([0]);
225
226
updateFoldingStateAtIndex(foldingModel, 0, true);
227
updateFoldingStateAtIndex(foldingModel, 2, true);
228
viewModel.updateFoldingRanges(foldingModel.regions);
229
cellList.setHiddenAreas(viewModel.getHiddenRanges(), true);
230
assert.strictEqual(cellList.length, 3);
231
232
// currently, focus on a folded cell will only focus the cell itself, excluding its "inner" cells
233
assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 });
234
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]);
235
236
cellList.focusNext(1, false);
237
// focus next should skip the folded items
238
assert.deepStrictEqual(viewModel.getFocus(), { start: 2, end: 3 });
239
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]);
240
241
// unfold
242
updateFoldingStateAtIndex(foldingModel, 2, false);
243
viewModel.updateFoldingRanges(foldingModel.regions);
244
cellList.setHiddenAreas(viewModel.getHiddenRanges(), true);
245
assert.strictEqual(cellList.length, 4);
246
assert.deepStrictEqual(viewModel.getFocus(), { start: 2, end: 3 });
247
});
248
});
249
250
test('notebook cell list focus/selection with folding regions and applyEdits', async function () {
251
await withTestNotebook(
252
[
253
['# header a', 'markdown', CellKind.Markup, [], {}],
254
['var b = 1;', 'javascript', CellKind.Code, [], {}],
255
['# header b', 'markdown', CellKind.Markup, [], {}],
256
['var b = 2;', 'javascript', CellKind.Code, [], {}],
257
['var c = 3', 'javascript', CellKind.Markup, [], {}],
258
['# header d', 'markdown', CellKind.Markup, [], {}],
259
['var e = 4;', 'javascript', CellKind.Code, [], {}],
260
],
261
(editor, viewModel, ds) => {
262
const foldingModel = ds.add(new FoldingModel());
263
foldingModel.attachViewModel(viewModel);
264
265
const cellList = createNotebookCellList(instantiationService, ds);
266
cellList.attachViewModel(viewModel);
267
cellList.setFocus([0]);
268
cellList.setSelection([0]);
269
270
updateFoldingStateAtIndex(foldingModel, 0, true);
271
updateFoldingStateAtIndex(foldingModel, 2, true);
272
viewModel.updateFoldingRanges(foldingModel.regions);
273
cellList.setHiddenAreas(viewModel.getHiddenRanges(), true);
274
assert.strictEqual(cellList.getModelIndex2(0), 0);
275
assert.strictEqual(cellList.getModelIndex2(1), 2);
276
277
editor.textModel.applyEdits([{
278
editType: CellEditType.Replace, index: 0, count: 2, cells: []
279
}], true, undefined, () => undefined, undefined, false);
280
viewModel.updateFoldingRanges(foldingModel.regions);
281
cellList.setHiddenAreas(viewModel.getHiddenRanges(), true);
282
283
assert.strictEqual(cellList.getModelIndex2(0), 0);
284
assert.strictEqual(cellList.getModelIndex2(1), 3);
285
286
// mimic undo
287
editor.textModel.applyEdits([{
288
editType: CellEditType.Replace, index: 0, count: 0, cells: [
289
ds.add(new TestCell(viewModel.viewType, 7, '# header f', 'markdown', CellKind.Code, [], languageService)),
290
ds.add(new TestCell(viewModel.viewType, 8, 'var g = 5;', 'javascript', CellKind.Code, [], languageService))
291
]
292
}], true, undefined, () => undefined, undefined, false);
293
viewModel.updateFoldingRanges(foldingModel.regions);
294
cellList.setHiddenAreas(viewModel.getHiddenRanges(), true);
295
assert.strictEqual(cellList.getModelIndex2(0), 0);
296
assert.strictEqual(cellList.getModelIndex2(1), 1);
297
assert.strictEqual(cellList.getModelIndex2(2), 2);
298
});
299
});
300
301
test('notebook cell list getModelIndex', async function () {
302
await withTestNotebook(
303
[
304
['# header a', 'markdown', CellKind.Markup, [], {}],
305
['var b = 1;', 'javascript', CellKind.Code, [], {}],
306
['# header b', 'markdown', CellKind.Markup, [], {}],
307
['var b = 2;', 'javascript', CellKind.Code, [], {}],
308
['# header c', 'markdown', CellKind.Markup, [], {}]
309
],
310
(editor, viewModel, ds) => {
311
const foldingModel = ds.add(new FoldingModel());
312
foldingModel.attachViewModel(viewModel);
313
314
const cellList = createNotebookCellList(instantiationService, ds);
315
cellList.attachViewModel(viewModel);
316
317
updateFoldingStateAtIndex(foldingModel, 0, true);
318
updateFoldingStateAtIndex(foldingModel, 2, true);
319
viewModel.updateFoldingRanges(foldingModel.regions);
320
cellList.setHiddenAreas(viewModel.getHiddenRanges(), true);
321
322
assert.deepStrictEqual(cellList.getModelIndex2(-1), 0);
323
assert.deepStrictEqual(cellList.getModelIndex2(0), 0);
324
assert.deepStrictEqual(cellList.getModelIndex2(1), 2);
325
assert.deepStrictEqual(cellList.getModelIndex2(2), 4);
326
});
327
});
328
329
330
test('notebook validate range', async () => {
331
await withTestNotebook(
332
[
333
['# header a', 'markdown', CellKind.Markup, [], {}],
334
['var b = 1;', 'javascript', CellKind.Code, [], {}]
335
],
336
(editor, viewModel) => {
337
assert.deepStrictEqual(viewModel.validateRange(null), null);
338
assert.deepStrictEqual(viewModel.validateRange(undefined), null);
339
assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 0 }), { start: 0, end: 0 });
340
assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 2 }), { start: 0, end: 2 });
341
assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 3 }), { start: 0, end: 2 });
342
assert.deepStrictEqual(viewModel.validateRange({ start: -1, end: 3 }), { start: 0, end: 2 });
343
assert.deepStrictEqual(viewModel.validateRange({ start: -1, end: 1 }), { start: 0, end: 1 });
344
assert.deepStrictEqual(viewModel.validateRange({ start: 2, end: 1 }), { start: 1, end: 2 });
345
assert.deepStrictEqual(viewModel.validateRange({ start: 2, end: -1 }), { start: 0, end: 2 });
346
});
347
});
348
349
test('notebook updateSelectionState', async function () {
350
await withTestNotebook(
351
[
352
['# header a', 'markdown', CellKind.Markup, [], {}],
353
['var b = 1;', 'javascript', CellKind.Code, [], {}]
354
],
355
(editor, viewModel) => {
356
viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 1, end: 2 }, { start: -1, end: 0 }] });
357
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]);
358
});
359
});
360
361
test('notebook cell selection w/ cell deletion', async function () {
362
await withTestNotebook(
363
[
364
['# header a', 'markdown', CellKind.Markup, [], {}],
365
['var b = 1;', 'javascript', CellKind.Code, [], {}]
366
],
367
(editor, viewModel) => {
368
viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 1, end: 2 }] });
369
runDeleteAction(editor, viewModel.cellAt(1)!);
370
// viewModel.deleteCell(1, true, false);
371
assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 });
372
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]);
373
runDeleteAction(editor, viewModel.cellAt(0)!);
374
assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 0 });
375
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 0 }]);
376
});
377
});
378
379
test('notebook cell selection w/ cell deletion from applyEdits', async function () {
380
await withTestNotebook(
381
[
382
['# header a', 'markdown', CellKind.Markup, [], {}],
383
['var b = 1;', 'javascript', CellKind.Code, [], {}],
384
['var c = 2;', 'javascript', CellKind.Code, [], {}]
385
],
386
async (editor, viewModel) => {
387
viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 1, end: 2 }] });
388
editor.textModel.applyEdits([{
389
editType: CellEditType.Replace,
390
index: 1,
391
count: 1,
392
cells: []
393
}], true, undefined, () => undefined, undefined, true);
394
assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 });
395
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]);
396
});
397
});
398
});
399
400