diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 850a02e7b18d02831a43ba6a3c647c1de2083c93..66ea87eb01c0c69da2f223c9a2743ed5f6c2e7bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -476,8 +476,8 @@ const embeddedEditorBackground = 'walkThrough.embeddedEditorBackground'; registerThemingParticipant((theme, collector) => { const color = getExtraColor(theme, embeddedEditorBackground, { dark: 'rgba(0, 0, 0, .4)', extra_dark: 'rgba(200, 235, 255, .064)', light: '#f4f4f4', hc: null }); if (color) { - collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-editor-background, - .monaco-workbench .part.editor > .content .notebook-editor .margin-view-overlays { background: ${color}; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .monaco-editor-background, + .monaco-workbench .part.editor > .content .notebook-editor .cell .margin-view-overlays { background: ${color}; }`); } const link = theme.getColor(textLinkForeground); if (link) { diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts index 87a89125db2b7f0fc38ccb7a55d4f5694481bb54..cb6d33fe20e1eabfee415f79279dff0be73b0d87 100644 --- a/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts @@ -17,11 +17,12 @@ import { getZoomLevel } from 'vs/base/browser/browser'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Action } from 'vs/base/common/actions'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { NotebookHandler, CellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/renderers/interfaces'; import { StatefullMarkdownCell } from 'vs/workbench/contrib/notebook/browser/renderers/markdownCell'; 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'; export class NotebookCellListDelegate implements IListVirtualDelegate { private _lineHeight: number; @@ -268,7 +269,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende @IConfigurationService configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService private readonly themeService: IThemeService, - @IWebviewService private readonly webviewService: IWebviewService + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService ) { super(handler, contextMenuService, configurationService, 'python'); } @@ -329,7 +331,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende this.showContextMenu(listIndex, element, e.posx, top + height); })); - elementDisposable?.add(new CodeCell(this.handler, element, templateData, this.themeService, this.webviewService, height)); + elementDisposable?.add(new CodeCell(this.handler, element, templateData, this.themeService, this.instantiationService, this.modelService, this.modeService, height)); } disposeTemplate(templateData: CellRenderTemplate): void { diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts index 3b566baecd81270aac2e4fd76ff91ab0daa912bb..ad7d61e003814979182d1a5904b55183e132d371 100644 --- a/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts @@ -9,7 +9,9 @@ import { CellRenderTemplate, NotebookHandler, CELL_MARGIN } from 'vs/workbench/c 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 { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; export class CodeCell extends Disposable { constructor( @@ -17,7 +19,9 @@ export class CodeCell extends Disposable { viewCell: CellViewModel, templateData: CellRenderTemplate, themeService: IThemeService, - webviewService: IWebviewService, + instantiationService: IInstantiationService, + modelService: IModelService, + modeService: IModeService, height: number | undefined ) { super(); @@ -96,10 +100,11 @@ export class CodeCell extends Disposable { if (viewCell.outputs.length > 0) { let hasDynamicHeight = true; for (let i = 0; i < viewCell.outputs.length; i++) { - let result = MimeTypeRenderer.render(viewCell.outputs[i], themeService, webviewService); + let outputItemDiv = document.createElement('div'); + let result = MimeTypeRenderer.render(viewCell.outputs[i], outputItemDiv, themeService, instantiationService, modelService, modeService, handler); + templateData.outputContainer?.appendChild(outputItemDiv); if (result) { hasDynamicHeight = hasDynamicHeight || result?.hasDynamicHeight; - templateData.outputContainer?.appendChild(result.element); if (result.shadowContent) { hasDynamicHeight = false; handler.createContentWidget(viewCell, i, result.shadowContent, totalHeight + 8); @@ -125,7 +130,6 @@ export class CodeCell extends Disposable { elementSizeObserver.dispose(); } }); - // const elementSizeObserver = new ElementSizeObserver(); elementSizeObserver.startObserving(); if (!hasDynamicHeight && clientHeight !== 0) { viewCell.dynamicHeight = totalHeight + 32 + clientHeight; @@ -140,10 +144,11 @@ export class CodeCell extends Disposable { if (viewCell.outputs.length > 0) { let hasDynamicHeight = true; for (let i = 0; i < viewCell.outputs.length; i++) { - let result = MimeTypeRenderer.render(viewCell.outputs[i], themeService, webviewService); + let outputItemDiv = document.createElement('div'); + let result = MimeTypeRenderer.render(viewCell.outputs[i], outputItemDiv, themeService, instantiationService, modelService, modeService, handler); + templateData.outputContainer?.appendChild(outputItemDiv); if (result) { hasDynamicHeight = hasDynamicHeight || result?.hasDynamicHeight; - templateData.outputContainer?.appendChild(result.element); if (result.shadowContent) { hasDynamicHeight = false; handler.createContentWidget(viewCell, i, result.shadowContent, totalHeight + 8); diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/renderers/outputRenderer.ts index 3d7a851f5029159661612b2942e36f1c8f9b06bf..2a156f44cca6ec4275a06be21417bf3ce947160b 100644 --- a/src/vs/workbench/contrib/notebook/browser/renderers/outputRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/renderers/outputRenderer.ts @@ -7,11 +7,16 @@ 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 { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { isArray } from 'vs/base/common/types'; import { IOutput } from 'vs/editor/common/modes'; import * as marked from 'vs/base/common/marked/marked'; import { NotebookHandler } from 'vs/workbench/contrib/notebook/browser/renderers/interfaces'; +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'; export function registerMineTypeRenderer(types: string[], renderer: IMimeRenderer) { types.forEach(type => { @@ -20,14 +25,12 @@ export function registerMineTypeRenderer(types: string[], renderer: IMimeRendere } export interface IRenderOutput { - element: HTMLElement; - whitespaceElement?: HTMLElement; shadowContent?: string; hasDynamicHeight: boolean; } interface IMimeRenderer { - render(output: IOutput, themeService: IThemeService, webviewService: IWebviewService, notebookHandler?: NotebookHandler): IRenderOutput; + render(output: IOutput, container: HTMLElement, themeService: IThemeService, instantiationService: IInstantiationService, modelService: IModelService, modeService: IModeService, notebookHandler?: NotebookHandler): IRenderOutput; } export class MimeTypeRenderer { @@ -42,23 +45,24 @@ export class MimeTypeRenderer { return this._renderers.get(type); } - static render(output: IOutput, themeService: IThemeService, webviewService: IWebviewService, notebookHandler?: NotebookHandler): IRenderOutput | null { - return MimeTypeRenderer.instance.getRenderer(output.output_type)?.render(output, themeService, webviewService, notebookHandler) ?? null; + static render(output: IOutput, container: HTMLElement, themeService: IThemeService, instantiationService: IInstantiationService, modelService: IModelService, modeService: IModeService, notebookHandler: NotebookHandler): IRenderOutput | null { + return MimeTypeRenderer.instance.getRenderer(output.output_type)?.render(output, container, themeService, instantiationService, modelService, modeService, notebookHandler) ?? null; } } registerMineTypeRenderer(['stream'], { render: ( output: IOutput, + container: HTMLElement, themeService: IThemeService, - webviewService: IWebviewService + instantiationService: IInstantiationService, + modelService: IModelService, + modeService: IModeService ) => { - const outputNode = document.createElement('div'); const contentNode = document.createElement('p'); contentNode.innerText = output.text; - outputNode.appendChild(contentNode); + container.appendChild(contentNode); return { - element: outputNode, hasDynamicHeight: false }; } @@ -67,10 +71,12 @@ registerMineTypeRenderer(['stream'], { registerMineTypeRenderer(['error'], { render: ( output: IOutput, + container: HTMLElement, themeService: IThemeService, - webviewService: IWebviewService + instantiationService: IInstantiationService, + modelService: IModelService, + modeService: IModeService ) => { - const outputNode = document.createElement('div'); const traceback = document.createElement('pre'); DOM.addClasses(traceback, 'traceback'); if (output.traceback) { @@ -78,9 +84,8 @@ registerMineTypeRenderer(['error'], { traceback.appendChild(handleANSIOutput(output.traceback[j], themeService)); } } - outputNode.appendChild(traceback); + container.appendChild(traceback); return { - element: outputNode, hasDynamicHeight: false }; } @@ -104,20 +109,49 @@ registerMineTypeRenderer(['error'], { class RichDisplayRenderer implements IMimeRenderer { private _mdRenderer: marked.Renderer = new marked.Renderer({ gfm: true });; - render(output: any, themeService: IThemeService, webviewService: IWebviewService, notebookHandler: NotebookHandler): IRenderOutput { - const display = document.createElement('div'); - const outputNode = document.createElement('div'); + render(output: any, container: HTMLElement, themeService: IThemeService, instantiationService: IInstantiationService, modelService: IModelService, modeService: IModeService, notebookHandler: NotebookHandler): IRenderOutput { let hasDynamicHeight = false; - DOM.addClasses(display, 'display'); if (output.data) { - if (output.data['application/javascript']) { + 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 { - element: outputNode, shadowContent: scriptVal, hasDynamicHeight }; @@ -126,7 +160,6 @@ class RichDisplayRenderer implements IMimeRenderer { let str = isArray(data) ? data.join('') : data; hasDynamicHeight = false; return { - element: outputNode, shadowContent: str, hasDynamicHeight }; @@ -135,7 +168,6 @@ class RichDisplayRenderer implements IMimeRenderer { let str = isArray(data) ? data.join('') : data; hasDynamicHeight = false; return { - element: outputNode, shadowContent: str, hasDynamicHeight }; @@ -144,31 +176,34 @@ class RichDisplayRenderer implements IMimeRenderer { const str = isArray(data) ? data.join('') : data; const mdOutput = document.createElement('div'); mdOutput.innerHTML = marked(str, { renderer: this._mdRenderer }); - outputNode.appendChild(mdOutput); + 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); - outputNode.appendChild(display); + 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); - outputNode.appendChild(display); + 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; - outputNode.appendChild(contentNode); + container.appendChild(contentNode); } } return { - element: outputNode, hasDynamicHeight }; } @@ -523,3 +558,25 @@ 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 + } + }; +}