diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index c09d171fc6d97acac03d0f0d193a8a1634fc45ee..c34d11c4bafd0e0f0aa6f1a7b46a909301325413 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -70,8 +70,11 @@ async function saveFileAndCloseAll(resource: vscode.Uri) { await documentClosed; } -async function saveAllFilesAndCloseAll(resource: vscode.Uri) { +async function saveAllFilesAndCloseAll(resource: vscode.Uri | undefined) { const documentClosed = new Promise((resolve, _reject) => { + if (!resource) { + return resolve(); + } const d = vscode.notebook.onDidCloseNotebookDocument(e => { if (e.uri.toString() === resource.toString()) { d.dispose(); @@ -393,23 +396,107 @@ suite('Notebook API tests', () => { await saveFileAndCloseAll(resource); }); - test('edit API', async function () { + test('edit API (replaceCells)', async function () { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { - editBuilder.insert(1, 'test 2', 'javascript', vscode.CellKind.Code, [], undefined); + editBuilder.replaceCells(1, 0, [{ cellKind: vscode.CellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); }); const cellChangeEventRet = await cellsChangeEvent; - assert.equal(cellChangeEventRet.document, vscode.notebook.activeNotebookEditor?.document); - assert.equal(cellChangeEventRet.changes.length, 1); - assert.deepEqual(cellChangeEventRet.changes[0].start, 1); - assert.deepEqual(cellChangeEventRet.changes[0].deletedCount, 0); - assert.equal(cellChangeEventRet.changes[0].items[0], vscode.notebook.activeNotebookEditor!.document.cells[1]); + assert.strictEqual(cellChangeEventRet.document === vscode.notebook.activeNotebookEditor?.document, true); + assert.strictEqual(cellChangeEventRet.document.isDirty, true); + assert.strictEqual(cellChangeEventRet.changes.length, 1); + assert.strictEqual(cellChangeEventRet.changes[0].start, 1); + assert.strictEqual(cellChangeEventRet.changes[0].deletedCount, 0); + assert.strictEqual(cellChangeEventRet.changes[0].items[0] === vscode.notebook.activeNotebookEditor!.document.cells[1], true); + + await saveAllFilesAndCloseAll(resource); + }); + + test('edit API (replaceOutput)', async function () { + // no output events yet... + this.skip(); + + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceOutput(0, [{ outputKind: vscode.CellOutputKind.Rich, data: { foo: 'bar' } }]); + }); + + const document = vscode.notebook.activeNotebookEditor?.document!; + assert.strictEqual(document.isDirty, false); + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].outputs.length, 1); + assert.strictEqual(document.cells[0].outputs[0].outputKind, vscode.CellOutputKind.Rich); + + await saveAllFilesAndCloseAll(undefined); + }); + + test('edit API (replaceOutput, event)', async function () { + // no output events yet... + this.skip(); + + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const outputChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceOutput(0, [{ outputKind: vscode.CellOutputKind.Rich, data: { foo: 'bar' } }]); + }); + + const value = await outputChangeEvent; + assert.strictEqual(value.document === vscode.notebook.activeNotebookEditor?.document, true); + assert.strictEqual(value.cells.length, 1); + assert.strictEqual(value.cells[0].outputs.length, 1); + assert.strictEqual(value.cells[0].outputs[0].outputKind, vscode.CellOutputKind.Rich); + + await saveAllFilesAndCloseAll(undefined); + }); + + test('edit API (replaceMetadata)', async function () { + + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceMetadata(0, { inputCollapsed: true, executionOrder: 17 }); + }); + + const document = vscode.notebook.activeNotebookEditor?.document!; + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].metadata.executionOrder, 17); + assert.strictEqual(document.cells[0].metadata.inputCollapsed, true); + + assert.strictEqual(document.isDirty, true); + await saveFileAndCloseAll(resource); + }); + + test('edit API (replaceMetadata, event)', async function () { + + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const event = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); + + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceMetadata(0, { inputCollapsed: true, executionOrder: 17 }); + }); + + const data = await event; + assert.strictEqual(data.document, vscode.notebook.activeNotebookEditor?.document); + assert.strictEqual(data.cell.metadata.executionOrder, 17); + assert.strictEqual(data.cell.metadata.inputCollapsed, true); + assert.strictEqual(data.document.isDirty, true); await saveFileAndCloseAll(resource); }); diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 7b67595e6ac70a9944a0f1875a0413c41249acc6..dd8b3f29585c3c2580b959905f42954b7aae0052 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -242,7 +242,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo const disposableStore = new DisposableStore(); const textModel = this._notebookService.getNotebookTextModel(doc); disposableStore.add(textModel!.onDidModelChangeProxy(e => { - this._proxy.$acceptModelChanged(textModel!.uri, e); + this._proxy.$acceptModelChanged(textModel!.uri, e, textModel!.isDirty); this._proxy.$acceptEditorPropertiesChanged(doc, { selections: { selections: textModel!.selections }, metadata: null }); })); disposableStore.add(textModel!.onDidSelectionChange(e => { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2ff5e70e14a0faad1851019cdeb38279d5b11bef..25182aee42f483d536b2828caf84849147474ae4 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1668,7 +1668,7 @@ export interface ExtHostNotebookShape { $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }): void; $onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void; - $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; + $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent, isDirty: boolean): void; $acceptModelSaved(uriComponents: UriComponents): void; $acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void; $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index ae6d7860f6275ba38a7d1bb3926c91e827b0870e..f9ba5b1ef75705e48b9a86a9c8b2da2c65d3aaed 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -213,6 +213,7 @@ export class ExtHostNotebookDocument extends Disposable { private _metadataChangeListener: IDisposable; private _displayOrder: string[] = []; private _versionId = 0; + private _isDirty: boolean = false; private _backupCounter = 1; private _backup?: vscode.NotebookDocumentBackup; private _disposed = false; @@ -274,7 +275,7 @@ export class ExtHostNotebookDocument extends Disposable { get version() { return that._versionId; }, get fileName() { return that.uri.fsPath; }, get viewType() { return that._viewType; }, - get isDirty() { return false; }, + get isDirty() { return that._isDirty; }, get isUntitled() { return that.uri.scheme === Schemas.untitled; }, get cells(): ReadonlyArray { return that._cells.map(cell => cell.cell); }, get languages() { return that._languages; }, @@ -311,8 +312,9 @@ export class ExtHostNotebookDocument extends Disposable { this._backup = undefined; } - acceptModelChanged(event: NotebookCellsChangedEvent): void { + acceptModelChanged(event: NotebookCellsChangedEvent, isDirty: boolean): void { this._versionId = event.versionId; + this._isDirty = isDirty; if (event.kind === NotebookCellsChangeType.Initialize) { this._spliceNotebookCells(event.changes, true); } if (event.kind === NotebookCellsChangeType.ModelChange) { @@ -1255,12 +1257,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN this._webviewComm.get(editorId)?.onDidReceiveMessage(forRendererType, message); } - $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void { - + $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent, isDirty: boolean): void { const document = this._documents.get(URI.revive(uriComponents)); - if (document) { - document.acceptModelChanged(event); + document.acceptModelChanged(event, isDirty); } } @@ -1401,7 +1401,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN 0, modelData.cells ]] - }); + }, false); // add cell document as vscode.TextDocument addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(document.notebookDocument, cell)));