diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index e3ea9eefeceb03ccf0f133f8aabf6991b04ab207..91a6a1c88e7c8f2f8368acc86ea2473f70e3c468 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -417,6 +417,34 @@ suite('Notebook API tests', () => { await saveAllFilesAndCloseAll(resource); }); + test('edit API (replaceOutput, USE NotebookCellOutput-type)', 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.replaceCellOutput(0, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('application/foo', 'bar'), + new vscode.NotebookCellOutputItem('application/json', { data: true }, { metadata: true }), + ])]); + }); + + const document = vscode.notebook.activeNotebookEditor?.document!; + assert.strictEqual(document.isDirty, true); + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].outputs.length, 1); + + // consuming is OLD api (for now) + const [output] = document.cells[0].outputs; + + assert.strictEqual(output.outputKind, vscode.CellOutputKind.Rich); + assert.strictEqual((output).data['application/foo'], 'bar'); + assert.deepStrictEqual((output).data['application/json'], { data: true }); + assert.deepStrictEqual((output).metadata, { custom: { 'application/json': { metadata: true } } }); + + await saveAllFilesAndCloseAll(undefined); + }); + test('edit API (replaceOutput)', async function () { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 6544399ba02bb40240996377828e03f796a3abe6..43b7b86306a94d0821f3585ac59c9fac07bbb84b 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1226,6 +1226,27 @@ declare module 'vscode' { export type CellOutput = CellStreamOutput | CellErrorOutput | CellDisplayOutput; + export class NotebookCellOutputItem { + + readonly mime: string; + readonly value: unknown; + readonly metadata?: Record; + + constructor(mime: string, value: unknown, metadata?: Record); + } + + //TODO@jrieken add id? + export class NotebookCellOutput { + + readonly outputs: NotebookCellOutputItem[]; + readonly metadata?: Record; + + constructor(outputs: NotebookCellOutputItem[], metadata?: Record); + + //TODO@jrieken HACK to workaround dependency issues... + toJSON(): any; + } + export enum NotebookCellRunState { Running = 1, Idle = 2, @@ -1407,14 +1428,14 @@ declare module 'vscode' { export interface WorkspaceEdit { replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void; replaceNotebookCells(uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void; - replaceNotebookCellOutput(uri: Uri, index: number, outputs: CellOutput[], metadata?: WorkspaceEditEntryMetadata): void; + replaceNotebookCellOutput(uri: Uri, index: number, outputs: (NotebookCellOutput | CellOutput)[], metadata?: WorkspaceEditEntryMetadata): void; replaceNotebookCellMetadata(uri: Uri, index: number, cellMetadata: NotebookCellMetadata, metadata?: WorkspaceEditEntryMetadata): void; } export interface NotebookEditorEdit { replaceMetadata(value: NotebookDocumentMetadata): void; replaceCells(start: number, end: number, cells: NotebookCellData[]): void; - replaceCellOutput(index: number, outputs: CellOutput[]): void; + replaceCellOutput(index: number, outputs: (NotebookCellOutput | CellOutput)[]): void; replaceCellMetadata(index: number, metadata: NotebookCellMetadata): void; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 7917c01235919b0322614f369e0cf14c7b2fa423..2a0576b68f943f63c010dd496e094311bdc149f0 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1147,7 +1147,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I NotebookCellRunState: extHostTypes.NotebookCellRunState, NotebookRunState: extHostTypes.NotebookRunState, NotebookCellStatusBarAlignment: extHostTypes.NotebookCellStatusBarAlignment, - NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType + NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType, + NotebookCellOutput: extHostTypes.NotebookCellOutput, + NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem, }; }; } diff --git a/src/vs/workbench/api/common/extHostNotebookEditor.ts b/src/vs/workbench/api/common/extHostNotebookEditor.ts index ea5ecb3512d9c7f6749fe82f3b6434c6a7077bf4..60e3af8a29395da1b2aad6ca409ba349435e4344 100644 --- a/src/vs/workbench/api/common/extHostNotebookEditor.ts +++ b/src/vs/workbench/api/common/extHostNotebookEditor.ts @@ -54,12 +54,18 @@ class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit { }); } - replaceCellOutput(index: number, outputs: vscode.CellOutput[]): void { + replaceCellOutput(index: number, outputs: (vscode.NotebookCellOutput | vscode.CellOutput)[]): void { this._throwIfFinalized(); this._collectedEdits.push({ editType: CellEditType.Output, index, - outputs: outputs.map(output => addIdToOutput(output)) + outputs: outputs.map(output => { + if (extHostTypes.NotebookCellOutput.isNotebookCellOutput(output)) { + return addIdToOutput(output.toJSON()); + } else { + return addIdToOutput(output); + } + }) }); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 7d59ed2e3c75b0453c9edc911265b550f6e48978..c0eb2a21f6a9991f9b5868a73df54afc8a56aa83 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -32,7 +32,7 @@ import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; -import { INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellOutputKind, IDisplayOutput, INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export interface PositionLike { line: number; @@ -1297,6 +1297,22 @@ export namespace LogLevel { } } +export namespace NotebookCellOutput { + export function from(output: types.NotebookCellOutput): IDisplayOutput { + return output.toJSON(); + } +} + +export namespace NotebookCellOutputItem { + export function from(output: types.NotebookCellOutputItem): IDisplayOutput { + return { + outputKind: CellOutputKind.Rich, + data: { [output.mime]: output.value }, + metadata: output.metadata && { custom: output.metadata } + }; + } +} + export namespace NotebookExclusiveDocumentPattern { export function from(pattern: { include: vscode.GlobPattern | undefined, exclude: vscode.GlobPattern | undefined }): { include: string | types.RelativePattern | undefined, exclude: string | types.RelativePattern | undefined }; export function from(pattern: vscode.GlobPattern): string | types.RelativePattern; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 3ba9bc36d3ccdfc2d5e060067bf86bd8ab7acf6e..e97f74d422402ceea6c4d7b10424ca7616c21451 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -14,7 +14,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { addIdToOutput, CellEditType, ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { addIdToOutput, CellEditType, ICellEditOperation, IDisplayOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as vscode from 'vscode'; function es5ClassCompat(target: Function): any { @@ -641,8 +641,18 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } } - replaceNotebookCellOutput(uri: URI, index: number, outputs: vscode.CellOutput[], metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.Output, index, outputs: outputs.map(output => addIdToOutput(output)) } }); + replaceNotebookCellOutput(uri: URI, index: number, outputs: (vscode.NotebookCellOutput | vscode.CellOutput)[], metadata?: vscode.WorkspaceEditEntryMetadata): void { + this._edits.push({ + _type: FileEditType.Cell, metadata, uri, edit: { + editType: CellEditType.Output, index, outputs: outputs.map(output => { + if (NotebookCellOutput.isNotebookCellOutput(output)) { + return addIdToOutput(output.toJSON()); + } else { + return addIdToOutput(output); + } + }) + } + }); } replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void { @@ -2772,6 +2782,50 @@ export enum ColorThemeKind { //#region Notebook +export class NotebookCellOutputItem { + + static isNotebookCellOutputItem(obj: unknown): obj is vscode.NotebookCellOutputItem { + return obj instanceof NotebookCellOutputItem; + } + + constructor( + readonly mime: string, + readonly value: unknown, // JSON'able + readonly metadata?: Record + ) { } +} + +export class NotebookCellOutput { + + static isNotebookCellOutput(obj: unknown): obj is vscode.NotebookCellOutput { + return obj instanceof NotebookCellOutput; + } + + constructor( + readonly outputs: NotebookCellOutputItem[], + readonly metadata?: Record + ) { } + + toJSON(): IDisplayOutput { + let data: { [key: string]: unknown; } = {}; + let custom: { [key: string]: unknown; } = {}; + let hasMetadata = false; + + for (let item of this.outputs) { + data[item.mime] = item.value; + if (item.metadata) { + custom[item.mime] = item.metadata; + hasMetadata = true; + } + } + return { + outputKind: CellOutputKind.Rich, + data, + metadata: hasMetadata ? { custom } : undefined + }; + } +} + export enum CellKind { Markdown = 1, Code = 2