diff --git a/extensions/notebook-renderers/package.json b/extensions/notebook-renderers/package.json index 18d013b2655..9b85015d4bd 100644 --- a/extensions/notebook-renderers/package.json +++ b/extensions/notebook-renderers/package.json @@ -1,7 +1,7 @@ { "name": "notebook-renderers", - "displayName": "Notebook Renderers", - "description": "Notebook renderers outputs", + "displayName": "Nteract Renderer", + "description": "Render notebook outputs with nteract", "extensionKind": [ "ui", "workspace" diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 00dfb4d44fe..e6d56a2eeca 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -10,7 +10,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/browser/notebookService'; import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { ICell, IOutput, INotebook, INotebookMimeTypeSelector } from 'vs/workbench/contrib/notebook/common/notebook'; +import { ICell, IOutput, INotebook, INotebookMimeTypeSelector } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class MainThreadCell implements ICell { private _onDidChangeOutputs = new Emitter(); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f7448456e56..61e20103834 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -50,7 +50,7 @@ import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtens import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline'; -import { INotebook, ICell, INotebookMimeTypeSelector } from 'vs/workbench/contrib/notebook/common/notebook'; +import { INotebook, ICell, INotebookMimeTypeSelector } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 1e449f377ed..e20a7a68a05 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -14,7 +14,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { ICell } from 'vs/workbench/contrib/notebook/common/notebook'; +import { ICell } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class ExtHostCell implements vscode.NotebookCell { @@ -69,6 +69,18 @@ export class ExtHostCell implements vscode.NotebookCell { } } +const standardTransforms = [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/svg+xml', + 'image/png', + 'image/jpeg', + 'text/plain' +]; + export class ExtHostNotebookDocument implements vscode.NotebookDocument { private static _handlePool: number = 0; readonly handle = ExtHostNotebookDocument._handlePool++; diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index e667272690a..c492e61cecd 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -25,7 +25,14 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContextKey, InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookHandler'; +import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +// Output renderers registration + +import 'vs/workbench/contrib/notebook/browser/output/transforms/streamTransform'; +import 'vs/workbench/contrib/notebook/browser/output/transforms/errorTransform'; +import 'vs/workbench/contrib/notebook/browser/output/transforms/richTransform'; + Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( diff --git a/src/vs/workbench/contrib/notebook/browser/notebookHandler.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts similarity index 93% rename from src/vs/workbench/contrib/notebook/browser/notebookHandler.ts rename to src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index fbebd398d67..d8a1258319a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookHandler.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -9,6 +9,7 @@ import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/renderers/cellViewModel'; +import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/output/outputRenderer'; export interface NotebookHandler { viewType: string | undefined; @@ -24,6 +25,7 @@ export interface NotebookHandler { triggerWheel(event: IMouseWheelEvent): void; getFontInfo(): BareFontInfo | undefined; getListDimension(): DOM.Dimension | null; + getOutputRenderer(): OutputRenderer; } export interface CellRenderTemplate { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 4ddf834ef2d..9e49b1c274b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -33,8 +33,9 @@ import { getZoomLevel } from 'vs/base/browser/browser'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { INotebook, CELL_MARGIN } from 'vs/workbench/contrib/notebook/common/notebook'; -import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookHandler'; +import { INotebook, CELL_MARGIN } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/output/outputRenderer'; const $ = DOM.$; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -63,6 +64,7 @@ export class NotebookEditor extends BaseEditor implements NotebookHandler { private relayoutDisposable: IDisposable | null = null; private dimension: DOM.Dimension | null = null; private editorFocus: IContextKey | null = null; + private outputRenderer: OutputRenderer; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -82,6 +84,11 @@ export class NotebookEditor extends BaseEditor implements NotebookHandler { super(NotebookEditor.ID, telemetryService, themeService, storageService); this.editorMemento = this.getEditorMemento(editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY); + this.outputRenderer = new OutputRenderer(this, this.instantiationService); + } + + getOutputRenderer(): OutputRenderer { + return this.outputRenderer; } get minimumWidth(): number { return 375; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 61a43a36a76..277eb95722d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -8,7 +8,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextModel } from 'vs/editor/common/model'; import { Emitter, Event } from 'vs/base/common/event'; import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; -import { INotebook, ICell } from 'vs/workbench/contrib/notebook/common/notebook'; +import { INotebook, ICell } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class NotebookEditorModel extends EditorModel { private _dirty = false; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts b/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts new file mode 100644 index 00000000000..542fd4e77e1 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IOutputTransformContribution } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { BrandedService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; +import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +export type IOutputTransformCtor = IConstructorSignature1; + +export interface IOutputTransformDescription { + id: string; + types: string[]; + ctor: IOutputTransformCtor; +} + +export namespace NotebookRegistry { + export function getOutputTransformContributions(): IOutputTransformDescription[] { + return NotebookRegistryImpl.INSTANCE.getNotebookOutputTransform(); + } +} + +export function registerOutputTransform(id: string, types: string[], ctor: { new(handler: NotebookHandler, ...services: Services): IOutputTransformContribution }): void { + NotebookRegistryImpl.INSTANCE.registerOutputTransform(id, types, ctor); +} + +class NotebookRegistryImpl { + + public static readonly INSTANCE = new NotebookRegistryImpl(); + + private readonly outputTransforms: IOutputTransformDescription[]; + + constructor() { + this.outputTransforms = []; + } + + public registerOutputTransform(id: string, types: string[], ctor: { new(handler: NotebookHandler, ...services: Services): IOutputTransformContribution }): void { + this.outputTransforms.push({ id: id, types: types, ctor: ctor as IOutputTransformCtor }); + } + + public getNotebookOutputTransform(): IOutputTransformDescription[] { + return this.outputTransforms.slice(0); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookService.ts b/src/vs/workbench/contrib/notebook/browser/notebookService.ts index a3c5c5df7c4..05da8dbc211 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookService.ts @@ -11,7 +11,7 @@ import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/noteb import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { INotebook, ICell, INotebookMimeTypeSelector } from 'vs/workbench/contrib/notebook/common/notebook'; +import { INotebook, ICell, INotebookMimeTypeSelector } from 'vs/workbench/contrib/notebook/common/notebookCommon'; function MODEL_ID(resource: URI): string { return resource.toString(); diff --git a/src/vs/workbench/contrib/notebook/browser/output/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/output/outputRenderer.ts new file mode 100644 index 00000000000..311ad7a6a50 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/output/outputRenderer.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IOutputTransformContribution, IOutput, IRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +export class OutputRenderer { + protected readonly _contributions: { [key: string]: IOutputTransformContribution; }; + protected readonly _mimeTypeMapping: { [key: string]: IOutputTransformContribution; }; + + constructor( + private readonly notebookHandler: NotebookHandler, + private readonly instantiationService: IInstantiationService + ) { + this._contributions = {}; + this._mimeTypeMapping = {}; + + let contributions = NotebookRegistry.getOutputTransformContributions(); + + for (const desc of contributions) { + try { + const contribution = this.instantiationService.createInstance(desc.ctor, notebookHandler); + this._contributions[desc.id] = contribution; + desc.types.forEach(mimeType => { + this._mimeTypeMapping[mimeType] = contribution; + }); + } catch (err) { + onUnexpectedError(err); + } + } + } + + renderNoop(output: IOutput, container: HTMLElement): IRenderOutput { + const contentNode = document.createElement('p'); + + contentNode.innerText = `No renderer could be found for output. It has the following output type: ${output.output_type}`; + container.appendChild(contentNode); + return { + hasDynamicHeight: false + }; + } + + render(output: IOutput, container: HTMLElement): IRenderOutput { + let transform = this._mimeTypeMapping[output.output_type]; + + if (transform) { + return transform.render(output, container); + } else { + return this.renderNoop(output, container); + } + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/output/transforms/errorTransform.ts similarity index 61% rename from src/vs/workbench/contrib/notebook/browser/renderers/outputRenderer.ts rename to src/vs/workbench/contrib/notebook/browser/output/transforms/errorTransform.ts index 7676eaa10b1..214745c4cba 100644 --- a/src/vs/workbench/contrib/notebook/browser/renderers/outputRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/output/transforms/errorTransform.ts @@ -3,95 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IOutputTransformContribution, IRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import * as DOM from 'vs/base/browser/dom'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { RGBA, Color } from 'vs/base/common/color'; import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; -import { isArray } from 'vs/base/common/types'; -import * as marked from 'vs/base/common/marked/marked'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { URI } from 'vs/base/common/uri'; -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IOutput } from 'vs/workbench/contrib/notebook/common/notebook'; -import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookHandler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -export function registerMineTypeRenderer(types: string[], renderer: IMimeRenderer) { - types.forEach(type => { - MimeTypeRenderer.instance.register(type, renderer); - }); -} - -export interface IRenderOutput { - shadowContent?: string; - hasDynamicHeight: boolean; -} - -interface IMimeRenderer { - render(output: IOutput, container: HTMLElement, themeService: IThemeService, instantiationService: IInstantiationService, modelService: IModelService, modeService: IModeService, notebookHandler?: NotebookHandler): IRenderOutput; -} - -export class MimeTypeRenderer { - static instance = new MimeTypeRenderer(); - private readonly _renderers = new Map(); - - register(type: string, renderer: IMimeRenderer) { - this._renderers.set(type, renderer); +class ErrorTransform implements IOutputTransformContribution { + constructor( + public handler: NotebookHandler, + @IThemeService private readonly themeService: IThemeService + ) { } - getRenderer(type: string) { - return this._renderers.get(type); - } - - static renderNoop(output: IOutput, container: HTMLElement): IRenderOutput { - const contentNode = document.createElement('p'); - - contentNode.innerText = `No renderer could be found for output. It has the following output type: ${output.output_type}`; - container.appendChild(contentNode); - return { - hasDynamicHeight: false - }; - } - - static render(output: IOutput, container: HTMLElement, themeService: IThemeService, instantiationService: IInstantiationService, modelService: IModelService, modeService: IModeService, notebookHandler: NotebookHandler): IRenderOutput { - return MimeTypeRenderer.instance.getRenderer(output.output_type)?.render(output, container, themeService, instantiationService, modelService, modeService, notebookHandler) ?? MimeTypeRenderer.renderNoop(output, container); - } -} - -registerMineTypeRenderer(['stream'], { - render: ( - output: IOutput, - container: HTMLElement, - themeService: IThemeService, - instantiationService: IInstantiationService, - modelService: IModelService, - modeService: IModeService - ) => { - const contentNode = document.createElement('p'); - contentNode.innerText = output.text; - container.appendChild(contentNode); - return { - hasDynamicHeight: false - }; - } -}); - -registerMineTypeRenderer(['error'], { - render: ( - output: IOutput, - container: HTMLElement, - themeService: IThemeService, - instantiationService: IInstantiationService, - modelService: IModelService, - modeService: IModeService - ) => { + render(output: any, container: HTMLElement): IRenderOutput { const traceback = document.createElement('pre'); DOM.addClasses(traceback, 'traceback'); if (output.traceback) { for (let j = 0; j < output.traceback.length; j++) { - traceback.appendChild(handleANSIOutput(output.traceback[j], themeService)); + traceback.appendChild(handleANSIOutput(output.traceback[j], this.themeService)); } } container.appendChild(traceback); @@ -99,143 +31,12 @@ registerMineTypeRenderer(['error'], { hasDynamicHeight: false }; } -}); -// Display Order -// -// application/json -// application/javascript -// text/html -// image/svg+xml -// text/markdown -// text/latex -// image/svg+xml -// image/gif -// image/png -// image/jpeg -// application/pdf -// text/plain - -class RichDisplayRenderer implements IMimeRenderer { - private _mdRenderer: marked.Renderer = new marked.Renderer({ gfm: true });; - - render(output: any, container: HTMLElement, themeService: IThemeService, instantiationService: IInstantiationService, modelService: IModelService, modeService: IModeService, notebookHandler: NotebookHandler): IRenderOutput { - let hasDynamicHeight = false; - - if (output.data) { - if (output.data['application/json']) { - let data = output.data['application/json']; - let str = JSON.stringify(data, null, '\t'); - - const editor = instantiationService.createInstance(CodeEditorWidget, container, { - ...getJSONSimpleEditorOptions(), - dimension: { - width: 0, - height: 0 - } - }, { - isSimpleWidget: true - }); - - let mode = modeService.create('json'); - let resource = URI.parse(`notebook-output-${Date.now()}.json`); - const textModel = modelService.createModel(str, mode, resource, false); - editor.setModel(textModel); - - let width = notebookHandler.getListDimension()!.width; - let fontInfo = notebookHandler.getFontInfo(); - let height = Math.min(textModel.getLineCount(), 16) * (fontInfo?.lineHeight || 18); - - editor.layout({ - height, - width - }); - - container.style.height = `${height + 16}px`; - - return { - hasDynamicHeight: true - }; - } else if (output.data['application/javascript']) { - let data = output.data['application/javascript']; - let str = isArray(data) ? data.join('') : data; - let scriptVal = ``; - hasDynamicHeight = false; - return { - shadowContent: scriptVal, - hasDynamicHeight - }; - } else if (output.data['text/html']) { - let data = output.data['text/html']; - let str = isArray(data) ? data.join('') : data; - hasDynamicHeight = false; - return { - shadowContent: str, - hasDynamicHeight - }; - } else if (output.data['image/svg+xml']) { - let data = output.data['image/svg+xml']; - let str = isArray(data) ? data.join('') : data; - hasDynamicHeight = false; - return { - shadowContent: str, - hasDynamicHeight - }; - } else if (output.data['text/markdown']) { - let data = output.data['text/markdown']; - const str = isArray(data) ? data.join('') : data; - const mdOutput = document.createElement('div'); - mdOutput.innerHTML = marked(str, { renderer: this._mdRenderer }); - container.appendChild(mdOutput); - hasDynamicHeight = true; - } else if (output.data['image/png']) { - const image = document.createElement('img'); - image.src = `data:image/png;base64,${output.data['image/png']}`; - const display = document.createElement('div'); - DOM.addClasses(display, 'display'); - display.appendChild(image); - container.appendChild(display); - hasDynamicHeight = true; - } else if (output.data['image/jpeg']) { - const image = document.createElement('img'); - image.src = `data:image/jpeg;base64,${output.data['image/jpeg']}`; - const display = document.createElement('div'); - DOM.addClasses(display, 'display'); - display.appendChild(image); - container.appendChild(display); - hasDynamicHeight = true; - } else if (output.data['text/plain']) { - let data = output.data['text/plain']; - let str = isArray(data) ? data.join('') : data; - const contentNode = document.createElement('p'); - contentNode.innerText = str; - container.appendChild(contentNode); - } else { - const contentNode = document.createElement('p'); - let mimeTypes = []; - for (const property in output.data) { - mimeTypes.push(property); - } - - let mimeTypesMessage = mimeTypes.join(', '); - - contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`; - container.appendChild(contentNode); - } - } else { - const contentNode = document.createElement('p'); - contentNode.innerText = `No data could be found for output.`; - container.appendChild(contentNode); - } - - return { - hasDynamicHeight - }; + dispose(): void { } } -registerMineTypeRenderer(['display_data', 'execute_result'], new RichDisplayRenderer()); - +registerOutputTransform('notebook.output.error', ['error'], ErrorTransform); /** * @param text The content to stylize. @@ -583,25 +384,3 @@ export function calcANSI8bitColor(colorNumber: number): RGBA | undefined { return undefined; } } - -export function getJSONSimpleEditorOptions(): IEditorOptions { - return { - wordWrap: 'on', - overviewRulerLanes: 0, - glyphMargin: false, - selectOnLineNumbers: false, - hideCursorInOverviewRuler: true, - selectionHighlight: false, - lineDecorationsWidth: 0, - overviewRulerBorder: false, - scrollBeyondLastLine: false, - renderLineHighlight: 'none', - minimap: { - enabled: false - }, - lineNumbers: 'off', - scrollbar: { - alwaysConsumeMouseWheel: false - } - }; -} diff --git a/src/vs/workbench/contrib/notebook/browser/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/output/transforms/richTransform.ts new file mode 100644 index 00000000000..3448af74c4d --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/output/transforms/richTransform.ts @@ -0,0 +1,171 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IOutputTransformContribution, IRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; +import * as DOM from 'vs/base/browser/dom'; +import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { isArray } from 'vs/base/common/types'; +import * as marked from 'vs/base/common/marked/marked'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { URI } from 'vs/base/common/uri'; + +class RichRenderer implements IOutputTransformContribution { + private _mdRenderer: marked.Renderer = new marked.Renderer({ gfm: true });; + + constructor( + public handler: NotebookHandler, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService + ) { + } + + render(output: any, container: HTMLElement): IRenderOutput { + let hasDynamicHeight = false; + + if (output.data) { + if (output.data['application/json']) { + let data = output.data['application/json']; + let str = JSON.stringify(data, null, '\t'); + + const editor = this.instantiationService.createInstance(CodeEditorWidget, container, { + ...getJSONSimpleEditorOptions(), + dimension: { + width: 0, + height: 0 + } + }, { + isSimpleWidget: true + }); + + let mode = this.modeService.create('json'); + let resource = URI.parse(`notebook-output-${Date.now()}.json`); + const textModel = this.modelService.createModel(str, mode, resource, false); + editor.setModel(textModel); + + let width = this.handler.getListDimension()!.width; + let fontInfo = this.handler.getFontInfo(); + let height = Math.min(textModel.getLineCount(), 16) * (fontInfo?.lineHeight || 18); + + editor.layout({ + height, + width + }); + + container.style.height = `${height + 16}px`; + + return { + hasDynamicHeight: true + }; + } else if (output.data['application/javascript']) { + let data = output.data['application/javascript']; + let str = isArray(data) ? data.join('') : data; + let scriptVal = ``; + hasDynamicHeight = false; + return { + shadowContent: scriptVal, + hasDynamicHeight + }; + } else if (output.data['text/html']) { + let data = output.data['text/html']; + let str = isArray(data) ? data.join('') : data; + hasDynamicHeight = false; + return { + shadowContent: str, + hasDynamicHeight + }; + } else if (output.data['image/svg+xml']) { + let data = output.data['image/svg+xml']; + let str = isArray(data) ? data.join('') : data; + hasDynamicHeight = false; + return { + shadowContent: str, + hasDynamicHeight + }; + } else if (output.data['text/markdown']) { + let data = output.data['text/markdown']; + const str = isArray(data) ? data.join('') : data; + const mdOutput = document.createElement('div'); + mdOutput.innerHTML = marked(str, { renderer: this._mdRenderer }); + container.appendChild(mdOutput); + hasDynamicHeight = true; + } else if (output.data['image/png']) { + const image = document.createElement('img'); + image.src = `data:image/png;base64,${output.data['image/png']}`; + const display = document.createElement('div'); + DOM.addClasses(display, 'display'); + display.appendChild(image); + container.appendChild(display); + hasDynamicHeight = true; + } else if (output.data['image/jpeg']) { + const image = document.createElement('img'); + image.src = `data:image/jpeg;base64,${output.data['image/jpeg']}`; + const display = document.createElement('div'); + DOM.addClasses(display, 'display'); + display.appendChild(image); + container.appendChild(display); + hasDynamicHeight = true; + } else if (output.data['text/plain']) { + let data = output.data['text/plain']; + let str = isArray(data) ? data.join('') : data; + const contentNode = document.createElement('p'); + contentNode.innerText = str; + container.appendChild(contentNode); + } else { + const contentNode = document.createElement('p'); + let mimeTypes = []; + for (const property in output.data) { + mimeTypes.push(property); + } + + let mimeTypesMessage = mimeTypes.join(', '); + + contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`; + container.appendChild(contentNode); + } + } else { + const contentNode = document.createElement('p'); + contentNode.innerText = `No data could be found for output.`; + container.appendChild(contentNode); + } + + return { + hasDynamicHeight + }; + } + + dispose(): void { + } +} + +registerOutputTransform('notebook.output.rich', ['display_data', 'execute_result'], RichRenderer); + + +export function getJSONSimpleEditorOptions(): IEditorOptions { + return { + wordWrap: 'on', + overviewRulerLanes: 0, + glyphMargin: false, + selectOnLineNumbers: false, + hideCursorInOverviewRuler: true, + selectionHighlight: false, + lineDecorationsWidth: 0, + overviewRulerBorder: false, + scrollBeyondLastLine: false, + renderLineHighlight: 'none', + minimap: { + enabled: false + }, + lineNumbers: 'off', + scrollbar: { + alwaysConsumeMouseWheel: false + } + }; +} diff --git a/src/vs/workbench/contrib/notebook/browser/output/transforms/streamTransform.ts b/src/vs/workbench/contrib/notebook/browser/output/transforms/streamTransform.ts new file mode 100644 index 00000000000..198ec654756 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/output/transforms/streamTransform.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IOutputTransformContribution, IRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; +import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +class StreamRenderer implements IOutputTransformContribution { + constructor( + handler: NotebookHandler + ) { + } + + render(output: any, container: HTMLElement): IRenderOutput { + const contentNode = document.createElement('p'); + contentNode.innerText = output.text; + container.appendChild(contentNode); + return { + hasDynamicHeight: false + }; + + } + + dispose(): void { + } +} + +registerOutputTransform('notebook.output.stream', ['stream'], StreamRenderer); diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts index 15b1abb547a..a9e433ec21f 100644 --- a/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts @@ -22,7 +22,7 @@ import { CellViewModel } from './cellViewModel'; import { CodeCell } from 'vs/workbench/contrib/notebook/browser/renderers/codeCell'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { CellRenderTemplate, NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookHandler'; +import { CellRenderTemplate, NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; export class NotebookCellListDelegate implements IListVirtualDelegate { private _lineHeight: number; diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/cellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/renderers/cellViewModel.ts index bc50991eb6e..7dae0f5379c 100644 --- a/src/vs/workbench/contrib/notebook/browser/renderers/cellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/renderers/cellViewModel.ts @@ -14,7 +14,7 @@ import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/renderer import { IOpenerService } from 'vs/platform/opener/common/opener'; import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ICell } from 'vs/workbench/contrib/notebook/common/notebook'; +import { ICell } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class CellViewModel extends Disposable { private _textModel: ITextModel | null = null; diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts index 1d75155058a..6d43c508cfa 100644 --- a/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts @@ -6,13 +6,12 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/renderers/cellViewModel'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/renderers/sizeObserver'; -import { MimeTypeRenderer } from 'vs/workbench/contrib/notebook/browser/renderers/outputRenderer'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { CELL_MARGIN } from 'vs/workbench/contrib/notebook/common/notebook'; -import { CellRenderTemplate, NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookHandler'; +import { CELL_MARGIN } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellRenderTemplate, NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; export class CodeCell extends Disposable { constructor( @@ -102,7 +101,7 @@ export class CodeCell extends Disposable { let hasDynamicHeight = true; for (let i = 0; i < viewCell.outputs.length; i++) { let outputItemDiv = document.createElement('div'); - let result = MimeTypeRenderer.render(viewCell.outputs[i], outputItemDiv, themeService, instantiationService, modelService, modeService, handler); + let result = handler.getOutputRenderer().render(viewCell.outputs[i], outputItemDiv); templateData.outputContainer?.appendChild(outputItemDiv); if (result) { hasDynamicHeight = hasDynamicHeight || result?.hasDynamicHeight; @@ -144,7 +143,7 @@ export class CodeCell extends Disposable { let hasDynamicHeight = true; for (let i = 0; i < viewCell.outputs.length; i++) { let outputItemDiv = document.createElement('div'); - let result = MimeTypeRenderer.render(viewCell.outputs[i], outputItemDiv, themeService, instantiationService, modelService, modeService, handler); + let result = handler.getOutputRenderer().render(viewCell.outputs[i], outputItemDiv); templateData.outputContainer?.appendChild(outputItemDiv); if (result) { hasDynamicHeight = hasDynamicHeight || result?.hasDynamicHeight; diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/contentWidget.ts b/src/vs/workbench/contrib/notebook/browser/renderers/contentWidget.ts index bf02c00d588..8c7ebd2f370 100644 --- a/src/vs/workbench/contrib/notebook/browser/renderers/contentWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/renderers/contentWidget.ts @@ -13,8 +13,8 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { URI } from 'vs/base/common/uri'; import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader'; import * as path from 'vs/base/common/path'; -import { CELL_MARGIN } from 'vs/workbench/contrib/notebook/common/notebook'; -import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookHandler'; +import { CELL_MARGIN } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; export interface IDimentionMessage { type: 'dimension'; diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/renderers/markdownCell.ts index ff400cc64d8..e1681a92eae 100644 --- a/src/vs/workbench/contrib/notebook/browser/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/renderers/markdownCell.ts @@ -9,8 +9,8 @@ import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/renderers/c import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/renderers/sizeObserver'; -import { CELL_MARGIN } from 'vs/workbench/contrib/notebook/common/notebook'; -import { NotebookHandler, CellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookHandler'; +import { CELL_MARGIN } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookHandler, CellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; export class StatefullMarkdownCell extends Disposable { private editor: CodeEditorWidget | null = null; diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/outputRendererWebview.js b/src/vs/workbench/contrib/notebook/browser/renderers/outputRendererWebview.js deleted file mode 100644 index 002407cee3d..00000000000 --- a/src/vs/workbench/contrib/notebook/browser/renderers/outputRendererWebview.js +++ /dev/null @@ -1,83 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/* eslint-disable no-case-declarations */ -(function () { - // eslint-disable-next-line no-undef - const vscode = acquireVsCodeApi(); - console.log('a'); - - window.addEventListener('message', event => { - let id = event.data.id; - - switch (event.data.type) { - case 'html': - { - let content = event.data.content; - let newElement = document.createElement('div'); - newElement.style.position = 'absolute'; - newElement.style.top = event.data.top + 'px'; - newElement.id = id; - document.getElementById('container').appendChild(newElement); - newElement.innerHTML = content; - let arr = newElement.getElementsByTagName('script'); - for (let n = 0; n < arr.length; n++) { - // eslint-disable-next-line no-eval - eval(arr[n].innerHTML); //run script inside div - } - vscode.postMessage({ - type: 'dimension', - id: id, - data: { - height: newElement.clientHeight - } - }); - } - break; - case 'scroll': - { - let element = document.getElementById(id); - let top = event.data.top; - if (event.data.widgetTop !== undefined) { - let widgetTop = event.data.widgetTop; - element.style.top = widgetTop + 'px'; - document.getElementById('container').style.top = top + 'px'; - } else { - document.getElementById('container').style.top = top + 'px'; - } - - vscode.postMessage({ - type: 'scroll-ack', - id: id, - data: { - top: top, - }, - version: event.data.version - }); - } - break; - case 'view-scroll': - { - document.getElementById('container').style.top = top + 'px'; - for (let i = 0; i < event.data.widgets.length; i++) { - let widget = document.getElementById(event.data.widgets[i].id); - widget.style.top = event.data.widgets[i].top + 'px'; - } - - vscode.postMessage({ - type: 'scroll-ack', - data: { - top: top, - }, - version: event.data.version - }); - break; - } - case 'clear': - document.getElementById('container').innerHTML = ''; - break; - } - }); -}()); diff --git a/src/vs/workbench/contrib/notebook/common/notebook.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts similarity index 88% rename from src/vs/workbench/contrib/notebook/common/notebook.ts rename to src/vs/workbench/contrib/notebook/common/notebookCommon.ts index aa5103a3586..fc3d31712bc 100644 --- a/src/vs/workbench/contrib/notebook/common/notebook.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -113,6 +113,20 @@ export interface INotebook { save(): Promise; } +export interface IRenderOutput { + shadowContent?: string; + hasDynamicHeight: boolean; +} + +export interface IOutputTransformContribution { + /** + * Dispose this contribution. + */ + dispose(): void; + + render(output: IOutput, container: HTMLElement): IRenderOutput; +} + export const CELL_MARGIN = 24;