diff --git a/.eslintrc.json b/.eslintrc.json index 49414693957..7a3b9a7459d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -811,8 +811,7 @@ "trigger", "unregister", "write", - "move", - "clear" + "move" ] } ] diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index 4e5bd7769b8..958e642daae 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -8,25 +8,161 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import { join } from 'path'; -function waitFor(ms: number): Promise { - let resolveFunc: () => void; +export function once(event: vscode.Event): vscode.Event { + return (listener: any, thisArgs = null, disposables?: any) => { + // we need this, in case the event fires during the listener call + let didFire = false; + let result: vscode.Disposable; + result = event(e => { + if (didFire) { + return; + } else if (result) { + result.dispose(); + } else { + didFire = true; + } - const promise = new Promise(resolve => { - resolveFunc = resolve; - }); - setTimeout(() => { - resolveFunc!(); - }, ms); + return listener.call(thisArgs, e); + }, null, disposables); - return promise; + if (didFire) { + result.dispose(); + } + + return result; + }; } +async function getEventOncePromise(event: vscode.Event): Promise { + return new Promise((resolve, _reject) => { + once(event)((result: T) => resolve(result)); + }); +} + +suite('API tests', () => { + test('document open/close event', async function () { + const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + const firstDocumentOpen = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await firstDocumentOpen; + + const firstDocumentClose = getEventOncePromise(vscode.notebook.onDidCloseNotebookDocument); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await firstDocumentClose; + }); + + test('shared document in notebook editors', async function () { + const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + let counter = 0; + const disposables: vscode.Disposable[] = []; + disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => { + counter++; + })); + disposables.push(vscode.notebook.onDidCloseNotebookDocument(() => { + counter--; + })); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(counter, 1); + + await vscode.commands.executeCommand('workbench.action.splitEditor'); + assert.equal(counter, 1); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + assert.equal(counter, 0); + + disposables.forEach(d => d.dispose()); + }); + + test('editor open/close event', async function () { + const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + const firstEditorOpen = getEventOncePromise(vscode.notebook.onDidChangeVisibleNotebookEditors); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await firstEditorOpen; + + const firstEditorClose = getEventOncePromise(vscode.notebook.onDidChangeVisibleNotebookEditors); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await firstEditorClose; + }); + + test('editor open/close event', async function () { + const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + let count = 0; + const disposables: vscode.Disposable[] = []; + disposables.push(vscode.notebook.onDidChangeVisibleNotebookEditors(() => { + count = vscode.notebook.visibleNotebookEditors.length; + })); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(count, 1); + + await vscode.commands.executeCommand('workbench.action.splitEditor'); + assert.equal(count, 2); + + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + assert.equal(count, 0); + }); + + test('editor editing event', async function () { + const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + 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, + deletedCount: 0, + items: [ + vscode.notebook.activeNotebookEditor!.document.cells[1] + ] + }); + + const moveCellEvent = getEventOncePromise(vscode.notebook.onDidMoveNotebookCell); + await vscode.commands.executeCommand('notebook.cell.moveUp'); + const moveCellEventRet = await moveCellEvent; + assert.deepEqual(moveCellEventRet, { + document: vscode.notebook.activeNotebookEditor!.document, + index: 1, + newIndex: 0 + }); + + const cellOutputChange = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.commands.executeCommand('notebook.cell.execute'); + const cellOutputsAddedRet = await cellOutputChange; + assert.deepEqual(cellOutputsAddedRet, { + document: vscode.notebook.activeNotebookEditor!.document, + cells: [vscode.notebook.activeNotebookEditor!.document.cells[0]] + }); + assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 1); + + const cellOutputClear = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.commands.executeCommand('notebook.cell.clearOutputs'); + const cellOutputsCleardRet = await cellOutputClear; + assert.deepEqual(cellOutputsCleardRet, { + document: vscode.notebook.activeNotebookEditor!.document, + cells: [vscode.notebook.activeNotebookEditor!.document.cells[0]] + }); + assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 0); + + // const cellChangeLanguage = getEventOncePromise(vscode.notebook.onDidChangeCellLanguage); + // await vscode.commands.executeCommand('notebook.cell.changeToMarkdown'); + // const cellChangeLanguageRet = await cellChangeLanguage; + // assert.deepEqual(cellChangeLanguageRet, { + // document: vscode.notebook.activeNotebookEditor!.document, + // cells: vscode.notebook.activeNotebookEditor!.document.cells[0], + // language: 'markdown' + // }); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); +}); + suite('notebook workflow', () => { test('notebook open', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, 'test'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); @@ -48,8 +184,6 @@ suite('notebook workflow', () => { test('notebook cell actions', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, 'test'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); @@ -119,8 +253,6 @@ suite('notebook workflow', () => { test('move cells will not recreate cells in ExtHost', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); await vscode.commands.executeCommand('notebook.focusTop'); @@ -139,8 +271,6 @@ suite('notebook workflow', () => { // const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await waitFor(500); - // assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); // const editor = vscode.notebook.activeNotebookEditor!; @@ -164,9 +294,6 @@ suite('notebook workflow', () => { test('cell runnable metadata is respected', async () => { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); - assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.notebook.activeNotebookEditor!; @@ -188,9 +315,6 @@ suite('notebook workflow', () => { test('document runnable metadata is respected', async () => { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); - assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.notebook.activeNotebookEditor!; @@ -213,8 +337,6 @@ suite('notebook dirty state', () => { test('notebook open', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, 'test'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); @@ -235,8 +357,6 @@ suite('notebook dirty state', () => { await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[1], vscode.notebook.activeNotebookEditor?.selection); @@ -251,8 +371,6 @@ suite('notebook undo redo', () => { test('notebook open', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, 'test'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); @@ -296,8 +414,6 @@ suite('notebook working copy', () => { test('notebook revert on close', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, ''); @@ -307,8 +423,6 @@ suite('notebook working copy', () => { // close active editor from command will revert the file await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[0], vscode.notebook.activeNotebookEditor?.selection); @@ -321,8 +435,6 @@ suite('notebook working copy', () => { test('notebook revert', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, ''); @@ -342,8 +454,6 @@ suite('notebook working copy', () => { test('multiple tabs: dirty + clean', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, ''); @@ -352,7 +462,6 @@ suite('notebook working copy', () => { const secondResource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './second.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - await waitFor(500); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); // make sure that the previous dirty editor is still restored in the extension host and no data loss @@ -369,8 +478,6 @@ suite('notebook working copy', () => { test('multiple tabs: two dirty tabs and switching', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, ''); @@ -379,7 +486,6 @@ suite('notebook working copy', () => { const secondResource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './second.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - await waitFor(500); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, ''); @@ -407,8 +513,6 @@ suite('notebook working copy', () => { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); const firstNotebookEditor = vscode.notebook.activeNotebookEditor; assert.equal(firstNotebookEditor !== undefined, true, 'notebook first'); assert.equal(firstNotebookEditor!.selection?.source, 'test'); @@ -433,8 +537,6 @@ suite('metadata', () => { test('custom metadata should be supported', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); @@ -446,8 +548,6 @@ suite('metadata', () => { test('custom metadata should be supported', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); @@ -464,8 +564,6 @@ suite('regression', () => { test('microsoft/vscode-github-issue-notebooks#26. Insert template cell in the new empty document', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, ''); assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); @@ -476,8 +574,6 @@ suite('regression', () => { test('#97830, #97764. Support switch to other editor types', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); @@ -496,15 +592,11 @@ suite('regression', () => { test('#96105 - dirty editors', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); - - await waitFor(500); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); // now it's dirty, open the resource with notebook editor should open a new one await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await waitFor(500); - assert.notEqual(vscode.notebook.activeNotebookEditor, undefined, 'notebook first'); assert.notEqual(vscode.window.activeTextEditor, undefined); @@ -518,10 +610,9 @@ suite('webview resource uri', () => { test('asWebviewUri', async function () { const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await waitFor(500); assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); const uri = vscode.notebook.activeNotebookEditor!.asWebviewUri(vscode.Uri.parse('./hello.png')); assert.equal(uri.scheme, 'vscode-webview-resource'); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); }); }); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 01399110f29..69badea9ba8 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1801,7 +1801,7 @@ declare module 'vscode' { } - export interface NotebookCellsChange { + export interface NotebookCellsChangeData { readonly start: number; readonly deletedCount: number; readonly items: NotebookCell[]; @@ -1813,7 +1813,7 @@ declare module 'vscode' { * The affected document. */ readonly document: NotebookDocument; - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; } export interface NotebookCellMoveEvent { @@ -1826,21 +1826,13 @@ declare module 'vscode' { readonly newIndex: number; } - export interface NotebookCellOutputsClearEvent { - - /** - * The affected document. - */ - readonly document: NotebookDocument; - readonly cell: NotebookCell; - } - - export interface NotebookAllCellsOutputsClearEvent { + export interface NotebookCellOutputsChangeEvent { /** * The affected document. */ readonly document: NotebookDocument; + readonly cells: NotebookCell[]; } export interface NotebookCellLanguageChangeEvent { @@ -1929,8 +1921,7 @@ declare module 'vscode' { export const onDidChangeNotebookCells: Event; export const onDidMoveNotebookCell: Event; - export const onDidClearCellOutputs: Event; - export const onDidClearAllCellsOutputs: Event; + export const onDidChangeCellOutputs: Event; export const onDidChangeCellLanguage: Event; /** * Create a document that is the concatenation of all notebook cells. By default all code-cells are included diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 8840907e6d5..bf40d92fcc4 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -952,13 +952,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidMoveNotebookCell(listener, thisArgs, disposables); }, - onDidClearCellOutputs(listener, thisArgs?, disposables?) { + onDidChangeCellOutputs(listener, thisArgs?, disposables?) { checkProposedApiEnabled(extension); - return extHostNotebook.onDidClearCellOutputs(listener, thisArgs, disposables); - }, - onDidClearAllCellsOutputs(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); - return extHostNotebook.onDidClearAllCellsOutputs(listener, thisArgs, disposables); + return extHostNotebook.onDidChangeCellOutputs(listener, thisArgs, disposables); }, onDidChangeCellLanguage(listener, thisArgs?, disposables?) { checkProposedApiEnabled(extension); diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index c5e7f1e7ea1..2147a49ad16 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -45,8 +45,7 @@ function getObservable(obj: T): IObservable { interface INotebookEventEmitter { emitModelChange(events: vscode.NotebookCellsChangeEvent): void; emitMoveChange(event: vscode.NotebookCellMoveEvent): void; - emitCellOutputsClear(event: vscode.NotebookCellOutputsClearEvent): void; - emitAllCellsOutputsClearEvent(event: vscode.NotebookAllCellsOutputsClearEvent): void; + emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void; emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void; } @@ -232,6 +231,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo constructor( private readonly _proxy: MainThreadNotebookShape, private _documentsAndEditors: ExtHostDocumentsAndEditors, + private _emitter: INotebookEventEmitter, public viewType: string, public uri: URI, public renderingHandler: ExtHostNotebookOutputRenderingHandler @@ -259,22 +259,22 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo get isDirty() { return false; } - accpetModelChanged(event: NotebookCellsChangedEvent, emitter?: INotebookEventEmitter): void { + accpetModelChanged(event: NotebookCellsChangedEvent): void { this._versionId = event.versionId; if (event.kind === NotebookCellsChangeType.ModelChange) { - this.$spliceNotebookCells(event.changes, emitter); + this.$spliceNotebookCells(event.changes); } else if (event.kind === NotebookCellsChangeType.Move) { - this.$moveCell(event.index, event.newIdx, emitter); + this.$moveCell(event.index, event.newIdx); } else if (event.kind === NotebookCellsChangeType.CellClearOutput) { - this.$clearCellOutputs(event.index, emitter); + this.$clearCellOutputs(event.index); } else if (event.kind === NotebookCellsChangeType.CellsClearOutput) { - this.$clearAllCellOutputs(emitter); + this.$clearAllCellOutputs(); } else if (event.kind === NotebookCellsChangeType.ChangeLanguage) { - this.$changeCellLanguage(event.index, event.language, emitter); + this.$changeCellLanguage(event.index, event.language); } } - private $spliceNotebookCells(splices: NotebookCellsSplice2[], emitter?: INotebookEventEmitter): void { + private $spliceNotebookCells(splices: NotebookCellsSplice2[]): void { if (this._disposed) { return; } @@ -283,7 +283,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo return; } - let contentChangeEvents: vscode.NotebookCellsChange[] = []; + let contentChangeEvents: vscode.NotebookCellsChangeData[] = []; splices.reverse().forEach(splice => { let cellDtos = splice[2]; @@ -316,7 +316,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo this.cells.splice(splice[0], splice[1], ...newCells); - const event: vscode.NotebookCellsChange = { + const event: vscode.NotebookCellsChangeData = { start: splice[0], deletedCount: splice[1], items: newCells @@ -325,40 +325,46 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo contentChangeEvents.push(event); }); - emitter?.emitModelChange({ + this._emitter.emitModelChange({ document: this, changes: contentChangeEvents }); } - private $moveCell(index: number, newIdx: number, emitter?: INotebookEventEmitter): void { + private $moveCell(index: number, newIdx: number): void { const cells = this.cells.splice(index, 1); this.cells.splice(newIdx, 0, ...cells); const event: vscode.NotebookCellMoveEvent = { document: this, index, newIndex: newIdx }; - emitter?.emitMoveChange(event); + this._emitter.emitMoveChange(event); } - private $clearCellOutputs(index: number, emitter?: INotebookEventEmitter): void { + private $clearCellOutputs(index: number): void { const cell = this.cells[index]; cell.outputs = []; - const event: vscode.NotebookCellOutputsClearEvent = { document: this, cell }; - emitter?.emitCellOutputsClear(event); + const event: vscode.NotebookCellOutputsChangeEvent = { document: this, cells: [cell] }; + this._emitter.emitCellOutputsChange(event); } - private $clearAllCellOutputs(emitter?: INotebookEventEmitter): void { - this.cells.forEach(cell => cell.outputs = []); - const event: vscode.NotebookAllCellsOutputsClearEvent = { document: this }; - emitter?.emitAllCellsOutputsClearEvent(event); + private $clearAllCellOutputs(): void { + const modifedCells: vscode.NotebookCell[] = []; + this.cells.forEach(cell => { + if (cell.outputs.length !== 0) { + cell.outputs = []; + modifedCells.push(cell); + } + }); + const event: vscode.NotebookCellOutputsChangeEvent = { document: this, cells: modifedCells }; + this._emitter.emitCellOutputsChange(event); } - private $changeCellLanguage(index: number, language: string, emitter?: INotebookEventEmitter): void { + private $changeCellLanguage(index: number, language: string): void { const cell = this.cells[index]; cell.language = language; const event: vscode.NotebookCellLanguageChangeEvent = { document: this, cell, language }; - emitter?.emitCellLanguageChange(event); + this._emitter.emitCellLanguageChange(event); } - eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice[]) { + async eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice[]) { let renderers = new Set(); let outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => { let outputs = diff.toInsert; @@ -379,7 +385,11 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo return [diff.start, diff.deleteCount, transformedOutputs]; }); - this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers)); + await this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers)); + this._emitter.emitCellOutputsChange({ + document: this, + cells: [cell] + }); } transformMimeTypes(output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { @@ -674,10 +684,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event; private readonly _onDidMoveNotebookCell = new Emitter(); readonly onDidMoveNotebookCell = this._onDidMoveNotebookCell.event; - private readonly _onDidClearCellOutputs = new Emitter(); - readonly onDidClearCellOutputs = this._onDidClearCellOutputs.event; - private readonly _onDidClearAllCellsOutputs = new Emitter(); - readonly onDidClearAllCellsOutputs = this._onDidClearAllCellsOutputs.event; + private readonly _onDidChangeCellOutputs = new Emitter(); + readonly onDidChangeCellOutputs = this._onDidChangeCellOutputs.event; private readonly _onDidChangeCellLanguage = new Emitter(); readonly onDidChangeCellLanguage = this._onDidChangeCellLanguage.event; @@ -797,7 +805,21 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN let document = this._documents.get(URI.revive(uri).toString()); if (!document) { - document = this._unInitializedDocuments.get(revivedUri.toString()) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this); + const that = this; + document = this._unInitializedDocuments.get(revivedUri.toString()) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, { + emitModelChange(event: vscode.NotebookCellsChangeEvent): void { + that._onDidChangeNotebookCells.fire(event); + }, + emitMoveChange(event: vscode.NotebookCellMoveEvent): void { + that._onDidMoveNotebookCell.fire(event); + }, + emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void { + that._onDidChangeCellOutputs.fire(event); + }, + emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { + that._onDidChangeCellLanguage.fire(event); + } + }, viewType, revivedUri, this); this._unInitializedDocuments.set(revivedUri.toString(), document); } @@ -1003,24 +1025,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const document = this._documents.get(URI.revive(uriComponents).toString()); if (document) { - const that = this; - document.accpetModelChanged(event, { - emitModelChange(event: vscode.NotebookCellsChangeEvent): void { - that._onDidChangeNotebookCells.fire(event); - }, - emitMoveChange(event: vscode.NotebookCellMoveEvent): void { - that._onDidMoveNotebookCell.fire(event); - }, - emitCellOutputsClear(event: vscode.NotebookCellOutputsClearEvent): void { - that._onDidClearCellOutputs.fire(event); - }, - emitAllCellsOutputsClearEvent(event: vscode.NotebookAllCellsOutputsClearEvent): void { - that._onDidClearAllCellsOutputs.fire(event); - }, - emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { - that._onDidChangeCellLanguage.fire(event); - } - }); + document.accpetModelChanged(event); } } @@ -1111,7 +1116,22 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const revivedUriStr = revivedUri.toString(); const viewType = modelData.viewType; if (!this._documents.has(revivedUriStr)) { - let document = this._unInitializedDocuments.get(revivedUriStr) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this); + const that = this; + let document = this._unInitializedDocuments.get(revivedUriStr) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, { + emitModelChange(event: vscode.NotebookCellsChangeEvent): void { + that._onDidChangeNotebookCells.fire(event); + }, + emitMoveChange(event: vscode.NotebookCellMoveEvent): void { + that._onDidMoveNotebookCell.fire(event); + }, + emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void { + that._onDidChangeCellOutputs.fire(event); + }, + emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { + that._onDidChangeCellLanguage.fire(event); + } + }, viewType, revivedUri, this); + this._unInitializedDocuments.delete(revivedUriStr); if (modelData.metadata) { document.metadata = { diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index 36f488c4ab3..6ff2c217afc 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -54,8 +54,7 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD this._disposables.add(extHostNotebooks.onDidMoveNotebookCell(e => documentChange(e.document))); this._disposables.add(extHostNotebooks.onDidChangeCellLanguage(e => documentChange(e.document))); - this._disposables.add(extHostNotebooks.onDidClearAllCellsOutputs(e => documentChange(e.document))); - this._disposables.add(extHostNotebooks.onDidClearCellOutputs(e => documentChange(e.document))); + this._disposables.add(extHostNotebooks.onDidChangeCellOutputs(e => documentChange(e.document))); this._disposables.add(extHostNotebooks.onDidChangeNotebookCells(e => documentChange(e.document))); }