Change:
The following changes to the current output API proposals are on the table. The focus is on simplifying and streamlining things (while still being equally powerful) Instead of three types for output, have a single type The effective output of a cell () is many of these types (e.g NotebookCellOutput[]) Each instance of has a mime type, a value, and its metadata (iff applicable) Use a mime type for , respectively use an error mime type for Usage sample
partial implementation is here
First round of feedback showed that we need to have more structure, e.g an array of arrarys of outputs. Will update the sample above
Proposal and setting output is merged, but reading output is still the old API
from The need for an extension to concat previous output bits with new output bits is bogus and wasteful, esp. since we will immediately remove what's already there. So, the idea of append or update output is something we should go after. I see two possibilities: something similar to a -object to refer to an previous output-object to which is wants to be appended The former approach is something well-known in API terms but we must be able to "spy" on the object so that we know when 'appendOutput' or such is called. The API concept for this to spec types as interface only and by offering a way to create an instance of said interface, e.g . This works well for long-lived objects but is likely cumbersome when the lifetime is short - as it is with notebook output. It is cumbersome because the extension owns the object and therefore must dispose it but the UI and API know/control when the object is of no more interest. Therefore the second option might be better and simpler: An extension can keep hold of the last output and when creating a new object it would pass it along. It could look like so (building on the new output proposal ) This makes it data-only objects which is generally simpler and preferred. What remains is how to signal to an extension that a complete new output is now active? The events we have for that might be enough tho
At this stage, I can only see this being useful & working (for python extension) when output is cleared. I.e. output is empty. I.e. we monitor changes to cell output (existing events in API). Else as an extension, I'll continue to assume the output we have is the latest. Though this does open the door for issues when user hits or the like (SCM revert), and this results in output changing, and we aren't in sync with those.
I see two kinds: is cleared (e.g by the user) and that's not a problem is reset (e.g by another extension) and that's something you might wanna know. Tho, I am unsure about that
I work on the for VS Code and we have some questions regarding the existing and proposed cell output functionality. Our question seems to have been partially discussed above, but specifically, it's around the handling of STDOUT. The suggestion is to treat cell stream output as ​, but that will (and currently does) give the wrong output for regular STDOUT. The issue is best expressed by example: Lab with a Python notebook with the following code: The output is the same as you'd get from a command line app: A notebook opened with the Python VS Code extension also behaves this way. Lab with a .NET Interactive notebook with the following code: The output is the same as you'd get from a command line app: Code with a .NET Interactive notebook (i.e., the extension I work on) with the same code as (2) produces this output: The issue ultimately stems from each call to getting wrapped in its own DOM element where from the user's perspective, these should really be together, probably replacing/updating an existing DOM element. Have you had any discussions with the Python team around how to handle console/stream output? I'm not sure what the solution looks like, but possibly it could fit into the existing and proposed models as a special mime type that corresponds to STDOUT and that VS Code does the special updating/replacing? Something like ​? Or perhaps it's a different API to append to the STDOUT display stream? Given that this is a problem that every notebook kernel author will encounter, I'm hoping that there'll be a common approach to handling the display of this.
Can you validate you requirements against my thought: So, with the append-output we would not treat and render outputs as new things, e.g would/should not contain newlines and be rendered on a single line.
You mean the contructor argument of ? If I'm understanding the proposal, then our extension would track a specific object that corresponds to STDOUT of cell 1 and another object for STDERR. Then in our own code when we get a signal of STDOUT value received, we simply return a new setting the value to the previous value, then tracking the new one for next time? E.g.,
Yes, along those lines. The final API shape might still change but the essence is that we allow you append to an existing output. Todays proposal uses the linking via the ctor-arg but I am also thinking of something event based
Does this new API support concatenating output received from terminals. For instance today we handle progress bars displayed in terminals. We handle the command when concatenating the stdout/stderr.
We have done this. With the revised extension we are going to change how output items are delivered to the editor

Discussion:
await saveAllFilesAndCloseAll(resource); }); <ins> 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((<vscode.CellDisplayOutput>output).data['application/foo'], 'bar'); assert.deepStrictEqual((<vscode.CellDisplayOutput>output).data['application/json'], { data: true }); assert.deepStrictEqual((<vscode.CellDisplayOutput>output).metadata, { custom: { 'application/json': { metadata: true } } }); await saveAllFilesAndCloseAll(undefined); }); </ins> test('edit API (replaceOutput)', async function () { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb');

export type CellOutput = CellStreamOutput | CellErrorOutput | CellDisplayOutput; <ins> export class NotebookCellOutputItem { readonly mime: string; readonly value: unknown; readonly metadata?: Record<string, string | number | boolean>; constructor(mime: string, value: unknown, metadata?: Record<string, string | number | boolean>); } //TODO@jrieken add id? export class NotebookCellOutput { readonly outputs: NotebookCellOutputItem[]; readonly metadata?: Record<string, string | number | boolean>; constructor(outputs: NotebookCellOutputItem[], metadata?: Record<string, string | number | boolean>); //TODO@jrieken HACK to workaround dependency issues... toJSON(): any; } </ins> export enum NotebookCellRunState { Running = 1, Idle = 2,

export interface WorkspaceEdit { replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void; replaceNotebookCells(uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void; <del> replaceNotebookCellOutput(uri: Uri, index: number, outputs: CellOutput[], metadata?: WorkspaceEditEntryMetadata): void; </del> <ins> replaceNotebookCellOutput(uri: Uri, index: number, outputs: (NotebookCellOutput | CellOutput)[], metadata?: WorkspaceEditEntryMetadata): void; </ins> 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; <del> replaceCellOutput(index: number, outputs: CellOutput[]): void; </del> <ins> replaceCellOutput(index: number, outputs: (NotebookCellOutput | CellOutput)[]): void; </ins> replaceCellMetadata(index: number, metadata: NotebookCellMetadata): void; }

NotebookCellRunState: extHostTypes.NotebookCellRunState, NotebookRunState: extHostTypes.NotebookRunState, NotebookCellStatusBarAlignment: extHostTypes.NotebookCellStatusBarAlignment, <del> NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType </del> <ins> NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType, NotebookCellOutput: extHostTypes.NotebookCellOutput, NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem, </ins> }; }; }

}); } <del> replaceCellOutput(index: number, outputs: vscode.CellOutput[]): void { </del> <ins> replaceCellOutput(index: number, outputs: (vscode.NotebookCellOutput | vscode.CellOutput)[]): void { </ins> this._throwIfFinalized(); this._collectedEdits.push({ editType: CellEditType.Output, index, <del> outputs: outputs.map(output => addIdToOutput(output)) </del> <ins> outputs: outputs.map(output => { if (extHostTypes.NotebookCellOutput.isNotebookCellOutput(output)) { return addIdToOutput(output.toJSON()); } else { return addIdToOutput(output); } }) </ins> }); }

import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; <del> import { INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; </del> <ins> import { CellOutputKind, IDisplayOutput, INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; </ins> export interface PositionLike { line: number;

} } <ins> 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 } }; } } </ins> 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;

import { generateUuid } from 'vs/base/common/uuid'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; <del> import { addIdToOutput, CellEditType, ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; </del> <ins> import { addIdToOutput, CellEditType, ICellEditOperation, IDisplayOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; </ins> import type * as vscode from 'vscode'; function es5ClassCompat(target: Function): any {

} } <del> 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)) } }); </del> <ins> 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); } }) } }); </ins> } replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void {

//#region Notebook <ins> 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<string, string | number | boolean> ) { } } export class NotebookCellOutput { static isNotebookCellOutput(obj: unknown): obj is vscode.NotebookCellOutput { return obj instanceof NotebookCellOutput; } constructor( readonly outputs: NotebookCellOutputItem[], readonly metadata?: Record<string, string | number | boolean> ) { } 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 }; } } </ins> export enum CellKind { Markdown = 1, Code = 2