Skip to content

Commit 7d29c80

Browse files
committed
operation batching and dnd folded region
1 parent a42c255 commit 7d29c80

3 files changed

Lines changed: 108 additions & 24 deletions

File tree

src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,18 @@ export class CellDragAndDropController extends Disposable {
612612

613613
private onCellDrop(event: CellDragEvent): void {
614614
const draggedCell = this.currentDraggedCell!;
615+
let draggedCells: ICellViewModel[] = [draggedCell];
616+
617+
if (draggedCell.cellKind === CellKind.Markdown) {
618+
const currCellIndex = this.notebookEditor.viewModel!.getCellIndex(draggedCell);
619+
const nextVisibleCellIndex = this.notebookEditor.viewModel!.getNextVisibleCellIndex(currCellIndex);
620+
621+
if (nextVisibleCellIndex > currCellIndex + 1) {
622+
// folding ;)
623+
draggedCells = this.notebookEditor.viewModel!.viewCells.slice(currCellIndex, nextVisibleCellIndex);
624+
}
625+
}
626+
615627
this.dragCleanup();
616628

617629
const isCopy = (event.browserEvent.ctrlKey && !platform.isMacintosh) || (event.browserEvent.altKey && platform.isMacintosh);
@@ -626,9 +638,9 @@ export class CellDragAndDropController extends Disposable {
626638
}
627639

628640
if (isCopy) {
629-
this.copyCell(draggedCell, event.draggedOverCell, dropDirection);
641+
this.copyCells(draggedCells, event.draggedOverCell, dropDirection);
630642
} else {
631-
this.moveCell(draggedCell, event.draggedOverCell, dropDirection);
643+
this.moveCells(draggedCells, event.draggedOverCell, dropDirection);
632644
}
633645
}
634646

@@ -674,16 +686,33 @@ export class CellDragAndDropController extends Disposable {
674686
}));
675687
}
676688

677-
private async moveCell(draggedCell: ICellViewModel, ontoCell: ICellViewModel, direction: 'above' | 'below') {
678-
await this.notebookEditor.moveCell(draggedCell, ontoCell, direction);
689+
private async moveCells(draggedCells: ICellViewModel[], ontoCell: ICellViewModel, direction: 'above' | 'below') {
690+
this.notebookEditor.textModel!.pushStackElement('Move Cells');
691+
for (let i = 0; i < draggedCells.length; i++) {
692+
await this.notebookEditor.moveCell(draggedCells[i], ontoCell, direction);
693+
}
694+
this.notebookEditor.textModel!.pushStackElement('Move Cells');
679695
}
680696

681-
private copyCell(draggedCell: ICellViewModel, ontoCell: ICellViewModel, direction: 'above' | 'below') {
682-
const editState = draggedCell.editState;
683-
const newCell = this.notebookEditor.insertNotebookCell(ontoCell, draggedCell.cellKind, direction, draggedCell.getText());
684-
if (newCell) {
685-
this.notebookEditor.focusNotebookCell(newCell, editState === CellEditState.Editing ? 'editor' : 'container');
697+
private copyCells(draggedCells: ICellViewModel[], ontoCell: ICellViewModel, direction: 'above' | 'below') {
698+
this.notebookEditor.textModel!.pushStackElement('Copy Cells');
699+
let firstNewCell: ICellViewModel | undefined = undefined;
700+
let firstNewCellState: CellEditState = CellEditState.Preview;
701+
for (let i = 0; i < draggedCells.length; i++) {
702+
const draggedCell = draggedCells[0];
703+
const newCell = this.notebookEditor.insertNotebookCell(ontoCell, draggedCell.cellKind, direction, draggedCell.getText());
704+
705+
if (newCell && !firstNewCell) {
706+
firstNewCell = newCell;
707+
firstNewCellState = draggedCell.editState;
708+
}
686709
}
710+
711+
if (firstNewCell) {
712+
this.notebookEditor.focusNotebookCell(firstNewCell, firstNewCellState === CellEditState.Editing ? 'editor' : 'container');
713+
}
714+
715+
this.notebookEditor.textModel!.pushStackElement('Copy Cells');
687716
}
688717
}
689718

src/vs/workbench/contrib/notebook/common/model/cellEdit.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class InsertCellEdit implements IResourceUndoRedoElement {
3131
) {
3232
}
3333

34-
undo(): void | Promise<void> {
34+
undo(): void {
3535
if (!this.editingDelegate.deleteCell) {
3636
throw new Error('Notebook Delete Cell not implemented for Undo/Redo');
3737
}
@@ -41,7 +41,7 @@ export class InsertCellEdit implements IResourceUndoRedoElement {
4141
this.editingDelegate.emitSelections(this.beforedSelections);
4242
}
4343
}
44-
redo(): void | Promise<void> {
44+
redo(): void {
4545
if (!this.editingDelegate.insertCell) {
4646
throw new Error('Notebook Insert Cell not implemented for Undo/Redo');
4747
}
@@ -70,7 +70,7 @@ export class DeleteCellEdit implements IResourceUndoRedoElement {
7070
// this._rawCell.source = [cell.getText()];
7171
}
7272

73-
undo(): void | Promise<void> {
73+
undo(): void {
7474
if (!this.editingDelegate.insertCell) {
7575
throw new Error('Notebook Insert Cell not implemented for Undo/Redo');
7676
}
@@ -81,7 +81,7 @@ export class DeleteCellEdit implements IResourceUndoRedoElement {
8181
}
8282
}
8383

84-
redo(): void | Promise<void> {
84+
redo(): void {
8585
if (!this.editingDelegate.deleteCell) {
8686
throw new Error('Notebook Delete Cell not implemented for Undo/Redo');
8787
}
@@ -107,7 +107,7 @@ export class MoveCellEdit implements IResourceUndoRedoElement {
107107
) {
108108
}
109109

110-
undo(): void | Promise<void> {
110+
undo(): void {
111111
if (!this.editingDelegate.moveCell) {
112112
throw new Error('Notebook Move Cell not implemented for Undo/Redo');
113113
}
@@ -118,7 +118,7 @@ export class MoveCellEdit implements IResourceUndoRedoElement {
118118
}
119119
}
120120

121-
redo(): void | Promise<void> {
121+
redo(): void {
122122
if (!this.editingDelegate.moveCell) {
123123
throw new Error('Notebook Move Cell not implemented for Undo/Redo');
124124
}
@@ -142,7 +142,7 @@ export class SpliceCellsEdit implements IResourceUndoRedoElement {
142142
) {
143143
}
144144

145-
undo(): void | Promise<void> {
145+
undo(): void {
146146
if (!this.editingDelegate.deleteCell || !this.editingDelegate.insertCell) {
147147
throw new Error('Notebook Insert/Delete Cell not implemented for Undo/Redo');
148148
}
@@ -162,7 +162,7 @@ export class SpliceCellsEdit implements IResourceUndoRedoElement {
162162
}
163163
}
164164

165-
redo(): void | Promise<void> {
165+
redo(): void {
166166
if (!this.editingDelegate.deleteCell || !this.editingDelegate.insertCell) {
167167
throw new Error('Notebook Insert/Delete Cell not implemented for Undo/Redo');
168168
}

src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri';
1010
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
1111
import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IProcessedOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType, ICellDto2, IMainCellDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
1212
import { ITextSnapshot } from 'vs/editor/common/model';
13-
import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
13+
import { IUndoRedoService, UndoRedoElementType, IUndoRedoElement, IResourceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo';
1414
import { InsertCellEdit, DeleteCellEdit, MoveCellEdit, SpliceCellsEdit } from 'vs/workbench/contrib/notebook/common/model/cellEdit';
1515
import { ITextModelService } from 'vs/editor/common/services/resolverService';
1616

@@ -66,6 +66,53 @@ export class NotebookTextModelSnapshot implements ITextSnapshot {
6666

6767
}
6868

69+
class StackOperation implements IResourceUndoRedoElement {
70+
type: UndoRedoElementType.Resource;
71+
72+
private _operations: IUndoRedoElement[] = [];
73+
74+
constructor(readonly resource: URI, readonly label: string) {
75+
this.type = UndoRedoElementType.Resource;
76+
}
77+
78+
pushEditOperation(element: IUndoRedoElement) {
79+
this._operations.push(element);
80+
}
81+
82+
undo(): void {
83+
this._operations.reverse().forEach(o => o.undo());
84+
}
85+
redo(): void | Promise<void> {
86+
this._operations.forEach(o => o.redo());
87+
}
88+
}
89+
90+
export class NotebookOperationManager {
91+
private _pendingStackOperation: StackOperation | null = null;
92+
constructor(private _undoService: IUndoRedoService, private _resource: URI) {
93+
94+
}
95+
96+
pushStackElement(label: string) {
97+
if (this._pendingStackOperation) {
98+
this._undoService.pushElement(this._pendingStackOperation);
99+
this._pendingStackOperation = null;
100+
return;
101+
}
102+
103+
this._pendingStackOperation = new StackOperation(this._resource, label);
104+
}
105+
106+
pushEditOperation(element: IUndoRedoElement) {
107+
if (this._pendingStackOperation) {
108+
this._pendingStackOperation.pushEditOperation(element);
109+
return;
110+
}
111+
112+
this._undoService.pushElement(element);
113+
}
114+
}
115+
69116
export class NotebookTextModel extends Disposable implements INotebookTextModel {
70117

71118
private _cellhandlePool: number = 0;
@@ -112,6 +159,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
112159
protected readonly _onDidChangeDirty = this._register(new Emitter<void>());
113160
readonly onDidChangeDirty = this._onDidChangeDirty.event;
114161

162+
private _operationManager: NotebookOperationManager;
163+
115164
constructor(
116165
public handle: number,
117166
public viewType: string,
@@ -122,6 +171,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
122171
) {
123172
super();
124173
this.cells = [];
174+
175+
this._operationManager = new NotebookOperationManager(this._undoService, uri);
125176
}
126177

127178
get isDirty() {
@@ -173,6 +224,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
173224
this._increaseVersionId();
174225
}
175226

227+
pushStackElement(label: string) {
228+
this._operationManager.pushStackElement(label);
229+
}
230+
176231
$applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[], synchronous: boolean): boolean {
177232
if (modelVersionId !== this._versionId) {
178233
return false;
@@ -255,7 +310,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
255310
return [diff[0], deletedCells, diff[2]] as [number, NotebookCellTextModel[], NotebookCellTextModel[]];
256311
});
257312

258-
this._undoService.pushElement(new SpliceCellsEdit(this.uri, undoDiff, {
313+
this._operationManager.pushEditOperation(new SpliceCellsEdit(this.uri, undoDiff, {
259314
insertCell: this._insertCellDelegate.bind(this),
260315
deleteCell: this._deleteCellDelegate.bind(this),
261316
emitSelections: this._emitSelectionsDelegate.bind(this)
@@ -266,7 +321,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
266321
}
267322

268323
$handleEdit(label: string | undefined, undo: () => void, redo: () => void): void {
269-
this._undoService.pushElement({
324+
this._operationManager.pushEditOperation({
270325
type: UndoRedoElementType.Resource,
271326
resource: this.uri,
272327
label: label ?? nls.localize('defaultEditLabel', "Edit"),
@@ -502,7 +557,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
502557
const cell = this.createCellTextModel(source, language, type, [], metadata);
503558

504559
if (pushUndoStop) {
505-
this._undoService.pushElement(new InsertCellEdit(this.uri, index, cell, {
560+
this._operationManager.pushEditOperation(new InsertCellEdit(this.uri, index, cell, {
506561
insertCell: this._insertCellDelegate.bind(this),
507562
deleteCell: this._deleteCellDelegate.bind(this),
508563
emitSelections: this._emitSelectionsDelegate.bind(this)
@@ -522,7 +577,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
522577

523578
insertCell2(index: number, cell: NotebookCellTextModel, synchronous: boolean, pushUndoStop: boolean): void {
524579
if (pushUndoStop) {
525-
this._undoService.pushElement(new InsertCellEdit(this.uri, index, cell, {
580+
this._operationManager.pushEditOperation(new InsertCellEdit(this.uri, index, cell, {
526581
insertCell: this._insertCellDelegate.bind(this),
527582
deleteCell: this._deleteCellDelegate.bind(this),
528583
emitSelections: this._emitSelectionsDelegate.bind(this)
@@ -536,7 +591,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
536591
deleteCell2(index: number, synchronous: boolean, pushUndoStop: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined) {
537592
const cell = this.cells[index];
538593
if (pushUndoStop) {
539-
this._undoService.pushElement(new DeleteCellEdit(this.uri, index, cell, {
594+
this._operationManager.pushEditOperation(new DeleteCellEdit(this.uri, index, cell, {
540595
insertCell: this._insertCellDelegate.bind(this),
541596
deleteCell: this._deleteCellDelegate.bind(this),
542597
emitSelections: this._emitSelectionsDelegate.bind(this)
@@ -553,7 +608,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
553608
moveCellToIdx2(index: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined): boolean {
554609
const cell = this.cells[index];
555610
if (pushedToUndoStack) {
556-
this._undoService.pushElement(new MoveCellEdit(this.uri, index, newIdx, {
611+
this._operationManager.pushEditOperation(new MoveCellEdit(this.uri, index, newIdx, {
557612
moveCell: (fromIndex: number, toIndex: number, beforeSelections: number[] | undefined, endSelections: number[] | undefined) => {
558613
this.moveCellToIdx2(fromIndex, toIndex, true, false, beforeSelections, endSelections);
559614
},

0 commit comments

Comments
 (0)