Merge pull request #100545 from microsoft/rebornix/separate-textmodel-selection

Notebook: separate selections and text model
This commit is contained in:
Peng Lyu
2020-06-25 15:37:10 -07:00
committed by GitHub
23 changed files with 634 additions and 420 deletions
@@ -56,7 +56,32 @@ async function splitEditor() {
await once;
}
suite('API tests', () => {
suite('Notebook API tests', () => {
// test.only('crash', async function () {
// for (let i = 0; i < 200; i++) {
// let resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
// await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor');
// resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
// await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor');
// }
// });
// test.only('crash', async function () {
// for (let i = 0; i < 200; i++) {
// let resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
// await vscode.commands.executeCommand('workbench.action.files.save');
// await vscode.commands.executeCommand('workbench.action.closeAllEditors');
// resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
// await vscode.commands.executeCommand('workbench.action.files.save');
// await vscode.commands.executeCommand('workbench.action.closeAllEditors');
// }
// });
test('document open/close event', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
const firstDocumentOpen = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument);
@@ -222,6 +247,13 @@ suite('API tests', () => {
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const firstEditor = vscode.notebook.activeNotebookEditor;
assert.equal(firstEditor?.document.cells.length, 1);
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
test('notebook editor active/visible', async function () {
@@ -290,7 +322,7 @@ suite('API tests', () => {
assert.equal(cellChangeEventRet.changes[0].items[0], vscode.notebook.activeNotebookEditor!.document.cells[1]);
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
test('initialzation should not emit cell change events.', async function () {
@@ -766,6 +798,9 @@ suite('metadata', () => {
assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false);
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123);
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript');
await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
@@ -781,6 +816,9 @@ suite('metadata', () => {
const activeCell = vscode.notebook.activeNotebookEditor!.selection;
assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1);
assert.equal(activeCell?.metadata.custom!['testCellMetadata'] as number, 123);
await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
});
@@ -808,7 +846,7 @@ suite('regression', () => {
await vscode.commands.executeCommand('vscode.openWith', resource, 'default');
assert.equal(vscode.window.activeTextEditor?.document.uri.path, resource.path);
await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
@@ -824,7 +862,7 @@ suite('regression', () => {
assert.notEqual(vscode.notebook.activeNotebookEditor, undefined, 'notebook first');
assert.notEqual(vscode.window.activeTextEditor, undefined);
// await vscode.commands.executeCommand('workbench.action.files.saveAll');
await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
+1 -1
View File
@@ -1762,7 +1762,7 @@ declare module 'vscode' {
resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Promise<void>;
saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
readonly onDidChangeNotebook: Event<NotebookDocumentContentChangeEvent>;
readonly onDidChangeNotebook: Event<NotebookDocumentContentChangeEvent | NotebookDocumentEditEvent>;
backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Promise<NotebookDocumentBackup>;
kernel?: NotebookKernel;
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as DOM from 'vs/base/browser/dom';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta, INotebookModelAddedData } from '../common/extHost.protocol';
import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
@@ -40,7 +41,7 @@ export class MainThreadNotebookDocument extends Disposable {
) {
super();
this._textModel = new NotebookTextModel(handle, viewType, supportBackup, uri);
this._textModel = new NotebookTextModel(handle, viewType, supportBackup, uri, undoRedoService);
this._register(this._textModel.onDidModelChangeProxy(e => {
this._proxy.$acceptModelChanged(this.uri, e);
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: { selections: this._textModel.selections }, metadata: null });
@@ -51,9 +52,18 @@ export class MainThreadNotebookDocument extends Disposable {
}));
}
async applyEdit(modelVersionId: number, edits: ICellEditOperation[], emitToExtHost: boolean): Promise<boolean> {
async applyEdit(modelVersionId: number, edits: ICellEditOperation[], emitToExtHost: boolean, synchronous: boolean): Promise<boolean> {
await this.notebookService.transformEditsOutputs(this.textModel, edits);
return this._textModel.$applyEdit(modelVersionId, edits);
if (synchronous) {
return this._textModel.$applyEdit(modelVersionId, edits, emitToExtHost, synchronous);
} else {
return new Promise(resolve => {
this._register(DOM.scheduleAtNextAnimationFrame(() => {
const ret = this._textModel.$applyEdit(modelVersionId, edits, emitToExtHost, true);
resolve(ret);
}));
});
}
}
async spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[]) {
@@ -528,7 +538,7 @@ export class MainThreadNotebookController implements IMainNotebookController {
await mainthreadNotebook.applyEdit(mainthreadNotebook.textModel.versionId, [
{ editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 },
{ editType: CellEditType.Insert, index: 0, cells: data.cells }
], true);
], true, false);
}
return mainthreadNotebook.textModel;
}
@@ -548,7 +558,7 @@ export class MainThreadNotebookController implements IMainNotebookController {
index: 0,
cells: backup.cells || []
}
], false);
], false, true);
// create document in ext host with cells data
await this._mainThreadNotebook.addNotebookDocument({
@@ -625,7 +635,7 @@ export class MainThreadNotebookController implements IMainNotebookController {
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
if (mainthreadNotebook) {
return await mainthreadNotebook.applyEdit(modelVersionId, edits, true);
return await mainthreadNotebook.applyEdit(modelVersionId, edits, true, true);
}
return false;
@@ -674,7 +674,7 @@ async function moveCell(context: INotebookCellActionContext, direction: 'up' | '
if (result) {
// move cell command only works when the cell container has focus
await context.notebookEditor.focusNotebookCell(context.cell, 'container');
await context.notebookEditor.focusNotebookCell(result, 'container');
}
}
@@ -210,17 +210,17 @@ export interface INotebookEditor extends IEditor {
/**
* Move a cell up one spot
*/
moveCellUp(cell: ICellViewModel): Promise<boolean>;
moveCellUp(cell: ICellViewModel): Promise<ICellViewModel | null>;
/**
* Move a cell down one spot
*/
moveCellDown(cell: ICellViewModel): Promise<boolean>;
moveCellDown(cell: ICellViewModel): Promise<ICellViewModel | null>;
/**
* Move a cell above or below another cell
*/
moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise<boolean>;
moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise<ICellViewModel | null>;
/**
* Focus the container of a cell (the monaco editor inside is not focused).
@@ -371,6 +371,7 @@ export interface INotebookEditor extends IEditor {
}
export interface INotebookCellList {
isDisposed: boolean
readonly contextKeyService: IContextKeyService;
elementAt(position: number): ICellViewModel | undefined;
elementHeight(element: ICellViewModel): number;
@@ -6,7 +6,7 @@
import * as DOM from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -30,7 +30,7 @@ export class NotebookEditor extends BaseEditor {
static readonly ID: string = 'workbench.editor.notebook';
private readonly _editorMemento: IEditorMemento<INotebookEditorViewState>;
private readonly _groupListener = this._register(new MutableDisposable());
private readonly _groupListener = this._register(new DisposableStore());
private readonly _widgetDisposableStore: DisposableStore = new DisposableStore();
private _widget: IBorrowValue<NotebookEditorWidget> = { value: undefined };
private _rootElement!: HTMLElement;
@@ -49,13 +49,13 @@ export class NotebookEditor extends BaseEditor {
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@IEditorService private readonly _editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IEditorDropService private readonly _editorDropService: IEditorDropService,
@INotificationService private readonly _notificationService: INotificationService,
@INotebookEditorWidgetService private readonly _notebookWidgetService: INotebookEditorWidgetService,
) {
super(NotebookEditor.ID, telemetryService, themeService, storageService);
this._editorMemento = this.getEditorMemento<INotebookEditorViewState>(editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
this._editorMemento = this.getEditorMemento<INotebookEditorViewState>(_editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
}
set viewModel(newModel: NotebookViewModel | undefined) {
@@ -100,7 +100,14 @@ export class NotebookEditor extends BaseEditor {
setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
super.setEditorVisible(visible, group);
this._groupListener.value = group?.onWillCloseEditor(e => this._saveEditorViewState(e.editor));
if (group) {
this._groupListener.add(group.onWillCloseEditor(e => this._saveEditorViewState(e.editor)));
this._groupListener.add(group.onDidGroupChange(() => {
if (this._editorGroupService.activeGroup !== group) {
this._widget?.value?.updateEditorFocus();
}
}));
}
if (!visible) {
this._saveEditorViewState(this.input);
@@ -197,6 +204,10 @@ export class NotebookEditor extends BaseEditor {
private _saveEditorViewState(input: IEditorInput | undefined): void {
if (this.group && this._widget.value && input instanceof NotebookEditorInput) {
if (this._widget.value.isDisposed) {
return;
}
const state = this._widget.value.getEditorViewState();
this._editorMemento.saveEditorState(this.group, input.resource, state);
}
@@ -105,10 +105,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return this._isDisposed;
}
private readonly _onDidChangeModel = new Emitter<NotebookTextModel | undefined>();
private readonly _onDidChangeModel = this._register(new Emitter<NotebookTextModel | undefined>());
readonly onDidChangeModel: Event<NotebookTextModel | undefined> = this._onDidChangeModel.event;
private readonly _onDidFocusEditorWidget = new Emitter<void>();
private readonly _onDidFocusEditorWidget = this._register(new Emitter<void>());
readonly onDidFocusEditorWidget = this._onDidFocusEditorWidget.event;
set viewModel(newModel: NotebookViewModel | undefined) {
@@ -129,7 +129,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
private _activeKernel: INotebookKernelInfo | undefined = undefined;
private readonly _onDidChangeKernel = new Emitter<void>();
private readonly _onDidChangeKernel = this._register(new Emitter<void>());
readonly onDidChangeKernel: Event<void> = this._onDidChangeKernel.event;
get activeKernel() {
@@ -217,7 +217,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
updateEditorFocus() {
// Note - focus going to the webview will fire 'blur', but the webview element will be
// a descendent of the notebook editor root.
this._editorFocus?.set(DOM.isAncestor(document.activeElement, this._overlayContainer));
const focused = DOM.isAncestor(document.activeElement, this._overlayContainer);
this._editorFocus?.set(focused);
this._notebookViewModel?.setFocus(focused);
}
hasFocus() {
@@ -700,14 +702,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
const focus = this._list.getFocus()[0];
if (typeof focus === 'number') {
const element = this._notebookViewModel!.viewCells[focus];
const itemDOM = this._list?.domElementOfElement(element!);
let editorFocused = false;
if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) {
editorFocused = true;
}
if (element) {
const itemDOM = this._list?.domElementOfElement(element);
let editorFocused = !!(document.activeElement && itemDOM && itemDOM.contains(document.activeElement));
state.editorFocused = editorFocused;
state.focus = focus;
state.editorFocused = editorFocused;
state.focus = focus;
}
}
}
@@ -906,7 +907,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
(direction === 'above' ? index : nextIndex) :
index;
const newCell = this._notebookViewModel!.createCell(insertIndex, initialText.split(/\r?\n/g), language, type, cell?.metadata, true);
return newCell;
return newCell as CellViewModel;
}
async splitNotebookCell(cell: ICellViewModel): Promise<CellViewModel[] | null> {
@@ -946,41 +947,41 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return true;
}
async moveCellDown(cell: ICellViewModel): Promise<boolean> {
async moveCellDown(cell: ICellViewModel): Promise<ICellViewModel | null> {
if (!this._notebookViewModel!.metadata.editable) {
return false;
return null;
}
const index = this._notebookViewModel!.getCellIndex(cell);
if (index === this._notebookViewModel!.length - 1) {
return false;
return null;
}
const newIdx = index + 1;
return this._moveCellToIndex(index, newIdx);
}
async moveCellUp(cell: ICellViewModel): Promise<boolean> {
async moveCellUp(cell: ICellViewModel): Promise<ICellViewModel | null> {
if (!this._notebookViewModel!.metadata.editable) {
return false;
return null;
}
const index = this._notebookViewModel!.getCellIndex(cell);
if (index === 0) {
return false;
return null;
}
const newIdx = index - 1;
return this._moveCellToIndex(index, newIdx);
}
async moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise<boolean> {
async moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise<ICellViewModel | null> {
if (!this._notebookViewModel!.metadata.editable) {
return false;
return null;
}
if (cell === relativeToCell) {
return false;
return null;
}
const originalIdx = this._notebookViewModel!.getCellIndex(cell);
@@ -994,23 +995,24 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return this._moveCellToIndex(originalIdx, newIdx);
}
private async _moveCellToIndex(index: number, newIdx: number): Promise<boolean> {
private async _moveCellToIndex(index: number, newIdx: number): Promise<ICellViewModel | null> {
if (index === newIdx) {
return false;
return null;
}
if (!this._notebookViewModel!.moveCellToIdx(index, newIdx, true)) {
throw new Error('Notebook Editor move cell, index out of range');
}
let r: (val: boolean) => void;
let r: (val: ICellViewModel | null) => void;
DOM.scheduleAtNextAnimationFrame(() => {
if (this._isDisposed) {
r(false);
r(null);
}
this._list?.revealElementInView(this._notebookViewModel!.viewCells[newIdx]);
r(true);
const viewCell = this._notebookViewModel!.viewCells[newIdx];
this._list?.revealElementInView(viewCell);
r(viewCell);
});
return new Promise(resolve => { r = resolve; });
@@ -1262,6 +1264,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
dispose() {
this._isDisposed = true;
// dispose webview first
this._webview?.dispose();
this.notebookService.removeNotebookEditor(this);
const keys = Object.keys(this._contributions);
for (let i = 0, len = keys.length; i < len; i++) {
@@ -1271,7 +1276,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._localStore.clear();
this._list?.dispose();
this._webview?.dispose();
this._overlayContainer.remove();
this.viewModel?.dispose();
@@ -92,8 +92,9 @@ class NotebookEditorWidgetService implements INotebookEditorWidgetService {
private _disposeWidget(widget: NotebookEditorWidget): void {
widget.onWillHide();
widget.getDomNode().remove();
const domNode = widget.getDomNode();
widget.dispose();
domNode.remove();
}
private _freeWidget(input: NotebookEditorInput, source: IEditorGroup, target: IEditorGroup): void {
@@ -45,6 +45,12 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
private _hiddenRangeIds: string[] = [];
private hiddenRangesPrefixSum: PrefixSumComputer | null = null;
private _isDisposed = false;
get isDisposed() {
return this._isDisposed;
}
constructor(
private listUser: string,
container: HTMLElement,
@@ -162,6 +168,10 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
attachViewModel(model: NotebookViewModel) {
this._viewModel = model;
this._viewModelStore.add(model.onDidChangeViewCells((e) => {
if (this._isDisposed) {
return;
}
const currentRanges = this._hiddenRangeIds.map(id => this._viewModel!.getTrackedRange(id)).filter(range => range !== null) as ICellRange[];
const newVisibleViewCells: CellViewModel[] = getVisibleCells(this._viewModel!.viewCells as CellViewModel[], currentRanges);
@@ -197,7 +207,11 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
deletedOutputs.forEach(output => this._onDidRemoveOutput.fire(output));
});
} else {
DOM.scheduleAtNextAnimationFrame(() => {
this._viewModelStore.add(DOM.scheduleAtNextAnimationFrame(() => {
if (this._isDisposed) {
return;
}
viewDiffs.reverse().forEach((diff) => {
const hideOutputs: IProcessedOutput[] = [];
const deletedOutputs: IProcessedOutput[] = [];
@@ -216,7 +230,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
hideOutputs.forEach(output => this._onDidHideOutput.fire(output));
deletedOutputs.forEach(output => this._onDidRemoveOutput.fire(output));
});
});
}));
}
}));
@@ -490,7 +504,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
domElementOfElement(element: ICellViewModel): HTMLElement | null {
const index = this._getViewIndexUpperBound(element);
if (index !== undefined) {
if (index !== undefined && index >= 0) {
return this.view.domElement(index);
}
@@ -518,7 +532,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
updateElementHeight2(element: ICellViewModel, size: number): void {
const index = this._getViewIndexUpperBound(element);
if (index === undefined) {
if (index === undefined || index < 0 || index >= this.length) {
return;
}
@@ -874,6 +888,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
}
dispose() {
this._isDisposed = true;
this._viewModelStore.dispose();
this._localDisposableStore.dispose();
super.dispose();
@@ -61,7 +61,12 @@ class RichRenderer implements IOutputTransformContribution {
let mimeTypesMessage = mimeTypes.join(', ');
contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`;
if (preferredMimeType) {
contentNode.innerText = `No renderer could be found for MIME type: ${preferredMimeType}`;
} else {
contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`;
}
container.appendChild(contentNode);
return {
@@ -340,11 +340,19 @@ ${loaderJs}
}
async initialize(content: string) {
if (!document.body.contains(this.element)) {
throw new Error('Element is already detached from the DOM tree');
}
this.webview = this._createInset(this.webviewService, content);
this.webview.mountTo(this.element);
this._register(this.webview);
this._register(this.webview.onDidClickLink(link => {
if (this._disposed) {
return;
}
if (!link) {
return;
}
@@ -356,6 +364,10 @@ ${loaderJs}
}));
this._register(this.webview.onDidReload(() => {
if (this._disposed) {
return;
}
this.preloadsCache.clear();
for (const [output, inset] of this.insetMapping.entries()) {
this.updateRendererPreloads(inset.preloads);
@@ -364,6 +376,10 @@ ${loaderJs}
}));
this._register(this.webview.onMessage((data: FromWebviewMessage) => {
if (this._disposed) {
return;
}
if (data.__vscode_notebook_message) {
if (data.type === 'dimension') {
let height = data.data.height;
@@ -739,6 +755,10 @@ ${loaderJs}
}
private _sendMessageToWebview(message: ToWebviewMessage) {
if (this._disposed) {
return;
}
this.webview.postMessage(message);
}
@@ -748,6 +768,7 @@ ${loaderJs}
dispose() {
this._disposed = true;
this.webview.dispose();
super.dispose();
}
}
@@ -90,14 +90,17 @@ export abstract class BaseCellViewModel extends Disposable {
options: model.IModelDeltaDecoration;
}>();
private _lastDecorationId: number = 0;
protected _textModel?: model.ITextModel;
get textModel(): model.ITextModel | undefined {
return this._textModel;
return this.model.textModel;
}
set textModel(m: model.ITextModel | undefined) {
this.model.textModel = m;
}
hasModel(): this is IEditableCellViewModel {
return !!this._textModel;
return !!this.model.textModel;
}
private _dragging: boolean = false;
@@ -127,7 +130,7 @@ export abstract class BaseCellViewModel extends Disposable {
abstract onDeselect(): void;
assertTextModelAttached(): boolean {
if (this._textModel && this._textEditor && this._textEditor.getModel() === this._textModel) {
if (this.textModel && this._textEditor && this._textEditor.getModel() === this.textModel) {
return true;
}
@@ -148,7 +151,7 @@ export abstract class BaseCellViewModel extends Disposable {
}
this._textEditor = editor;
this._textModel = this._textEditor.getModel() || undefined;
this.textModel = this._textEditor.getModel() || undefined;
if (this._editorViewStates) {
this._restoreViewState(this._editorViewStates);
@@ -183,7 +186,7 @@ export abstract class BaseCellViewModel extends Disposable {
});
this._textEditor = undefined;
this._textModel = undefined;
this.textModel = undefined;
this._cursorChangeListener?.dispose();
this._cursorChangeListener = null;
this._onDidChangeEditorAttachState.fire();
@@ -315,7 +318,7 @@ export abstract class BaseCellViewModel extends Disposable {
}
const firstViewLineTop = this._textEditor.getTopForPosition(1, 1);
const lastViewLineTop = this._textEditor.getTopForPosition(this._textModel!.getLineCount(), this._textModel!.getLineLength(this._textModel!.getLineCount()));
const lastViewLineTop = this._textEditor.getTopForPosition(this.textModel!.getLineCount(), this.textModel!.getLineLength(this.textModel!.getLineCount()));
const selectionTop = this._textEditor.getTopForPosition(selection.startLineNumber, selection.startColumn);
if (selectionTop === lastViewLineTop) {
@@ -343,7 +346,7 @@ export abstract class BaseCellViewModel extends Disposable {
let cellMatches: model.FindMatch[] = [];
if (this.assertTextModelAttached()) {
cellMatches = this._textModel!.findMatches(value, false, false, false, null, false);
cellMatches = this.textModel!.findMatches(value, false, false, false, null, false);
} else {
const lineCount = this.textBuffer.getLineCount();
const fullRange = new Range(1, 1, lineCount, this.textBuffer.getLineLength(lineCount) + 1);
@@ -5,181 +5,24 @@
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ICell, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IResourceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri';
import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { ITextCellEditingDelegate } from 'vs/workbench/contrib/notebook/common/model/cellEdit';
/**
* It should not modify Undo/Redo stack
*/
export interface ICellEditingDelegate {
insertCell?(index: number, viewCell: BaseCellViewModel): void;
deleteCell?(index: number): void;
moveCell?(fromIndex: number, toIndex: number): void;
createCellViewModel?(cell: ICell): BaseCellViewModel;
export interface IViewCellEditingDelegate extends ITextCellEditingDelegate {
createCellViewModel?(cell: NotebookCellTextModel): BaseCellViewModel;
createCell?(index: number, source: string | string[], language: string, type: CellKind): BaseCellViewModel;
setSelections(selections: number[]): void;
}
export class InsertCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = 'Insert Cell';
constructor(
public resource: URI,
private insertIndex: number,
private cell: BaseCellViewModel,
private editingDelegate: ICellEditingDelegate,
private beforedSelections: number[],
private endSelections: number[]
) {
}
undo(): void | Promise<void> {
if (!this.editingDelegate.deleteCell) {
throw new Error('Notebook Delete Cell not implemented for Undo/Redo');
}
this.editingDelegate.deleteCell(this.insertIndex);
this.editingDelegate.setSelections(this.beforedSelections);
}
redo(): void | Promise<void> {
if (!this.editingDelegate.insertCell) {
throw new Error('Notebook Insert Cell not implemented for Undo/Redo');
}
this.editingDelegate.insertCell(this.insertIndex, this.cell);
this.editingDelegate.setSelections(this.endSelections);
}
}
export class DeleteCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = 'Delete Cell';
private _rawCell: ICell;
constructor(
public resource: URI,
private insertIndex: number,
cell: BaseCellViewModel,
private editingDelegate: ICellEditingDelegate,
private beforedSelections: number[],
private endSelections: number[]
) {
this._rawCell = cell.model;
// save inmem text to `ICell`
// no needed any more as the text buffer is transfered to `raw_cell`
// this._rawCell.source = [cell.getText()];
}
undo(): void | Promise<void> {
if (!this.editingDelegate.insertCell || !this.editingDelegate.createCellViewModel) {
throw new Error('Notebook Insert Cell not implemented for Undo/Redo');
}
const cell = this.editingDelegate.createCellViewModel(this._rawCell);
this.editingDelegate.insertCell(this.insertIndex, cell);
this.editingDelegate.setSelections(this.beforedSelections);
}
redo(): void | Promise<void> {
if (!this.editingDelegate.deleteCell) {
throw new Error('Notebook Delete Cell not implemented for Undo/Redo');
}
this.editingDelegate.deleteCell(this.insertIndex);
this.editingDelegate.setSelections(this.endSelections);
}
}
export class MoveCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = 'Delete Cell';
constructor(
public resource: URI,
private fromIndex: number,
private toIndex: number,
private editingDelegate: ICellEditingDelegate,
private beforedSelections: number[],
private endSelections: number[]
) {
}
undo(): void | Promise<void> {
if (!this.editingDelegate.moveCell) {
throw new Error('Notebook Move Cell not implemented for Undo/Redo');
}
this.editingDelegate.moveCell(this.toIndex, this.fromIndex);
this.editingDelegate.setSelections(this.beforedSelections);
}
redo(): void | Promise<void> {
if (!this.editingDelegate.moveCell) {
throw new Error('Notebook Move Cell not implemented for Undo/Redo');
}
this.editingDelegate.moveCell(this.fromIndex, this.toIndex);
this.editingDelegate.setSelections(this.endSelections);
}
}
export class SpliceCellsEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = 'Insert Cell';
constructor(
public resource: URI,
private diffs: [number, CellViewModel[], CellViewModel[]][],
private editingDelegate: ICellEditingDelegate,
private beforeHandles: number[],
private endHandles: number[]
) {
}
undo(): void | Promise<void> {
if (!this.editingDelegate.deleteCell || !this.editingDelegate.insertCell) {
throw new Error('Notebook Insert/Delete Cell not implemented for Undo/Redo');
}
this.diffs.forEach(diff => {
for (let i = 0; i < diff[2].length; i++) {
this.editingDelegate.deleteCell!(diff[0]);
}
diff[1].reverse().forEach(cell => {
this.editingDelegate.insertCell!(diff[0], cell);
});
});
this.editingDelegate.setSelections(this.beforeHandles);
}
redo(): void | Promise<void> {
if (!this.editingDelegate.deleteCell || !this.editingDelegate.insertCell) {
throw new Error('Notebook Insert/Delete Cell not implemented for Undo/Redo');
}
this.diffs.reverse().forEach(diff => {
for (let i = 0; i < diff[1].length; i++) {
this.editingDelegate.deleteCell!(diff[0]);
}
diff[2].reverse().forEach(cell => {
this.editingDelegate.insertCell!(diff[0], cell);
});
});
this.editingDelegate.setSelections(this.endHandles);
}
}
export class JoinCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = 'Join Cell';
private _deletedRawCell: ICell;
private _deletedRawCell: NotebookCellTextModel;
constructor(
public resource: URI,
private index: number,
@@ -189,7 +32,7 @@ export class JoinCellEdit implements IResourceUndoRedoElement {
private inverseRange: Range,
private insertContent: string,
private removedCell: BaseCellViewModel,
private editingDelegate: ICellEditingDelegate,
private editingDelegate: IViewCellEditingDelegate,
) {
this._deletedRawCell = this.removedCell.model;
}
@@ -209,12 +52,12 @@ export class JoinCellEdit implements IResourceUndoRedoElement {
const cell = this.editingDelegate.createCellViewModel(this._deletedRawCell);
if (this.direction === 'above') {
this.editingDelegate.insertCell(this.index, cell);
this.editingDelegate.setSelections([cell.handle]);
this.editingDelegate.insertCell(this.index, this._deletedRawCell);
this.editingDelegate.emitSelections([cell.handle]);
cell.focusMode = CellFocusMode.Editor;
} else {
this.editingDelegate.insertCell(this.index, cell);
this.editingDelegate.setSelections([this.cell.handle]);
this.editingDelegate.insertCell(this.index, cell.model);
this.editingDelegate.emitSelections([this.cell.handle]);
this.cell.focusMode = CellFocusMode.Editor;
}
}
@@ -230,7 +73,7 @@ export class JoinCellEdit implements IResourceUndoRedoElement {
]);
this.editingDelegate.deleteCell(this.index);
this.editingDelegate.setSelections([this.cell.handle]);
this.editingDelegate.emitSelections([this.cell.handle]);
this.cell.focusMode = CellFocusMode.Editor;
}
}
@@ -247,13 +90,13 @@ export class SplitCellEdit implements IResourceUndoRedoElement {
private cellContents: string[],
private language: string,
private cellKind: CellKind,
private editingDelegate: ICellEditingDelegate
private editingDelegate: IViewCellEditingDelegate
) {
}
async undo(): Promise<void> {
if (!this.editingDelegate.deleteCell || !this.editingDelegate.createCellViewModel) {
if (!this.editingDelegate.deleteCell) {
throw new Error('Notebook Delete Cell not implemented for Undo/Redo');
}
@@ -270,12 +113,12 @@ export class SplitCellEdit implements IResourceUndoRedoElement {
this.editingDelegate.deleteCell(this.index + 1);
}
this.editingDelegate.setSelections([this.cell.handle]);
this.editingDelegate.emitSelections([this.cell.handle]);
this.cell.focusMode = CellFocusMode.Editor;
}
async redo(): Promise<void> {
if (!this.editingDelegate.insertCell || !this.editingDelegate.createCell) {
if (!this.editingDelegate.createCell) {
throw new Error('Notebook Insert Cell not implemented for Undo/Redo');
}
@@ -291,7 +134,7 @@ export class SplitCellEdit implements IResourceUndoRedoElement {
}
if (lastCell) {
this.editingDelegate.setSelections([lastCell.handle]);
this.editingDelegate.emitSelections([lastCell.handle]);
lastCell.focusMode = CellFocusMode.Editor;
}
}
@@ -173,17 +173,17 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
* Text model is used for editing.
*/
async resolveTextModel(): Promise<model.ITextModel> {
if (!this._textModel) {
if (!this.textModel) {
const ref = await this._modelService.createModelReference(this.model.uri);
this._textModel = ref.object.textEditorModel;
this.textModel = ref.object.textEditorModel;
this._register(ref);
this._register(this._textModel.onDidChangeContent(() => {
this._register(this.textModel.onDidChangeContent(() => {
this.editState = CellEditState.Editing;
this._onDidChangeState.fire({ contentChanged: true });
}));
}
return this._textModel;
return this.textModel;
}
onDeselect() {
@@ -147,16 +147,16 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie
}
async resolveTextModel(): Promise<model.ITextModel> {
if (!this._textModel) {
if (!this.textModel) {
const ref = await this._modelService.createModelReference(this.model.uri);
this._textModel = ref.object.textEditorModel;
this.textModel = ref.object.textEditorModel;
this._register(ref);
this._register(this._textModel.onDidChangeContent(() => {
this._register(this.textModel.onDidChangeContent(() => {
this._html = null;
this._onDidChangeState.fire({ contentChanged: true });
}));
}
return this._textModel;
return this.textModel;
}
onDeselect() {
@@ -19,7 +19,6 @@ import { WorkspaceTextEdit } from 'vs/editor/common/modes';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { CellEditState, CellFindMatch, ICellRange, ICellViewModel, NotebookLayoutInfo, IEditableCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { DeleteCellEdit, InsertCellEdit, MoveCellEdit, SpliceCellsEdit, JoinCellEdit, SplitCellEdit } from 'vs/workbench/contrib/notebook/browser/viewModel/cellEdit';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { NotebookEventDispatcher, NotebookMetadataChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher';
import { CellFoldingState, EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel';
@@ -31,6 +30,8 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no
import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer';
import { dirname } from 'vs/base/common/resources';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { SplitCellEdit, JoinCellEdit } from 'vs/workbench/contrib/notebook/browser/viewModel/cellEdit';
import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel';
import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer';
export interface INotebookEditorViewState {
@@ -199,7 +200,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
return this._notebook.metadata;
}
private readonly _onDidChangeViewCells = new Emitter<INotebookViewCellsUpdateEvent>();
private readonly _onDidChangeViewCells = this._register(new Emitter<INotebookViewCellsUpdateEvent>());
get onDidChangeViewCells(): Event<INotebookViewCellsUpdateEvent> { return this._onDidChangeViewCells.event; }
private _lastNotebookEditResource: URI[] = [];
@@ -215,7 +216,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
return this._layoutInfo;
}
private readonly _onDidChangeSelection = new Emitter<void>();
private readonly _onDidChangeSelection = this._register(new Emitter<void>());
get onDidChangeSelection(): Event<void> { return this._onDidChangeSelection.event; }
private _selections: number[] = [];
@@ -242,6 +243,11 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
public readonly id: string;
private _foldingRanges: FoldingRegions | null = null;
private _hiddenRanges: ICellRange[] = [];
private _focused: boolean = true;
get focused() {
return this._focused;
}
constructor(
public viewType: string,
@@ -259,23 +265,20 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
this._instanceId = strings.singleLetterHash(MODEL_ID);
this._register(this._notebook.onDidChangeCells(e => {
const diffs = e.map(splice => {
const diffs = e.splices.map(splice => {
return [splice[0], splice[1], splice[2].map(cell => {
return createCellViewModel(this._instantiationService, this, cell as NotebookCellTextModel);
})] as [number, number, CellViewModel[]];
});
const undoDiff = diffs.map(diff => {
const deletedCells = this.viewCells.slice(diff[0], diff[0] + diff[1]);
return [diff[0], deletedCells, diff[2]] as [number, CellViewModel[], CellViewModel[]];
});
diffs.reverse().forEach(diff => {
const deletedCells = this._viewCells.splice(diff[0], diff[1], ...diff[2]);
this._decorationsTree.acceptReplace(diff[0], diff[1], diff[2].length, true);
deletedCells.forEach(cell => {
this._handleToViewCellMapping.delete(cell.handle);
// dispsoe the cell to release ref to the cell text document
cell.dispose();
});
diff[2].forEach(cell => {
@@ -285,7 +288,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
});
this._onDidChangeViewCells.fire({
synchronous: true,
synchronous: e.synchronous,
splices: diffs
});
@@ -315,12 +318,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
}
}
this._undoService.pushElement(new SpliceCellsEdit(this.uri, undoDiff, {
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
setSelections: this._setSelectionsDelegate.bind(this)
}, this.selectionHandles, endSelectionHandles));
this.selectionHandles = endSelectionHandles;
}));
@@ -328,6 +325,13 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
this.eventDispatcher.emit([new NotebookMetadataChangedEvent(e)]);
}));
this._register(this._notebook.emitSelections(selections => {
// text model emit selection change (for example, undo/redo)
// we should update the selection handle wisely
// TODO, if the editor is note selected, undo/redo should not change the focused element selection
this.updateSelectionsFromEdits(selections);
}));
this._register(this.eventDispatcher.onDidChangeLayout((e) => {
this._layoutInfo = e.value;
@@ -353,6 +357,16 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
});
}
setFocus(focused: boolean) {
this._focused = focused;
}
updateSelectionsFromEdits(selections: number[]) {
if (this._focused) {
this.selectionHandles = selections;
}
}
getFoldingStartIndex(index: number): number {
if (!this._foldingRanges) {
return -1;
@@ -582,87 +596,20 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
return result;
}
private _createCellDelegate(index: number, source: string | string[], language: string, type: CellKind) {
const cell = this._notebook.createCellTextModel(source, language, type, [], undefined);
let newCell: CellViewModel = createCellViewModel(this._instantiationService, this, cell);
this._viewCells!.splice(index, 0, newCell);
this._handleToViewCellMapping.set(newCell.handle, newCell);
this._notebook.insertNewCell(index, [cell]);
this._localStore.add(newCell);
this._decorationsTree.acceptReplace(index, 0, 1, true);
this._onDidChangeViewCells.fire({ synchronous: true, splices: [[index, 0, [newCell]]] });
return newCell;
}
private _insertCellDelegate(insertIndex: number, insertCell: CellViewModel) {
this._viewCells!.splice(insertIndex, 0, insertCell);
this._handleToViewCellMapping.set(insertCell.handle, insertCell);
this._notebook.insertNewCell(insertIndex, [insertCell.model as NotebookCellTextModel]);
this._localStore.add(insertCell);
this._onDidChangeViewCells.fire({ synchronous: true, splices: [[insertIndex, 0, [insertCell]]] });
}
private _deleteCellDelegate(deleteIndex: number) {
const deleteCell = this._viewCells[deleteIndex];
this._viewCells.splice(deleteIndex, 1);
this._handleToViewCellMapping.delete(deleteCell.handle);
this._notebook.removeCell(deleteIndex, 1);
this._decorationsTree.acceptReplace(deleteIndex, 1, 0, true);
this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] });
}
private _setSelectionsDelegate(selections: number[]) {
this.selectionHandles = selections;
}
createCell(index: number, source: string | string[], language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, synchronous: boolean, pushUndoStop: boolean = true) {
const cell = this._notebook.createCellTextModel(source, language, type, [], metadata);
let newCell: CellViewModel = createCellViewModel(this._instantiationService, this, cell);
this._viewCells!.splice(index, 0, newCell);
this._handleToViewCellMapping.set(newCell.handle, newCell);
this._notebook.insertNewCell(index, [cell]);
this._localStore.add(newCell);
if (pushUndoStop) {
this._undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, {
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
setSelections: this._setSelectionsDelegate.bind(this)
}, this.selectionHandles, this.selectionHandles));
}
this._decorationsTree.acceptReplace(index, 0, 1, true);
this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 0, [newCell]]] });
return newCell;
this._notebook.createCell2(index, source, language, type, metadata, synchronous, pushUndoStop, undefined, undefined);
// TODO, rely on createCell to be sync
return this.viewCells[index];
}
insertCell(index: number, cell: NotebookCellTextModel, synchronous: boolean): CellViewModel {
let newCell: CellViewModel = createCellViewModel(this._instantiationService, this, cell);
this._viewCells!.splice(index, 0, newCell);
this._handleToViewCellMapping.set(newCell.handle, newCell);
this._notebook.insertNewCell(index, [newCell.model]);
this._localStore.add(newCell);
this._undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, {
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
setSelections: this._setSelectionsDelegate.bind(this)
}, this.selectionHandles, this.selectionHandles));
this._decorationsTree.acceptReplace(index, 0, 1, true);
this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 0, [newCell]]] });
return newCell;
insertCell(index: number, cell: NotebookCellTextModel, synchronous: boolean, pushUndoStop: boolean = true): CellViewModel {
this._notebook.insertCell2(index, cell, synchronous, pushUndoStop);
// TODO, rely on createCell to be sync // this will trigger it to synchronous update
return this._viewCells[index];
}
deleteCell(index: number, synchronous: boolean, pushUndoStop: boolean = true) {
const primarySelectionIndex = this.selectionHandles.length ? this._viewCells.indexOf(this.getCellByHandle(this.selectionHandles[0])!) : null;
let viewCell = this._viewCells[index];
this._viewCells.splice(index, 1);
this._handleToViewCellMapping.delete(viewCell.handle);
this._notebook.removeCell(index, 1);
let endSelections: number[] = [];
if (this.selectionHandles.length) {
const primarySelectionHandle = this.selectionHandles[0];
@@ -680,23 +627,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
}
}
if (pushUndoStop) {
this._undoService.pushElement(new DeleteCellEdit(this.uri, index, viewCell, {
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
createCellViewModel: (cell: NotebookCellTextModel) => {
return createCellViewModel(this._instantiationService, this, cell);
},
setSelections: this._setSelectionsDelegate.bind(this)
}, this.selectionHandles, endSelections));
}
this.selectionHandles = endSelections;
this._decorationsTree.acceptReplace(index, 1, 0, true);
this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] });
viewCell.dispose();
this._notebook.deleteCell2(index, synchronous, pushUndoStop, this.selectionHandles, endSelections);
}
moveCellToIdx(index: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean = true): boolean {
@@ -705,24 +636,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
return false;
}
this.viewCells.splice(index, 1);
this.viewCells!.splice(newIdx, 0, viewCell);
this._notebook.moveCellToIdx(index, newIdx);
if (pushedToUndoStack) {
this._undoService.pushElement(new MoveCellEdit(this.uri, index, newIdx, {
moveCell: (fromIndex: number, toIndex: number) => {
this.moveCellToIdx(fromIndex, toIndex, true, false);
},
setSelections: this._setSelectionsDelegate.bind(this)
}, this.selectionHandles, this.selectionHandles));
}
this.selectionHandles = this.selectionHandles;
this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] });
this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[newIdx, 0, [viewCell]]] });
this._notebook.moveCellToIdx2(index, newIdx, synchronous, pushedToUndoStack, undefined, [viewCell.handle]);
return true;
}
@@ -806,23 +720,10 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
let newLinesContents = this._computeCellLinesContents(cell, splitPoints);
if (newLinesContents) {
const editorSelections = cell.getSelections();
// update the contents of the first cell
cell.textModel.applyEdits([
{ range: cell.textModel.getFullModelRange(), text: newLinesContents[0] }
], false);
// create new cells based on the new text models
const language = cell.model.language;
this._notebook.splitNotebookCell(index, newLinesContents, this.selectionHandles);
const language = cell.language;
const kind = cell.cellKind;
let insertIndex = this.getCellIndex(cell) + 1;
const newCells = [];
for (let j = 1; j < newLinesContents.length; j++, insertIndex++) {
newCells.push(this.createCell(insertIndex, newLinesContents[j], language, kind, undefined, true, false));
}
this.selectionHandles = [cell.handle];
this._undoService.pushElement(new SplitCellEdit(
this.uri,
@@ -833,16 +734,17 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
language,
kind,
{
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
createCellViewModel: (cell: NotebookCellTextModel) => {
return createCellViewModel(this._instantiationService, this, cell);
createCell: (index: number, source: string | string[], language: string, type: CellKind) => {
return this.createCell(index, source, language, type, undefined, true, false) as BaseCellViewModel;
},
createCell: this._createCellDelegate.bind(this),
setSelections: this._setSelectionsDelegate.bind(this)
deleteCell: (index: number) => {
this.deleteCell(index, true, false);
},
emitSelections: (selections: number[]) => {
this.updateSelectionsFromEdits(selections);
}
}
));
return newCells;
}
}
@@ -908,12 +810,18 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
insertContent,
cell,
{
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
insertCell: (index: number, cell: NotebookCellTextModel) => {
this.insertCell(index, cell, true, false);
},
deleteCell: (index: number) => {
this.deleteCell(index, true, false);
},
createCellViewModel: (cell: NotebookCellTextModel) => {
return createCellViewModel(this._instantiationService, this, cell);
},
setSelections: this._setSelectionsDelegate.bind(this)
emitSelections: (selections: number[]) => {
this.updateSelectionsFromEdits(selections);
}
})
);
@@ -956,12 +864,18 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
insertContent,
below,
{
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
insertCell: (index: number, cell: NotebookCellTextModel) => {
this.insertCell(index, cell, true, false);
},
deleteCell: (index: number) => {
this.deleteCell(index, true, false);
},
createCellViewModel: (cell: NotebookCellTextModel) => {
return createCellViewModel(this._instantiationService, this, cell);
},
setSelections: this._setSelectionsDelegate.bind(this)
emitSelections: (selections: number[]) => {
this.updateSelectionsFromEdits(selections);
}
})
);
@@ -0,0 +1,184 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IResourceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
/**
* It should not modify Undo/Redo stack
*/
export interface ITextCellEditingDelegate {
insertCell?(index: number, cell: NotebookCellTextModel): void;
deleteCell?(index: number): void;
moveCell?(fromIndex: number, toIndex: number, beforeSelections: number[] | undefined, endSelections: number[] | undefined): void;
emitSelections(selections: number[]): void;
}
export class InsertCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = 'Insert Cell';
constructor(
public resource: URI,
private insertIndex: number,
private cell: NotebookCellTextModel,
private editingDelegate: ITextCellEditingDelegate,
private beforedSelections: number[] | undefined,
private endSelections: number[] | undefined
) {
}
undo(): void | Promise<void> {
if (!this.editingDelegate.deleteCell) {
throw new Error('Notebook Delete Cell not implemented for Undo/Redo');
}
this.editingDelegate.deleteCell(this.insertIndex);
if (this.beforedSelections) {
this.editingDelegate.emitSelections(this.beforedSelections);
}
}
redo(): void | Promise<void> {
if (!this.editingDelegate.insertCell) {
throw new Error('Notebook Insert Cell not implemented for Undo/Redo');
}
this.editingDelegate.insertCell(this.insertIndex, this.cell);
if (this.endSelections) {
this.editingDelegate.emitSelections(this.endSelections);
}
}
}
export class DeleteCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = 'Delete Cell';
constructor(
public resource: URI,
private insertIndex: number,
private _cell: NotebookCellTextModel,
private editingDelegate: ITextCellEditingDelegate,
private beforedSelections: number[] | undefined,
private endSelections: number[] | undefined
) {
// save inmem text to `ICell`
// no needed any more as the text buffer is transfered to `raw_cell`
// this._rawCell.source = [cell.getText()];
}
undo(): void | Promise<void> {
if (!this.editingDelegate.insertCell) {
throw new Error('Notebook Insert Cell not implemented for Undo/Redo');
}
this.editingDelegate.insertCell(this.insertIndex, this._cell);
if (this.beforedSelections) {
this.editingDelegate.emitSelections(this.beforedSelections);
}
}
redo(): void | Promise<void> {
if (!this.editingDelegate.deleteCell) {
throw new Error('Notebook Delete Cell not implemented for Undo/Redo');
}
this.editingDelegate.deleteCell(this.insertIndex);
if (this.endSelections) {
this.editingDelegate.emitSelections(this.endSelections);
}
}
}
export class MoveCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = 'Delete Cell';
constructor(
public resource: URI,
private fromIndex: number,
private toIndex: number,
private editingDelegate: ITextCellEditingDelegate,
private beforedSelections: number[] | undefined,
private endSelections: number[] | undefined
) {
}
undo(): void | Promise<void> {
if (!this.editingDelegate.moveCell) {
throw new Error('Notebook Move Cell not implemented for Undo/Redo');
}
this.editingDelegate.moveCell(this.toIndex, this.fromIndex, this.endSelections, this.beforedSelections);
if (this.beforedSelections) {
this.editingDelegate.emitSelections(this.beforedSelections);
}
}
redo(): void | Promise<void> {
if (!this.editingDelegate.moveCell) {
throw new Error('Notebook Move Cell not implemented for Undo/Redo');
}
this.editingDelegate.moveCell(this.fromIndex, this.toIndex, this.beforedSelections, this.endSelections);
if (this.endSelections) {
this.editingDelegate.emitSelections(this.endSelections);
}
}
}
export class SpliceCellsEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = 'Insert Cell';
constructor(
public resource: URI,
private diffs: [number, NotebookCellTextModel[], NotebookCellTextModel[]][],
private editingDelegate: ITextCellEditingDelegate,
private beforeHandles: number[] | undefined,
private endHandles: number[] | undefined
) {
}
undo(): void | Promise<void> {
if (!this.editingDelegate.deleteCell || !this.editingDelegate.insertCell) {
throw new Error('Notebook Insert/Delete Cell not implemented for Undo/Redo');
}
this.diffs.forEach(diff => {
for (let i = 0; i < diff[2].length; i++) {
this.editingDelegate.deleteCell!(diff[0]);
}
diff[1].reverse().forEach(cell => {
this.editingDelegate.insertCell!(diff[0], cell);
});
});
if (this.beforeHandles) {
this.editingDelegate.emitSelections(this.beforeHandles);
}
}
redo(): void | Promise<void> {
if (!this.editingDelegate.deleteCell || !this.editingDelegate.insertCell) {
throw new Error('Notebook Insert/Delete Cell not implemented for Undo/Redo');
}
this.diffs.reverse().forEach(diff => {
for (let i = 0; i < diff[1].length; i++) {
this.editingDelegate.deleteCell!(diff[0]);
}
diff[2].reverse().forEach(cell => {
this.editingDelegate.insertCell!(diff[0], cell);
});
});
if (this.endHandles) {
this.editingDelegate.emitSelections(this.endHandles);
}
}
}
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { ICell, IProcessedOutput, NotebookCellOutputsSplice, CellKind, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICell, IProcessedOutput, NotebookCellOutputsSplice, CellKind, NotebookCellMetadata, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
import { URI } from 'vs/base/common/uri';
import * as model from 'vs/editor/common/model';
@@ -69,6 +69,16 @@ export class NotebookCellTextModel extends Disposable implements ICell {
return this._textBuffer;
}
private _textModel?: model.ITextModel;
get textModel(): model.ITextModel | undefined {
return this._textModel;
}
set textModel(m: model.ITextModel | undefined) {
this._textModel = m;
}
constructor(
readonly uri: URI,
public handle: number,
@@ -109,4 +119,24 @@ export class NotebookCellTextModel extends Disposable implements ICell {
this._onDidChangeOutputs.fire(splices);
}
getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata): NotebookCellMetadata {
const editable = this.metadata?.editable ??
documentMetadata.cellEditable;
const runnable = this.metadata?.runnable ??
documentMetadata.cellRunnable;
const hasExecutionOrder = this.metadata?.hasExecutionOrder ??
documentMetadata.cellHasExecutionOrder;
return {
...(this.metadata || {}),
...{
editable,
runnable,
hasExecutionOrder
}
};
}
}
@@ -9,6 +9,8 @@ import { URI } from 'vs/base/common/uri';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
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';
import { ITextSnapshot } from 'vs/editor/common/model';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { InsertCellEdit, DeleteCellEdit, MoveCellEdit, SpliceCellsEdit } from 'vs/workbench/contrib/notebook/common/model/cellEdit';
function compareRangesUsingEnds(a: [number, number], b: [number, number]): number {
if (a[1] === b[1]) {
@@ -68,15 +70,17 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
private readonly _onWillDispose: Emitter<void> = this._register(new Emitter<void>());
readonly onWillDispose: Event<void> = this._onWillDispose.event;
private readonly _onDidChangeCells = new Emitter<NotebookCellTextModelSplice[]>();
get onDidChangeCells(): Event<NotebookCellTextModelSplice[]> { return this._onDidChangeCells.event; }
private _onDidModelChangeProxy = new Emitter<NotebookCellsChangedEvent>();
private readonly _onDidChangeCells = this._register(new Emitter<{ synchronous: boolean, splices: NotebookCellTextModelSplice[] }>());
get onDidChangeCells() { return this._onDidChangeCells.event; }
private readonly _emitSelections = this._register(new Emitter<number[]>());
get emitSelections() { return this._emitSelections.event; }
private _onDidModelChangeProxy = this._register(new Emitter<NotebookCellsChangedEvent>());
get onDidModelChangeProxy(): Event<NotebookCellsChangedEvent> { return this._onDidModelChangeProxy.event; }
private _onDidSelectionChangeProxy = new Emitter<number[] | null>();
private _onDidSelectionChangeProxy = this._register(new Emitter<number[] | null>());
get onDidSelectionChange(): Event<number[] | null> { return this._onDidSelectionChangeProxy.event; }
private _onDidChangeContent = new Emitter<void>();
private _onDidChangeContent = this._register(new Emitter<void>());
onDidChangeContent: Event<void> = this._onDidChangeContent.event;
private _onDidChangeMetadata = new Emitter<NotebookDocumentMetadata>();
private _onDidChangeMetadata = this._register(new Emitter<NotebookDocumentMetadata>());
onDidChangeMetadata: Event<NotebookDocumentMetadata> = this._onDidChangeMetadata.event;
private _mapping: Map<number, NotebookCellTextModel> = new Map();
private _cellListeners: Map<number, IDisposable> = new Map();
@@ -110,7 +114,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
public handle: number,
public viewType: string,
public supportBackup: boolean,
public uri: URI
public uri: URI,
private _undoService: IUndoRedoService
) {
super();
this.cells = [];
@@ -165,7 +170,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._increaseVersionId();
}
$applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[], emitToExtHost: boolean = true): boolean {
$applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[], emitToExtHost: boolean, synchronous: boolean): boolean {
if (modelVersionId !== this._versionId) {
return false;
}
@@ -242,7 +247,19 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
});
}
this._onDidChangeCells.fire(diffs);
const undoDiff = diffs.map(diff => {
const deletedCells = this.cells.slice(diff[0], diff[0] + diff[1]);
return [diff[0], deletedCells, diff[2]] as [number, NotebookCellTextModel[], NotebookCellTextModel[]];
});
this._undoService.pushElement(new SpliceCellsEdit(this.uri, undoDiff, {
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
emitSelections: this._emitSelectionsDelegate.bind(this)
}, undefined, undefined));
this._onDidChangeCells.fire({ synchronous: synchronous, splices: diffs });
return true;
}
@@ -446,6 +463,123 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.CellsClearOutput, versionId: this._versionId });
}
//#region Notebook Text Model Edit API
private _insertCellDelegate(insertIndex: number, insertCell: NotebookCellTextModel) {
this.insertNewCell(insertIndex, [insertCell]);
this._onDidChangeCells.fire({ synchronous: true, splices: [[insertIndex, 0, [insertCell]]] });
}
private _deleteCellDelegate(deleteIndex: number) {
this.removeCell(deleteIndex, 1);
this._onDidChangeCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] });
}
private _emitSelectionsDelegate(selections: number[]) {
this._emitSelections.fire(selections);
}
createCell2(index: number, source: string | string[], language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, synchronous: boolean, pushUndoStop: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined) {
const cell = this.createCellTextModel(source, language, type, [], metadata);
if (pushUndoStop) {
this._undoService.pushElement(new InsertCellEdit(this.uri, index, cell, {
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
emitSelections: this._emitSelectionsDelegate.bind(this)
}, beforeSelections, endSelections));
}
this.insertNewCell(index, [cell]);
this._onDidChangeCells.fire({ synchronous, splices: [[index, 0, [cell]]] });
if (endSelections) {
this._emitSelections.fire(endSelections);
}
return cell;
}
insertCell2(index: number, cell: NotebookCellTextModel, synchronous: boolean, pushUndoStop: boolean): void {
if (pushUndoStop) {
this._undoService.pushElement(new InsertCellEdit(this.uri, index, cell, {
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
emitSelections: this._emitSelectionsDelegate.bind(this)
}, undefined, undefined));
}
this.insertNewCell(index, [cell]);
this._onDidChangeCells.fire({ synchronous: synchronous, splices: [[index, 0, [cell]]] });
}
deleteCell2(index: number, synchronous: boolean, pushUndoStop: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined) {
const cell = this.cells[index];
if (pushUndoStop) {
this._undoService.pushElement(new DeleteCellEdit(this.uri, index, cell, {
insertCell: this._insertCellDelegate.bind(this),
deleteCell: this._deleteCellDelegate.bind(this),
emitSelections: this._emitSelectionsDelegate.bind(this)
}, beforeSelections, endSelections));
}
this.removeCell(index, 1);
this._onDidChangeCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] });
if (endSelections) {
this._emitSelections.fire(endSelections);
}
}
moveCellToIdx2(index: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined): boolean {
const cell = this.cells[index];
if (pushedToUndoStack) {
this._undoService.pushElement(new MoveCellEdit(this.uri, index, newIdx, {
moveCell: (fromIndex: number, toIndex: number, beforeSelections: number[] | undefined, endSelections: number[] | undefined) => {
this.moveCellToIdx2(fromIndex, toIndex, true, false, beforeSelections, endSelections);
},
emitSelections: this._emitSelectionsDelegate.bind(this)
}, beforeSelections, endSelections));
}
this.moveCellToIdx(index, newIdx);
// todo, we can't emit this change as it will create a new view model and that will hold
// a new reference to the document, thus
this._onDidChangeCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] });
this._onDidChangeCells.fire({ synchronous: synchronous, splices: [[newIdx, 0, [cell]]] });
if (endSelections) {
this._emitSelections.fire(endSelections);
}
return true;
}
async splitNotebookCell(index: number, newLinesContents: string[], endSelections: number[]) {
const cell = this.cells[index];
if (!cell.textModel) {
return;
}
cell.textModel.applyEdits([
{ range: cell.textModel.getFullModelRange(), text: newLinesContents[0] }
], false);
// create new cells based on the new text models
const language = cell.language;
const kind = cell.cellKind;
let insertIndex = index + 1;
const newCells = [];
for (let j = 1; j < newLinesContents.length; j++, insertIndex++) {
newCells.push(this.createCell2(insertIndex, newLinesContents[j], language, kind, undefined, true, false, undefined, undefined));
}
if (endSelections) {
this._emitSelections.fire(endSelections);
}
}
//#endregion
dispose() {
this._onWillDispose.fire();
this._cellListeners.forEach(val => val.dispose());
@@ -269,7 +269,7 @@ export interface INotebookTextModel {
languages: string[];
cells: ICell[];
renderers: Set<string>;
onDidChangeCells?: Event<NotebookCellTextModelSplice[]>;
onDidChangeCells?: Event<{ synchronous: boolean, splices: NotebookCellTextModelSplice[] }>;
onDidChangeContent: Event<void>;
onWillDispose(listener: () => void): IDisposable;
}
@@ -31,7 +31,7 @@ suite('NotebookTextModel', () => {
textModel.$applyEdit(textModel.versionId, [
{ editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [])] },
{ editType: CellEditType.Insert, index: 3, cells: [new TestCell(viewModel.viewType, 6, ['var f = 6;'], 'javascript', CellKind.Code, [])] },
]);
], true, true);
assert.equal(textModel.cells.length, 6);
@@ -56,7 +56,7 @@ suite('NotebookTextModel', () => {
textModel.$applyEdit(textModel.versionId, [
{ editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [])] },
{ editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 6, ['var f = 6;'], 'javascript', CellKind.Code, [])] },
]);
], true, true);
assert.equal(textModel.cells.length, 6);
@@ -81,7 +81,7 @@ suite('NotebookTextModel', () => {
textModel.$applyEdit(textModel.versionId, [
{ editType: CellEditType.Delete, index: 1, count: 1 },
{ editType: CellEditType.Delete, index: 3, count: 1 },
]);
], true, true);
assert.equal(textModel.cells[0].getValue(), 'var a = 1;');
assert.equal(textModel.cells[1].getValue(), 'var c = 3;');
@@ -104,7 +104,7 @@ suite('NotebookTextModel', () => {
textModel.$applyEdit(textModel.versionId, [
{ editType: CellEditType.Delete, index: 1, count: 1 },
{ editType: CellEditType.Insert, index: 3, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [])] },
]);
], true, true);
assert.equal(textModel.cells.length, 4);
@@ -129,7 +129,7 @@ suite('NotebookTextModel', () => {
textModel.$applyEdit(textModel.versionId, [
{ editType: CellEditType.Delete, index: 1, count: 1 },
{ editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [])] },
]);
], true, true);
assert.equal(textModel.cells.length, 4);
assert.equal(textModel.cells[0].getValue(), 'var a = 1;');
@@ -23,7 +23,7 @@ suite('NotebookViewModel', () => {
instantiationService.spy(IUndoRedoService, 'pushElement');
test('ctor', function () {
const notebook = new NotebookTextModel(0, 'notebook', false, URI.parse('test'));
const notebook = new NotebookTextModel(0, 'notebook', false, URI.parse('test'), undoRedoService);
const model = new NotebookEditorTestModel(notebook);
const eventDispatcher = new NotebookEventDispatcher();
const viewModel = new NotebookViewModel('notebook', model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService);
@@ -137,15 +137,15 @@ export class TestNotebookEditor implements INotebookEditor {
throw new Error('Method not implemented.');
}
moveCellDown(cell: CellViewModel): Promise<boolean> {
moveCellDown(cell: CellViewModel): Promise<ICellViewModel | null> {
throw new Error('Method not implemented.');
}
moveCellUp(cell: CellViewModel): Promise<boolean> {
moveCellUp(cell: CellViewModel): Promise<ICellViewModel | null> {
throw new Error('Method not implemented.');
}
moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise<boolean> {
moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise<ICellViewModel | null> {
throw new Error('Method not implemented.');
}
@@ -320,7 +320,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi
export function withTestNotebook(instantiationService: IInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string[], string, CellKind, IProcessedOutput[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void) {
const viewType = 'notebook';
const editor = new TestNotebookEditor();
const notebook = new NotebookTextModel(0, viewType, false, URI.parse('test'));
const notebook = new NotebookTextModel(0, viewType, false, URI.parse('test'), undoRedoService);
notebook.cells = cells.map((cell, index) => {
return new NotebookCellTextModel(notebook.uri, index, cell[0], cell[1], cell[2], cell[3], cell[4]);
});