Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.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 { IBulkEditService, ResourceEdit, ResourceTextEdit } from '../../../../../editor/browser/services/bulkEditService.js';
7
import { IPosition, Position } from '../../../../../editor/common/core/position.js';
8
import { Range } from '../../../../../editor/common/core/range.js';
9
import { EndOfLinePreference, IReadonlyTextBuffer } from '../../../../../editor/common/model.js';
10
import { PLAINTEXT_LANGUAGE_ID } from '../../../../../editor/common/languages/modesRegistry.js';
11
import { ILanguageService } from '../../../../../editor/common/languages/language.js';
12
import { ResourceNotebookCellEdit } from '../../../bulkEdit/browser/bulkCellEdits.js';
13
import { INotebookActionContext, INotebookCellActionContext } from './coreActions.js';
14
import { CellEditState, CellFocusMode, expandCellRangesWithHiddenCells, IActiveNotebookEditor, ICellViewModel } from '../notebookBrowser.js';
15
import { CellViewModel, NotebookViewModel } from '../viewModel/notebookViewModelImpl.js';
16
import { cloneNotebookCellTextModel } from '../../common/model/notebookCellTextModel.js';
17
import { CellEditType, CellKind, ICellEditOperation, ICellReplaceEdit, IOutputDto, ISelectionState, NotebookCellMetadata, SelectionStateType } from '../../common/notebookCommon.js';
18
import { cellRangeContains, cellRangesToIndexes, ICellRange } from '../../common/notebookRange.js';
19
import { localize } from '../../../../../nls.js';
20
import { INotificationService } from '../../../../../platform/notification/common/notification.js';
21
import { INotebookKernelHistoryService } from '../../common/notebookKernelService.js';
22
23
export async function changeCellToKind(kind: CellKind, context: INotebookActionContext, language?: string, mime?: string): Promise<void> {
24
const { notebookEditor } = context;
25
if (!notebookEditor.hasModel()) {
26
return;
27
}
28
29
if (notebookEditor.isReadOnly) {
30
return;
31
}
32
33
if (context.ui && context.cell) {
34
// action from UI
35
const { cell } = context;
36
37
if (cell.cellKind === kind) {
38
return;
39
}
40
41
const text = cell.getText();
42
const idx = notebookEditor.getCellIndex(cell);
43
44
if (language === undefined) {
45
const availableLanguages = notebookEditor.activeKernel?.supportedLanguages ?? [];
46
language = availableLanguages[0] ?? PLAINTEXT_LANGUAGE_ID;
47
}
48
49
notebookEditor.textModel.applyEdits([
50
{
51
editType: CellEditType.Replace,
52
index: idx,
53
count: 1,
54
cells: [{
55
cellKind: kind,
56
source: text,
57
language: language,
58
mime: mime ?? cell.mime,
59
outputs: cell.model.outputs,
60
metadata: cell.metadata,
61
}]
62
}
63
], true, {
64
kind: SelectionStateType.Index,
65
focus: notebookEditor.getFocus(),
66
selections: notebookEditor.getSelections()
67
}, () => {
68
return {
69
kind: SelectionStateType.Index,
70
focus: notebookEditor.getFocus(),
71
selections: notebookEditor.getSelections()
72
};
73
}, undefined, true);
74
const newCell = notebookEditor.cellAt(idx);
75
await notebookEditor.focusNotebookCell(newCell, cell.getEditState() === CellEditState.Editing ? 'editor' : 'container');
76
} else if (context.selectedCells) {
77
const selectedCells = context.selectedCells;
78
const rawEdits: ICellEditOperation[] = [];
79
80
selectedCells.forEach(cell => {
81
if (cell.cellKind === kind) {
82
return;
83
}
84
const text = cell.getText();
85
const idx = notebookEditor.getCellIndex(cell);
86
87
if (language === undefined) {
88
const availableLanguages = notebookEditor.activeKernel?.supportedLanguages ?? [];
89
language = availableLanguages[0] ?? PLAINTEXT_LANGUAGE_ID;
90
}
91
92
rawEdits.push(
93
{
94
editType: CellEditType.Replace,
95
index: idx,
96
count: 1,
97
cells: [{
98
cellKind: kind,
99
source: text,
100
language: language,
101
mime: mime ?? cell.mime,
102
outputs: cell.model.outputs,
103
metadata: cell.metadata,
104
}]
105
}
106
);
107
});
108
109
notebookEditor.textModel.applyEdits(rawEdits, true, {
110
kind: SelectionStateType.Index,
111
focus: notebookEditor.getFocus(),
112
selections: notebookEditor.getSelections()
113
}, () => {
114
return {
115
kind: SelectionStateType.Index,
116
focus: notebookEditor.getFocus(),
117
selections: notebookEditor.getSelections()
118
};
119
}, undefined, true);
120
}
121
}
122
123
export function runDeleteAction(editor: IActiveNotebookEditor, cell: ICellViewModel) {
124
const textModel = editor.textModel;
125
const selections = editor.getSelections();
126
const targetCellIndex = editor.getCellIndex(cell);
127
const containingSelection = selections.find(selection => selection.start <= targetCellIndex && targetCellIndex < selection.end);
128
129
const computeUndoRedo = !editor.isReadOnly || textModel.viewType === 'interactive';
130
if (containingSelection) {
131
const edits: ICellReplaceEdit[] = selections.reverse().map(selection => ({
132
editType: CellEditType.Replace, index: selection.start, count: selection.end - selection.start, cells: []
133
}));
134
135
const nextCellAfterContainingSelection = containingSelection.end >= editor.getLength() ? undefined : editor.cellAt(containingSelection.end);
136
137
textModel.applyEdits(edits, true, { kind: SelectionStateType.Index, focus: editor.getFocus(), selections: editor.getSelections() }, () => {
138
if (nextCellAfterContainingSelection) {
139
const cellIndex = textModel.cells.findIndex(cell => cell.handle === nextCellAfterContainingSelection.handle);
140
return { kind: SelectionStateType.Index, focus: { start: cellIndex, end: cellIndex + 1 }, selections: [{ start: cellIndex, end: cellIndex + 1 }] };
141
} else {
142
if (textModel.length) {
143
const lastCellIndex = textModel.length - 1;
144
return { kind: SelectionStateType.Index, focus: { start: lastCellIndex, end: lastCellIndex + 1 }, selections: [{ start: lastCellIndex, end: lastCellIndex + 1 }] };
145
146
} else {
147
return { kind: SelectionStateType.Index, focus: { start: 0, end: 0 }, selections: [{ start: 0, end: 0 }] };
148
}
149
}
150
}, undefined, computeUndoRedo);
151
} else {
152
const focus = editor.getFocus();
153
const edits: ICellReplaceEdit[] = [{
154
editType: CellEditType.Replace, index: targetCellIndex, count: 1, cells: []
155
}];
156
157
const finalSelections: ICellRange[] = [];
158
for (let i = 0; i < selections.length; i++) {
159
const selection = selections[i];
160
161
if (selection.end <= targetCellIndex) {
162
finalSelections.push(selection);
163
} else if (selection.start > targetCellIndex) {
164
finalSelections.push({ start: selection.start - 1, end: selection.end - 1 });
165
} else {
166
finalSelections.push({ start: targetCellIndex, end: targetCellIndex + 1 });
167
}
168
}
169
170
if (editor.cellAt(focus.start) === cell) {
171
// focus is the target, focus is also not part of any selection
172
const newFocus = focus.end === textModel.length ? { start: focus.start - 1, end: focus.end - 1 } : focus;
173
174
textModel.applyEdits(edits, true, { kind: SelectionStateType.Index, focus: editor.getFocus(), selections: editor.getSelections() }, () => ({
175
kind: SelectionStateType.Index, focus: newFocus, selections: finalSelections
176
}), undefined, computeUndoRedo);
177
} else {
178
// users decide to delete a cell out of current focus/selection
179
const newFocus = focus.start > targetCellIndex ? { start: focus.start - 1, end: focus.end - 1 } : focus;
180
181
textModel.applyEdits(edits, true, { kind: SelectionStateType.Index, focus: editor.getFocus(), selections: editor.getSelections() }, () => ({
182
kind: SelectionStateType.Index, focus: newFocus, selections: finalSelections
183
}), undefined, computeUndoRedo);
184
}
185
}
186
}
187
188
export async function moveCellRange(context: INotebookActionContext, direction: 'up' | 'down'): Promise<void> {
189
if (!context.notebookEditor.hasModel()) {
190
return;
191
}
192
const editor = context.notebookEditor;
193
const textModel = editor.textModel;
194
195
if (editor.isReadOnly) {
196
return;
197
}
198
199
let range: ICellRange | undefined = undefined;
200
201
if (context.cell) {
202
const idx = editor.getCellIndex(context.cell);
203
range = { start: idx, end: idx + 1 };
204
} else {
205
const selections = editor.getSelections();
206
const modelRanges = expandCellRangesWithHiddenCells(editor, selections);
207
range = modelRanges[0];
208
}
209
210
if (!range || range.start === range.end) {
211
return;
212
}
213
214
if (direction === 'up') {
215
if (range.start === 0) {
216
return;
217
}
218
219
const indexAbove = range.start - 1;
220
const finalSelection = { start: range.start - 1, end: range.end - 1 };
221
const focus = context.notebookEditor.getFocus();
222
const newFocus = cellRangeContains(range, focus) ? { start: focus.start - 1, end: focus.end - 1 } : { start: range.start - 1, end: range.start };
223
textModel.applyEdits([
224
{
225
editType: CellEditType.Move,
226
index: indexAbove,
227
length: 1,
228
newIdx: range.end - 1
229
}],
230
true,
231
{
232
kind: SelectionStateType.Index,
233
focus: editor.getFocus(),
234
selections: editor.getSelections()
235
},
236
() => ({ kind: SelectionStateType.Index, focus: newFocus, selections: [finalSelection] }),
237
undefined,
238
true
239
);
240
const focusRange = editor.getSelections()[0] ?? editor.getFocus();
241
editor.revealCellRangeInView(focusRange);
242
} else {
243
if (range.end >= textModel.length) {
244
return;
245
}
246
247
const indexBelow = range.end;
248
const finalSelection = { start: range.start + 1, end: range.end + 1 };
249
const focus = editor.getFocus();
250
const newFocus = cellRangeContains(range, focus) ? { start: focus.start + 1, end: focus.end + 1 } : { start: range.start + 1, end: range.start + 2 };
251
252
textModel.applyEdits([
253
{
254
editType: CellEditType.Move,
255
index: indexBelow,
256
length: 1,
257
newIdx: range.start
258
}],
259
true,
260
{
261
kind: SelectionStateType.Index,
262
focus: editor.getFocus(),
263
selections: editor.getSelections()
264
},
265
() => ({ kind: SelectionStateType.Index, focus: newFocus, selections: [finalSelection] }),
266
undefined,
267
true
268
);
269
270
const focusRange = editor.getSelections()[0] ?? editor.getFocus();
271
editor.revealCellRangeInView(focusRange);
272
}
273
}
274
275
export async function copyCellRange(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise<void> {
276
const editor = context.notebookEditor;
277
if (!editor.hasModel()) {
278
return;
279
}
280
281
const textModel = editor.textModel;
282
283
if (editor.isReadOnly) {
284
return;
285
}
286
287
let range: ICellRange | undefined = undefined;
288
289
if (context.ui) {
290
const targetCell = context.cell;
291
const targetCellIndex = editor.getCellIndex(targetCell);
292
range = { start: targetCellIndex, end: targetCellIndex + 1 };
293
} else {
294
const selections = editor.getSelections();
295
const modelRanges = expandCellRangesWithHiddenCells(editor, selections);
296
range = modelRanges[0];
297
}
298
299
if (!range || range.start === range.end) {
300
return;
301
}
302
303
if (direction === 'up') {
304
// insert up, without changing focus and selections
305
const focus = editor.getFocus();
306
const selections = editor.getSelections();
307
textModel.applyEdits([
308
{
309
editType: CellEditType.Replace,
310
index: range.end,
311
count: 0,
312
cells: cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(editor.cellAt(index)!.model))
313
}],
314
true,
315
{
316
kind: SelectionStateType.Index,
317
focus: focus,
318
selections: selections
319
},
320
() => ({ kind: SelectionStateType.Index, focus: focus, selections: selections }),
321
undefined,
322
true
323
);
324
} else {
325
// insert down, move selections
326
const focus = editor.getFocus();
327
const selections = editor.getSelections();
328
const newCells = cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(editor.cellAt(index)!.model));
329
const countDelta = newCells.length;
330
const newFocus = context.ui ? focus : { start: focus.start + countDelta, end: focus.end + countDelta };
331
const newSelections = context.ui ? selections : [{ start: range.start + countDelta, end: range.end + countDelta }];
332
textModel.applyEdits([
333
{
334
editType: CellEditType.Replace,
335
index: range.end,
336
count: 0,
337
cells: cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(editor.cellAt(index)!.model))
338
}],
339
true,
340
{
341
kind: SelectionStateType.Index,
342
focus: focus,
343
selections: selections
344
},
345
() => ({ kind: SelectionStateType.Index, focus: newFocus, selections: newSelections }),
346
undefined,
347
true
348
);
349
350
const focusRange = editor.getSelections()[0] ?? editor.getFocus();
351
editor.revealCellRangeInView(focusRange);
352
}
353
}
354
355
export async function joinSelectedCells(bulkEditService: IBulkEditService, notificationService: INotificationService, context: INotebookCellActionContext): Promise<void> {
356
const editor = context.notebookEditor;
357
if (editor.isReadOnly) {
358
return;
359
}
360
361
const edits: ResourceEdit[] = [];
362
const cells: ICellViewModel[] = [];
363
for (const selection of editor.getSelections()) {
364
cells.push(...editor.getCellsInRange(selection));
365
}
366
367
if (cells.length <= 1) {
368
return;
369
}
370
371
// check if all cells are of the same kind
372
const cellKind = cells[0].cellKind;
373
const isSameKind = cells.every(cell => cell.cellKind === cellKind);
374
if (!isSameKind) {
375
// cannot join cells of different kinds
376
// show warning and quit
377
const message = localize('notebookActions.joinSelectedCells', "Cannot join cells of different kinds");
378
return notificationService.warn(message);
379
}
380
381
// merge all cells content into first cell
382
const firstCell = cells[0];
383
const insertContent = cells.map(cell => cell.getText()).join(firstCell.textBuffer.getEOL());
384
const firstSelection = editor.getSelections()[0];
385
edits.push(
386
new ResourceNotebookCellEdit(editor.textModel.uri,
387
{
388
editType: CellEditType.Replace,
389
index: firstSelection.start,
390
count: firstSelection.end - firstSelection.start,
391
cells: [{
392
cellKind: firstCell.cellKind,
393
source: insertContent,
394
language: firstCell.language,
395
mime: firstCell.mime,
396
outputs: firstCell.model.outputs,
397
metadata: firstCell.metadata,
398
}]
399
}
400
)
401
);
402
403
for (const selection of editor.getSelections().slice(1)) {
404
edits.push(new ResourceNotebookCellEdit(editor.textModel.uri,
405
{
406
editType: CellEditType.Replace,
407
index: selection.start,
408
count: selection.end - selection.start,
409
cells: []
410
}));
411
}
412
413
if (edits.length) {
414
await bulkEditService.apply(
415
edits,
416
{ quotableLabel: localize('notebookActions.joinSelectedCells.label', "Join Notebook Cells") }
417
);
418
}
419
}
420
421
export async function joinNotebookCells(editor: IActiveNotebookEditor, range: ICellRange, direction: 'above' | 'below', constraint?: CellKind): Promise<{ edits: ResourceEdit[]; cell: ICellViewModel; endFocus: ICellRange; endSelections: ICellRange[] } | null> {
422
if (editor.isReadOnly) {
423
return null;
424
}
425
426
const textModel = editor.textModel;
427
const cells = editor.getCellsInRange(range);
428
429
if (!cells.length) {
430
return null;
431
}
432
433
if (range.start === 0 && direction === 'above') {
434
return null;
435
}
436
437
if (range.end === textModel.length && direction === 'below') {
438
return null;
439
}
440
441
for (let i = 0; i < cells.length; i++) {
442
const cell = cells[i];
443
444
if (constraint && cell.cellKind !== constraint) {
445
return null;
446
}
447
}
448
449
if (direction === 'above') {
450
const above = editor.cellAt(range.start - 1) as CellViewModel;
451
if (constraint && above.cellKind !== constraint) {
452
return null;
453
}
454
455
const insertContent = cells.map(cell => (cell.textBuffer.getEOL() ?? '') + cell.getText()).join('');
456
const aboveCellLineCount = above.textBuffer.getLineCount();
457
const aboveCellLastLineEndColumn = above.textBuffer.getLineLength(aboveCellLineCount);
458
459
return {
460
edits: [
461
new ResourceTextEdit(above.uri, { range: new Range(aboveCellLineCount, aboveCellLastLineEndColumn + 1, aboveCellLineCount, aboveCellLastLineEndColumn + 1), text: insertContent }),
462
new ResourceNotebookCellEdit(textModel.uri,
463
{
464
editType: CellEditType.Replace,
465
index: range.start,
466
count: range.end - range.start,
467
cells: []
468
}
469
)
470
],
471
cell: above,
472
endFocus: { start: range.start - 1, end: range.start },
473
endSelections: [{ start: range.start - 1, end: range.start }]
474
};
475
} else {
476
const below = editor.cellAt(range.end) as CellViewModel;
477
if (constraint && below.cellKind !== constraint) {
478
return null;
479
}
480
481
const cell = cells[0];
482
const restCells = [...cells.slice(1), below];
483
const insertContent = restCells.map(cl => (cl.textBuffer.getEOL() ?? '') + cl.getText()).join('');
484
485
const cellLineCount = cell.textBuffer.getLineCount();
486
const cellLastLineEndColumn = cell.textBuffer.getLineLength(cellLineCount);
487
488
return {
489
edits: [
490
new ResourceTextEdit(cell.uri, { range: new Range(cellLineCount, cellLastLineEndColumn + 1, cellLineCount, cellLastLineEndColumn + 1), text: insertContent }),
491
new ResourceNotebookCellEdit(textModel.uri,
492
{
493
editType: CellEditType.Replace,
494
index: range.start + 1,
495
count: range.end - range.start,
496
cells: []
497
}
498
)
499
],
500
cell,
501
endFocus: { start: range.start, end: range.start + 1 },
502
endSelections: [{ start: range.start, end: range.start + 1 }]
503
};
504
}
505
}
506
507
export async function joinCellsWithSurrounds(bulkEditService: IBulkEditService, context: INotebookCellActionContext, direction: 'above' | 'below'): Promise<void> {
508
const editor = context.notebookEditor;
509
const textModel = editor.textModel;
510
const viewModel = editor.getViewModel() as NotebookViewModel;
511
let ret: {
512
edits: ResourceEdit[];
513
cell: ICellViewModel;
514
endFocus: ICellRange;
515
endSelections: ICellRange[];
516
} | null = null;
517
518
if (context.ui) {
519
const focusMode = context.cell.focusMode;
520
const cellIndex = editor.getCellIndex(context.cell);
521
ret = await joinNotebookCells(editor, { start: cellIndex, end: cellIndex + 1 }, direction);
522
if (!ret) {
523
return;
524
}
525
526
await bulkEditService.apply(
527
ret?.edits,
528
{ quotableLabel: 'Join Notebook Cells' }
529
);
530
viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: ret.endFocus, selections: ret.endSelections });
531
ret.cell.updateEditState(CellEditState.Editing, 'joinCellsWithSurrounds');
532
editor.revealCellRangeInView(editor.getFocus());
533
if (focusMode === CellFocusMode.Editor) {
534
ret.cell.focusMode = CellFocusMode.Editor;
535
}
536
} else {
537
const selections = editor.getSelections();
538
if (!selections.length) {
539
return;
540
}
541
542
const focus = editor.getFocus();
543
const focusMode = editor.cellAt(focus.start)?.focusMode;
544
545
const edits: ResourceEdit[] = [];
546
let cell: ICellViewModel | null = null;
547
const cells: ICellViewModel[] = [];
548
549
for (let i = selections.length - 1; i >= 0; i--) {
550
const selection = selections[i];
551
const containFocus = cellRangeContains(selection, focus);
552
553
if (
554
selection.end >= textModel.length && direction === 'below'
555
|| selection.start === 0 && direction === 'above'
556
) {
557
if (containFocus) {
558
cell = editor.cellAt(focus.start)!;
559
}
560
561
cells.push(...editor.getCellsInRange(selection));
562
continue;
563
}
564
565
const singleRet = await joinNotebookCells(editor, selection, direction);
566
567
if (!singleRet) {
568
return;
569
}
570
571
edits.push(...singleRet.edits);
572
cells.push(singleRet.cell);
573
574
if (containFocus) {
575
cell = singleRet.cell;
576
}
577
}
578
579
if (!edits.length) {
580
return;
581
}
582
583
if (!cell || !cells.length) {
584
return;
585
}
586
587
await bulkEditService.apply(
588
edits,
589
{ quotableLabel: 'Join Notebook Cells' }
590
);
591
592
cells.forEach(cell => {
593
cell.updateEditState(CellEditState.Editing, 'joinCellsWithSurrounds');
594
});
595
596
viewModel.updateSelectionsState({ kind: SelectionStateType.Handle, primary: cell.handle, selections: cells.map(cell => cell.handle) });
597
editor.revealCellRangeInView(editor.getFocus());
598
const newFocusedCell = editor.cellAt(editor.getFocus().start);
599
if (focusMode === CellFocusMode.Editor && newFocusedCell) {
600
newFocusedCell.focusMode = CellFocusMode.Editor;
601
}
602
}
603
}
604
605
function _splitPointsToBoundaries(splitPoints: IPosition[], textBuffer: IReadonlyTextBuffer): IPosition[] | null {
606
const boundaries: IPosition[] = [];
607
const lineCnt = textBuffer.getLineCount();
608
const getLineLen = (lineNumber: number) => {
609
return textBuffer.getLineLength(lineNumber);
610
};
611
612
// split points need to be sorted
613
splitPoints = splitPoints.sort((l, r) => {
614
const lineDiff = l.lineNumber - r.lineNumber;
615
const columnDiff = l.column - r.column;
616
return lineDiff !== 0 ? lineDiff : columnDiff;
617
});
618
619
for (let sp of splitPoints) {
620
if (getLineLen(sp.lineNumber) + 1 === sp.column && sp.column !== 1 /** empty line */ && sp.lineNumber < lineCnt) {
621
sp = new Position(sp.lineNumber + 1, 1);
622
}
623
_pushIfAbsent(boundaries, sp);
624
}
625
626
if (boundaries.length === 0) {
627
return null;
628
}
629
630
// boundaries already sorted and not empty
631
const modelStart = new Position(1, 1);
632
const modelEnd = new Position(lineCnt, getLineLen(lineCnt) + 1);
633
return [modelStart, ...boundaries, modelEnd];
634
}
635
636
function _pushIfAbsent(positions: IPosition[], p: IPosition) {
637
const last = positions.length > 0 ? positions[positions.length - 1] : undefined;
638
if (!last || last.lineNumber !== p.lineNumber || last.column !== p.column) {
639
positions.push(p);
640
}
641
}
642
643
export function computeCellLinesContents(cell: ICellViewModel, splitPoints: IPosition[]): string[] | null {
644
const rangeBoundaries = _splitPointsToBoundaries(splitPoints, cell.textBuffer);
645
if (!rangeBoundaries) {
646
return null;
647
}
648
const newLineModels: string[] = [];
649
for (let i = 1; i < rangeBoundaries.length; i++) {
650
const start = rangeBoundaries[i - 1];
651
const end = rangeBoundaries[i];
652
653
newLineModels.push(cell.textBuffer.getValueInRange(new Range(start.lineNumber, start.column, end.lineNumber, end.column), EndOfLinePreference.TextDefined));
654
}
655
656
return newLineModels;
657
}
658
659
export function insertCell(
660
languageService: ILanguageService,
661
editor: IActiveNotebookEditor,
662
index: number,
663
type: CellKind,
664
direction: 'above' | 'below' = 'above',
665
initialText: string = '',
666
ui: boolean = false,
667
kernelHistoryService?: INotebookKernelHistoryService
668
) {
669
const viewModel = editor.getViewModel() as NotebookViewModel;
670
const activeKernel = editor.activeKernel;
671
if (viewModel.options.isReadOnly) {
672
return null;
673
}
674
675
const cell = editor.cellAt(index);
676
const nextIndex = ui ? viewModel.getNextVisibleCellIndex(index) : index + 1;
677
let language;
678
if (type === CellKind.Code) {
679
const supportedLanguages = activeKernel?.supportedLanguages ?? languageService.getRegisteredLanguageIds();
680
const defaultLanguage = supportedLanguages[0] || PLAINTEXT_LANGUAGE_ID;
681
682
if (cell?.cellKind === CellKind.Code) {
683
language = cell.language;
684
} else if (cell?.cellKind === CellKind.Markup) {
685
const nearestCodeCellIndex = viewModel.nearestCodeCellIndex(index);
686
if (nearestCodeCellIndex > -1) {
687
language = viewModel.cellAt(nearestCodeCellIndex)!.language;
688
} else {
689
language = defaultLanguage;
690
}
691
} else if (!cell && viewModel.length === 0) {
692
// No cells in notebook - check kernel history
693
const lastKernels = kernelHistoryService?.getKernels(viewModel.notebookDocument);
694
if (lastKernels?.all.length) {
695
const lastKernel = lastKernels.all[0];
696
language = lastKernel.supportedLanguages[0] || defaultLanguage;
697
} else {
698
language = defaultLanguage;
699
}
700
} else {
701
if (cell === undefined && direction === 'above') {
702
// insert cell at the very top
703
language = viewModel.viewCells.find(cell => cell.cellKind === CellKind.Code)?.language || defaultLanguage;
704
} else {
705
language = defaultLanguage;
706
}
707
}
708
709
if (!supportedLanguages.includes(language)) {
710
// the language no longer exists
711
language = defaultLanguage;
712
}
713
} else {
714
language = 'markdown';
715
}
716
717
const insertIndex = cell ?
718
(direction === 'above' ? index : nextIndex) :
719
index;
720
return insertCellAtIndex(viewModel, insertIndex, initialText, language, type, undefined, [], true, true);
721
}
722
723
export function insertCellAtIndex(viewModel: NotebookViewModel, index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IOutputDto[], synchronous: boolean, pushUndoStop: boolean): CellViewModel {
724
const endSelections: ISelectionState = { kind: SelectionStateType.Index, focus: { start: index, end: index + 1 }, selections: [{ start: index, end: index + 1 }] };
725
viewModel.notebookDocument.applyEdits([
726
{
727
editType: CellEditType.Replace,
728
index,
729
count: 0,
730
cells: [
731
{
732
cellKind: type,
733
language: language,
734
mime: undefined,
735
outputs: outputs,
736
metadata: metadata,
737
source: source
738
}
739
]
740
}
741
], synchronous, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: viewModel.getSelections() }, () => endSelections, undefined, pushUndoStop && !viewModel.options.isReadOnly);
742
return viewModel.cellAt(index)!;
743
}
744
745