Merge pull request #142584 from microsoft/rebornix/stream-output-renderer

Move streaming output rendering to renderer extension
This commit is contained in:
Peng Lyu 2022-02-10 15:52:00 -08:00 committed by GitHub
commit 8b4eaf7e00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 288 additions and 1283 deletions

View file

@ -25,10 +25,17 @@
"image/gif",
"image/png",
"image/jpeg",
"image/git",
"image/svg+xml",
"text/html",
"application/javascript",
"application/vnd.code.notebook.error"
"application/vnd.code.notebook.error",
"application/vnd.code.notebook.stdout",
"application/x.notebook.stdout",
"application/x.notebook.stream",
"application/vnd.code.notebook.stderr",
"application/x.notebook.stderr",
"text/plain"
]
}
]

View file

@ -3,13 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { ActivationFunction, OutputItem } from 'vscode-notebook-renderer';
import type { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer';
import { handleANSIOutput } from './ansi';
import { truncatedArrayOfString } from './textHelper';
interface IDisposable {
dispose(): void;
}
function renderImage(outputInfo: OutputItem, element: HTMLElement): IDisposable {
const blob = new Blob([outputInfo.data()], { type: outputInfo.mime });
const src = URL.createObjectURL(blob);
@ -76,21 +79,19 @@ function renderJavascript(outputInfo: OutputItem, container: HTMLElement): void
domEval(element);
}
function renderError(outputIfo: OutputItem, container: HTMLElement): void {
function renderError(outputInfo: OutputItem, container: HTMLElement): void {
const element = document.createElement('div');
container.appendChild(element);
type ErrorLike = Partial<Error>;
let err: ErrorLike;
try {
err = <ErrorLike>JSON.parse(outputIfo.text());
err = <ErrorLike>JSON.parse(outputInfo.text());
} catch (e) {
console.log(e);
return;
}
console.log(err);
if (err.stack) {
const stack = document.createElement('pre');
stack.classList.add('traceback');
@ -109,9 +110,70 @@ function renderError(outputIfo: OutputItem, container: HTMLElement): void {
container.classList.add('error');
}
function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boolean, ctx: RendererContext<void> & { readonly settings: { readonly lineLimit: number } }): void {
const outputContainer = container.parentElement;
if (!outputContainer) {
// should never happen
return;
}
const prev = outputContainer.previousSibling;
if (prev) {
// OutputItem in the same cell
// check if the previous item is a stream
const outputElement = (prev.firstChild as HTMLElement | null);
if (outputElement && outputElement.getAttribute('output-mime-type') === outputInfo.mime) {
// same stream
const text = outputInfo.text();
const element = document.createElement('span');
truncatedArrayOfString(outputInfo.id, [text], 30, element);
outputElement.appendChild(element);
return;
}
}
const element = document.createElement('span');
element.classList.add('output-stream');
const text = outputInfo.text();
truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, element);
container.appendChild(element);
container.setAttribute('output-mime-type', outputInfo.mime);
if (error) {
container.classList.add('error');
}
}
function renderText(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext<void> & { readonly settings: { readonly lineLimit: number } }): void {
const contentNode = document.createElement('div');
contentNode.classList.add('.output-plaintext');
const text = outputInfo.text();
truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, contentNode);
container.appendChild(contentNode);
}
export const activate: ActivationFunction<void> = (ctx) => {
const disposables = new Map<string, IDisposable>();
const latestContext = ctx as (RendererContext<void> & { readonly settings: { readonly lineLimit: number } });
const style = document.createElement('style');
style.textContent = `
.output-stream {
line-height: 22px;
font-family: var(--notebook-cell-output-font-family);
white-space: pre-wrap;
word-wrap: break-word;
font-size: var(--notebook-cell-output-font-size);
user-select: text;
-webkit-user-select: text;
-ms-user-select: text;
cursor: auto;
}
`;
document.body.appendChild(style);
return {
renderOutputItem: (outputInfo, element) => {
switch (outputInfo.mime) {
@ -137,6 +199,7 @@ export const activate: ActivationFunction<void> = (ctx) => {
case 'image/gif':
case 'image/png':
case 'image/jpeg':
case 'image/git':
{
const disposable = renderImage(outputInfo, element);
disposables.set(outputInfo.id, disposable);
@ -146,6 +209,25 @@ export const activate: ActivationFunction<void> = (ctx) => {
{
renderError(outputInfo, element);
}
break;
case 'application/vnd.code.notebook.stdout':
case 'application/x.notebook.stdout':
case 'application/x.notebook.stream':
{
renderStream(outputInfo, element, false, latestContext);
}
break;
case 'application/vnd.code.notebook.stderr':
case 'application/x.notebook.stderr':
{
renderStream(outputInfo, element, true, latestContext);
}
break;
case 'text/plain':
{
renderText(outputInfo, element, latestContext);
}
break;
default:
break;
}

View file

@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { handleANSIOutput } from './ansi';
function generateViewMoreElement(outputId: string) {
const container = document.createElement('span');
const first = document.createElement('span');
first.textContent = 'Output exceeds the ';
const second = document.createElement('a');
second.textContent = 'size limit';
second.href = `command:workbench.action.openSettings?["notebook.output.textLineLimit"]`;
const third = document.createElement('span');
third.textContent = '. Open the full output data';
const forth = document.createElement('a');
forth.textContent = ' in a text editor';
forth.href = `command:workbench.action.openLargeOutput?${outputId}`;
container.appendChild(first);
container.appendChild(second);
container.appendChild(third);
container.appendChild(forth);
return container;
}
export function truncatedArrayOfString(id: string, outputs: string[], linesLimit: number, container: HTMLElement) {
let buffer = outputs.join('\n').split(/\r|\n|\r\n/g);
let lineCount = buffer.length;
if (lineCount < linesLimit) {
const spanElement = handleANSIOutput(buffer.slice(0, linesLimit).join('\n'));
container.appendChild(spanElement);
return;
}
container.appendChild(generateViewMoreElement(id));
const div = document.createElement('div');
container.appendChild(div);
div.appendChild(handleANSIOutput(buffer.slice(0, linesLimit - 5).join('\n')));
// view more ...
const viewMoreSpan = document.createElement('span');
viewMoreSpan.innerText = '...';
container.appendChild(viewMoreSpan);
const div2 = document.createElement('div');
container.appendChild(div2);
div2.appendChild(handleANSIOutput(buffer.slice(lineCount - 5).join('\n')));
}

View file

@ -1,150 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { IModelService } from 'vs/editor/common/services/model';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { ICellOutputViewModel, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookDelegateForOutput } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon';
import { OutputRendererRegistry } from 'vs/workbench/contrib/notebook/browser/view/output/rendererRegistry';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
abstract class CodeRendererContrib extends Disposable implements IOutputTransformContribution {
getType() {
return RenderOutputType.Mainframe;
}
abstract getMimetypes(): string[];
constructor(
public notebookEditor: INotebookDelegateForOutput,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IModelService private readonly modelService: IModelService,
@ILanguageService private readonly languageService: ILanguageService,
) {
super();
}
abstract render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement): IRenderOutput;
protected _render(output: ICellOutputViewModel, container: HTMLElement, value: string, languageId: string): IRenderOutput {
const disposable = new DisposableStore();
const editor = this.instantiationService.createInstance(CodeEditorWidget, container, getOutputSimpleEditorOptions(), { isSimpleWidget: true, contributions: this.notebookEditor.creationOptions.cellEditorContributions });
if (output.cellViewModel instanceof CodeCellViewModel) {
disposable.add(output.cellViewModel.viewContext.eventDispatcher.onDidChangeLayout(() => {
const outputWidth = this.notebookEditor.getCellOutputLayoutInfo(output.cellViewModel).width;
const fontInfo = this.notebookEditor.getCellOutputLayoutInfo(output.cellViewModel).fontInfo;
const editorHeight = Math.min(16 * (fontInfo.lineHeight || 18), editor.getLayoutInfo().height);
editor.layout({ height: editorHeight, width: outputWidth });
container.style.height = `${editorHeight + 8}px`;
}));
}
disposable.add(editor.onDidContentSizeChange(e => {
const outputWidth = this.notebookEditor.getCellOutputLayoutInfo(output.cellViewModel).width;
const fontInfo = this.notebookEditor.getCellOutputLayoutInfo(output.cellViewModel).fontInfo;
const editorHeight = Math.min(16 * (fontInfo.lineHeight || 18), e.contentHeight);
editor.layout({ height: editorHeight, width: outputWidth });
container.style.height = `${editorHeight + 8}px`;
}));
const mode = this.languageService.createById(languageId);
const textModel = this.modelService.createModel(value, mode, undefined, false);
editor.setModel(textModel);
const width = this.notebookEditor.getCellOutputLayoutInfo(output.cellViewModel).width;
const fontInfo = this.notebookEditor.getCellOutputLayoutInfo(output.cellViewModel).fontInfo;
const height = Math.min(textModel.getLineCount(), 16) * (fontInfo.lineHeight || 18);
editor.layout({ height, width });
disposable.add(editor);
disposable.add(textModel);
container.style.height = `${height + 8}px`;
return { type: RenderOutputType.Mainframe, initHeight: height, disposable };
}
}
export class NotebookCodeRendererContribution extends Disposable {
constructor(@ILanguageService _languageService: ILanguageService) {
super();
const registeredMimeTypes = new Map();
const registerCodeRendererContrib = (mimeType: string, languageId: string) => {
if (registeredMimeTypes.has(mimeType)) {
return;
}
OutputRendererRegistry.registerOutputTransform(class extends CodeRendererContrib {
getMimetypes() {
return [mimeType];
}
render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement): IRenderOutput {
const str = item.data.toString();
return this._render(output, container, str, languageId);
}
});
registeredMimeTypes.set(mimeType, true);
};
_languageService.getRegisteredLanguageIds().forEach(id => {
registerCodeRendererContrib(`text/x-${id}`, id);
});
this._register(_languageService.onDidChange(() => {
_languageService.getRegisteredLanguageIds().forEach(id => {
registerCodeRendererContrib(`text/x-${id}`, id);
});
}));
registerCodeRendererContrib('application/json', 'json');
}
}
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(NotebookCodeRendererContribution, LifecyclePhase.Restored);
// --- utils ---
function getOutputSimpleEditorOptions(): IEditorConstructionOptions {
return {
dimension: { height: 0, width: 0 },
readOnly: true,
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
},
automaticLayout: true,
};
}

View file

@ -3,14 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { RunOnceScheduler } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { CellEditState, IInsetRenderOutput, INotebookEditor, INotebookEditorContribution, INotebookEditorDelegate, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { BUILTIN_RENDERER_ID, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
@ -105,14 +104,6 @@ class NotebookViewportContribution extends Disposable implements INotebookEditor
return;
}
if (pickedMimeTypeRenderer.rendererId === BUILTIN_RENDERER_ID) {
const renderer = this._notebookEditor.getOutputRenderer().getContribution(pickedMimeTypeRenderer.mimeType);
if (renderer?.getType() === RenderOutputType.Html) {
const renderResult = renderer.render(output, output.model.outputs.filter(op => op.mime === pickedMimeTypeRenderer.mimeType)[0], DOM.$(''), this._notebookEditor.textModel.uri) as IInsetRenderOutput;
this._notebookEditor.createOutput(viewCell, renderResult, 0);
}
return;
}
const renderer = this._notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId);
if (!renderer) {

View file

@ -9,10 +9,9 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { DiffElementViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel';
import { DiffSide, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser';
import { ICellOutputViewModel, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets';
import { ICellOutputViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { BUILTIN_RENDERER_ID, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
@ -28,7 +27,7 @@ interface IMimeTypeRenderer extends IQuickPickItem {
export class OutputElement extends Disposable {
readonly resizeListener = this._register(new DisposableStore());
domNode!: HTMLElement;
renderResult?: IRenderOutput;
renderResult?: IInsetRenderOutput;
constructor(
private _notebookEditor: INotebookTextDiffEditor,
@ -46,7 +45,7 @@ export class OutputElement extends Disposable {
render(index: number, beforeElement?: HTMLElement) {
const outputItemDiv = document.createElement('div');
let result: IRenderOutput | undefined = undefined;
let result: IInsetRenderOutput | undefined = undefined;
const [mimeTypes, pick] = this.output.resolveMimeTypes(this._notebookTextModel, undefined);
const pickedMimeTypeRenderer = mimeTypes[pick];
@ -80,14 +79,10 @@ export class OutputElement extends Disposable {
if (mimeTypes.length !== 0) {
if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) {
const renderer = this._notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId);
result = renderer
? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType }
: this._notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this._notebookTextModel.uri,);
} else {
result = this._notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this._notebookTextModel.uri);
}
const renderer = this._notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId);
result = renderer
? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType }
: this._renderMissingRenderer(this.output, pickedMimeTypeRenderer.mimeType);
this.output.pickedMimeType = pickedMimeTypeRenderer;
}
@ -106,52 +101,43 @@ export class OutputElement extends Disposable {
this._outputContainer.appendChild(outputItemDiv);
}
if (result.type !== RenderOutputType.Mainframe) {
// this.viewCell.selfSizeMonitoring = true;
this._notebookEditor.createOutput(
this._diffElementViewModel,
this._nestedCell,
result,
() => this.getOutputOffsetInCell(index),
this._diffElementViewModel instanceof SideBySideDiffElementViewModel
? this._diffSide
: this._diffElementViewModel.type === 'insert' ? DiffSide.Modified : DiffSide.Original
);
} else {
outputItemDiv.classList.add('foreground', 'output-element');
outputItemDiv.style.position = 'absolute';
}
if (result.type === RenderOutputType.Html || result.type === RenderOutputType.Extension) {
return;
this._notebookEditor.createOutput(
this._diffElementViewModel,
this._nestedCell,
result,
() => this.getOutputOffsetInCell(index),
this._diffElementViewModel instanceof SideBySideDiffElementViewModel
? this._diffSide
: this._diffElementViewModel.type === 'insert' ? DiffSide.Modified : DiffSide.Original
);
}
private _renderMissingRenderer(viewModel: ICellOutputViewModel, preferredMimeType: string | undefined): IInsetRenderOutput {
if (!viewModel.model.outputs.length) {
return this._renderMessage(viewModel, nls.localize('empty', "Cell has no output"));
}
if (!preferredMimeType) {
const mimeTypes = viewModel.model.outputs.map(op => op.mime);
const mimeTypesMessage = mimeTypes.join(', ');
return this._renderMessage(viewModel, nls.localize('noRenderer.2', "No renderer could be found for output. It has the following mimetypes: {0}", mimeTypesMessage));
}
return this._renderSearchForMimetype(viewModel, preferredMimeType);
}
let clientHeight = Math.ceil(outputItemDiv.clientHeight);
const elementSizeObserver = getResizesObserver(outputItemDiv, undefined, () => {
if (this._outputContainer && document.body.contains(this._outputContainer)) {
const height = Math.ceil(elementSizeObserver.getHeight());
private _renderSearchForMimetype(viewModel: ICellOutputViewModel, mimeType: string): IInsetRenderOutput {
const query = `@tag:notebookRenderer ${mimeType}`;
return {
type: RenderOutputType.Html,
source: viewModel,
htmlContent: `<p>No renderer could be found for mimetype "${mimeType}", but one might be available on the Marketplace.</p>
<a href="command:workbench.extensions.search?%22${query}%22" class="monaco-button monaco-text-button" tabindex="0" role="button" style="padding: 8px; text-decoration: none; color: rgb(255, 255, 255); background-color: rgb(14, 99, 156); max-width: 200px;">Search Marketplace</a>`
};
}
if (clientHeight === height) {
return;
}
clientHeight = height;
const currIndex = this.getCellOutputCurrentIndex();
if (currIndex < 0) {
return;
}
this.updateHeight(currIndex, height);
}
});
elementSizeObserver.startObserving();
this.resizeListener.add(elementSizeObserver);
this.updateHeight(index, clientHeight);
const top = this.getOutputOffsetInContainer(index);
outputItemDiv.style.top = `${top}px`;
private _renderMessage(viewModel: ICellOutputViewModel, message: string): IInsetRenderOutput {
return { type: RenderOutputType.Html, source: viewModel, htmlContent: `<p>${message}</p>` };
}
private async pickActiveMimeTypeRenderer(notebookTextModel: NotebookTextModel, viewModel: ICellOutputViewModel) {
@ -206,11 +192,7 @@ export class OutputElement extends Disposable {
}
}
private generateRendererInfo(renderId: string | undefined): string {
if (renderId === undefined || renderId === BUILTIN_RENDERER_ID) {
return nls.localize('builtinRenderInfo', "built-in");
}
private generateRendererInfo(renderId: string): string {
const renderInfo = this._notebookService.getRendererInfo(renderId);
if (renderInfo) {

View file

@ -12,7 +12,6 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
@ -35,7 +34,6 @@ export interface INotebookTextDiffEditor {
getOverflowContainerDomNode(): HTMLElement;
getLayoutInfo(): NotebookLayoutInfo;
layoutNotebookCell(cell: DiffElementViewModelBase, height: number): void;
getOutputRenderer(): OutputRenderer;
createOutput(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void;
showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide): void;
removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: ICellOutputViewModel, diffSide: DiffSide): void;

View file

@ -32,7 +32,6 @@ import { CellUri, INotebookDiffEditorModel, INotebookDiffResult, NOTEBOOK_DIFF_E
import { URI } from 'vs/base/common/uri';
import { IDiffChange, IDiffResult } from 'vs/base/common/diff/diff';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
import { SequencerByKey } from 'vs/base/common/async';
import { generateUuid } from 'vs/base/common/uuid';
import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
@ -66,7 +65,6 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
protected _scopeContextKeyService!: IContextKeyService;
private _model: INotebookDiffEditorModel | null = null;
private readonly _modifiedResourceDisposableStore = this._register(new DisposableStore());
private _outputRenderer: OutputRenderer;
get textModel() {
return this._model?.modified.notebook;
@ -108,7 +106,6 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
const editorOptions = this.configurationService.getValue<ICodeEditorOptions>('editor');
this._fontInfo = FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.value));
this._revealFirst = true;
this._outputRenderer = this.instantiationService.createInstance(OutputRenderer, this);
}
toggleNotebookCellSelection(cell: IGenericCellViewModel) {
@ -793,10 +790,6 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
this._diffElementViewModels = [];
}
getOutputRenderer(): OutputRenderer {
return this._outputRenderer;
}
deltaCellOutputContainerClassNames(diffSide: DiffSide, cellId: string, added: string[], removed: string[]) {
if (diffSide === DiffSide.Original) {
this._originalWebview?.deltaCellOutputContainerClassNames(cellId, added, removed);

View file

@ -86,7 +86,6 @@ import 'vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo'
import 'vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands';
import 'vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown';
import 'vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout';
import 'vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer';
import 'vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints';
import 'vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress';
import 'vs/workbench/contrib/notebook/browser/contrib/execute/execution';
@ -95,7 +94,6 @@ import 'vs/workbench/contrib/notebook/browser/contrib/execute/execution';
import 'vs/workbench/contrib/notebook/browser/diff/notebookDiffActions';
// Output renderers registration
import 'vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform';
import { editorOptionsRegistry } from 'vs/editor/common/config/editorOptions';
import { NotebookExecutionStateService } from 'vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl';
import { NotebookExecutionService } from 'vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl';
@ -396,7 +394,6 @@ class CellInfoContentProvider {
private _getResult(data: {
notebook: URI;
handle: number;
outputId?: string | undefined;
}, cell: NotebookCellTextModel) {
let result: { content: string; mode: ILanguageSelection } | undefined = undefined;
@ -438,7 +435,7 @@ class CellInfoContentProvider {
}
const ref = await this._notebookModelResolverService.resolve(data.notebook);
const cell = ref.object.notebook.cells.find(cell => cell.handle === data.handle);
const cell = ref.object.notebook.cells.find(cell => !!cell.outputs.find(op => op.outputId === data.outputId));
if (!cell) {
return null;

View file

@ -20,7 +20,7 @@ import { IEditorPane } from 'vs/workbench/common/editor';
import { CellViewModelStateChangeEvent, NotebookCellStateChangedEvent, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, ICellOutput, INotebookCellStatusBarItem, INotebookRendererInfo, INotebookSearchOptions, IOrderedMimeType, IOutputItemDto, NotebookCellInternalMetadata, NotebookCellMetadata, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, ICellOutput, INotebookCellStatusBarItem, INotebookRendererInfo, INotebookSearchOptions, IOrderedMimeType, NotebookCellInternalMetadata, NotebookCellMetadata, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { isCompositeNotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
@ -52,18 +52,10 @@ export const KERNEL_EXTENSIONS = new Map<string, string>([
//#region Output related types
export const enum RenderOutputType {
Mainframe,
Html,
Extension
}
export interface IRenderMainframeOutput {
type: RenderOutputType.Mainframe;
supportAppend?: boolean;
initHeight?: number;
disposable?: IDisposable;
}
export interface IRenderPlainHtmlOutput {
type: RenderOutputType.Html;
source: IDisplayOutputViewModel;
@ -78,28 +70,6 @@ export interface IRenderOutputViaExtension {
}
export type IInsetRenderOutput = IRenderPlainHtmlOutput | IRenderOutputViaExtension;
export type IRenderOutput = IRenderMainframeOutput | IInsetRenderOutput;
export interface IOutputTransformContribution {
getType(): RenderOutputType;
getMimetypes(): string[];
/**
* Dispose this contribution.
*/
dispose(): void;
/**
* Returns contents to place in the webview inset, or the {@link IRenderNoOutput}.
* This call is allowed to have side effects, such as placing output
* directly into the container element.
*/
render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput;
}
export interface IOutputRenderer {
render(viewModel: ICellOutputViewModel, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI): IRenderOutput;
getContribution(preferredMimeType: string): IOutputTransformContribution | undefined;
}
export interface ICellOutputViewModel extends IDisposable {
cellViewModel: IGenericCellViewModel;
@ -109,7 +79,6 @@ export interface ICellOutputViewModel extends IDisposable {
model: ICellOutput;
resolveMimeTypes(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined): [readonly IOrderedMimeType[], number];
pickedMimeType: IOrderedMimeType | undefined;
supportAppend(): boolean;
hasMultiMimeType(): boolean;
toRawJSON(): any;
}
@ -149,12 +118,6 @@ export interface ICommonCellInfo {
cellUri: URI;
}
export interface INotebookCellOutputLayoutInfo {
width: number;
height: number;
fontInfo: FontInfo;
}
export interface IFocusNotebookCellOptions {
readonly skipReveal?: boolean;
}
@ -467,11 +430,6 @@ export interface INotebookEditor {
getVisibleRangesPlusViewportBelow(): ICellRange[];
/**
* Fetch the output renderers for notebook outputs.
*/
getOutputRenderer(): IOutputRenderer;
/**
* Focus the container of a cell (the monaco editor inside is not focused).
*/

View file

@ -43,7 +43,7 @@ import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeg
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { PANEL_BORDER, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors';
import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, IActiveNotebookEditorDelegate, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookWebviewMessage, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, IActiveNotebookEditorDelegate, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookWebviewMessage, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { notebookDebug } from 'vs/workbench/contrib/notebook/browser/notebookLogger';
@ -52,7 +52,6 @@ import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/vie
import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd';
import { NotebookCellList, NOTEBOOK_WEBVIEW_BOUNDARY } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon';
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView';
import { CodeCellRenderer, MarkupCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer';
import { IAckOutputHeight, IMarkupCellInitialization } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages';
@ -66,7 +65,7 @@ import { NotebookEditorToolbar } from 'vs/workbench/contrib/notebook/browser/vie
import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys';
import { ListTopCellToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { BUILTIN_RENDERER_ID, CellKind, INotebookSearchOptions, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, INotebookSearchOptions, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
@ -332,8 +331,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
private readonly _editorFocus: IContextKey<boolean>;
private readonly _outputFocus: IContextKey<boolean>;
private readonly _editorEditable: IContextKey<boolean>;
private _outputRenderer: OutputRenderer;
protected readonly _contributions = new Map<string, INotebookEditorContribution>();
private _scrollBeyondLastLine: boolean;
private readonly _insetModifyQueueByOutputId = new SequencerByKey<string>();
@ -444,11 +441,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
}
}));
const that = this;
this._outputRenderer = this._register(this.instantiationService.createInstance(OutputRenderer, {
get creationOptions() { return that.creationOptions; },
getCellOutputLayoutInfo: that._getCellOutputLayoutInfo.bind(that)
}));
this._scrollBeyondLastLine = this.configurationService.getValue<boolean>('editor.scrollBeyondLastLine');
this._register(this.configurationService.onDidChangeConfiguration(e => {
@ -2361,25 +2353,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
return;
}
if (pickedMimeTypeRenderer.rendererId === BUILTIN_RENDERER_ID) {
const renderer = this.getOutputRenderer().getContribution(pickedMimeTypeRenderer.mimeType);
if (renderer?.getType() === RenderOutputType.Html) {
const renderResult = renderer.render(output, output.model.outputs.filter(op => op.mime === pickedMimeTypeRenderer.mimeType)[0], DOM.$(''), this.textModel!.uri) as IInsetRenderOutput;
if (!this._webview?.insetMapping.has(renderResult.source)) {
const p = new Promise<void>(resolve => {
this.onDidRenderOutput(e => {
if (e.model === renderResult.source.model) {
resolve();
}
});
});
this.createOutput(viewCell, renderResult, 0);
await p;
return;
}
}
return;
}
const renderer = this._notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId);
if (!renderer) {
@ -2548,30 +2521,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
};
}
private _getCellOutputLayoutInfo(cell: IGenericCellViewModel): INotebookCellOutputLayoutInfo {
if (!this._list) {
throw new Error('Editor is not initalized successfully');
}
if (!this._fontInfo) {
this._generateFontInfo();
}
const {
cellRunGutter,
codeCellLeftMargin,
cellRightMargin
} = this._notebookOptions.getLayoutConfiguration();
const width = (this._dimension?.width ?? 0) - (codeCellLeftMargin + cellRunGutter + cellRightMargin) - 8 /** padding */ * 2;
return {
width: Math.max(width, 0),
height: this._dimension?.height ?? 0,
fontInfo: this._fontInfo!
};
}
async createMarkupPreview(cell: MarkupCellViewModel) {
if (!this._webview) {
return;
@ -2707,10 +2656,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
}
}
getOutputRenderer(): OutputRenderer {
return this._outputRenderer;
}
//#region --- webview IPC ----
postMessage(message: any) {
if (this._webview?.isResolved()) {

View file

@ -22,14 +22,13 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { Memento } from 'vs/workbench/common/memento';
import { INotebookEditorContribution, notebookRendererExtensionPoint, notebooksExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookData, NotebookEditorPriority, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, TransientOptions, NotebookExtensionDescription } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, NotebookData, NotebookEditorPriority, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, TransientOptions, NotebookExtensionDescription } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { updateEditorTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions';
@ -277,7 +276,6 @@ export class NotebookOutputRendererInfoStore {
constructor(
@IStorageService storageService: IStorageService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
) {
this.preferredMimetypeMemento = new Memento('workbench.editor.notebook.preferredRenderer2', storageService);
}
@ -348,17 +346,6 @@ export class NotebookOutputRendererInfoStore {
};
}).filter(isDefined);
if (mimeTypeSupportedByCore(mimeType)) {
renderers.push({
score: ReuseOrder.BuiltIn,
ordered: {
mimeType,
rendererId: BUILTIN_RENDERER_ID,
isTrusted: mimeTypeIsAlwaysSecure(mimeType) || this.workspaceTrustManagementService.isWorkspaceTrusted()
}
});
}
if (renderers.length === 0) {
return [{ mimeType, rendererId: RENDERER_NOT_AVAILABLE, isTrusted: true }];
}

View file

@ -24,15 +24,13 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ViewContainerLocation } from 'vs/workbench/common/views';
import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSION_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
import { ICellOutputViewModel, ICellViewModel, IInsetRenderOutput, INotebookEditorDelegate, IRenderOutput, JUPYTER_EXTENSION_ID, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { ICellOutputViewModel, ICellViewModel, IInsetRenderOutput, INotebookEditorDelegate, JUPYTER_EXTENSION_ID, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { mimetypeIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { CodeCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon';
import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { BUILTIN_RENDERER_ID, CellUri, IOrderedMimeType, NotebookCellOutputsSplice, RENDERER_NOT_AVAILABLE } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellUri, IOrderedMimeType, NotebookCellOutputsSplice, RENDERER_NOT_AVAILABLE } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { OutputInnerContainerTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart';
@ -43,7 +41,7 @@ interface IMimeTypeRenderer extends IQuickPickItem {
}
interface IRenderResult {
initRenderIsSynchronous: boolean;
initRenderIsSynchronous: false;
}
// DOM structure
@ -67,22 +65,7 @@ export class CellOutputElement extends Disposable {
innerContainer?: HTMLElement;
renderedOutputContainer!: HTMLElement;
renderResult?: IRenderOutput;
public useDedicatedDOM: boolean = true;
private _height: number = -1;
get domOffsetHeight() {
if (this.useDedicatedDOM) {
if (this._height === -1) {
return this.innerContainer?.offsetHeight ?? 0;
} else {
return this._height;
}
} else {
return 0;
}
}
renderResult?: IInsetRenderOutput;
private readonly contextKeyService: IContextKeyService;
@ -98,7 +81,7 @@ export class CellOutputElement extends Disposable {
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IContextKeyService parentContextKeyService: IContextKeyService,
@IMenuService private readonly menuService: IMenuService,
@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,
@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService
) {
super();
@ -132,23 +115,11 @@ export class CellOutputElement extends Disposable {
}
this.notebookEditor.removeInset(this.output);
if (this.renderResult && this.renderResult.type === RenderOutputType.Mainframe) {
this.renderResult.disposable?.dispose();
}
}
forceReadDOM() {
if (this.useDedicatedDOM && this.innerContainer) {
this._height = this.innerContainer.offsetHeight;
}
}
updateDOMTop(top: number) {
if (this.useDedicatedDOM) {
if (this.innerContainer) {
this.innerContainer.style.top = `${top}px`;
}
if (this.innerContainer) {
this.innerContainer.style.top = `${top}px`;
}
}
@ -178,66 +149,25 @@ export class CellOutputElement extends Disposable {
this.notebookEditor.removeInset(this.output);
}
// this.output.pickedMimeType = pick;
this.render(nextElement as HTMLElement);
this._relayoutCell();
}
// insert after previousSibling
private _generateInnerOutputContainer(previousSibling: HTMLElement | undefined, pickedMimeTypeRenderer: IOrderedMimeType, forceBreakStreaming: boolean) {
if (this.output.supportAppend() && !forceBreakStreaming) {
// current output support append
if (previousSibling) {
if (this._divSupportAppend(previousSibling as HTMLElement | null, pickedMimeTypeRenderer.mimeType)) {
this.useDedicatedDOM = false;
this.innerContainer = previousSibling as HTMLElement;
} else {
this.useDedicatedDOM = true;
this.innerContainer = DOM.$('.output-inner-container');
if (previousSibling.nextElementSibling) {
this.outputContainer.domNode.insertBefore(this.innerContainer, previousSibling.nextElementSibling);
} else {
this.outputContainer.domNode.appendChild(this.innerContainer);
}
}
} else {
// no previousSibling, append it to the very last
if (this._divSupportAppend(this.outputContainer.domNode.lastChild as HTMLElement | null, pickedMimeTypeRenderer.mimeType)) {
// last element allows append
this.useDedicatedDOM = false;
this.innerContainer = this.outputContainer.domNode.lastChild as HTMLElement;
} else {
this.useDedicatedDOM = true;
this.innerContainer = DOM.$('.output-inner-container');
this.outputContainer.domNode.appendChild(this.innerContainer);
}
}
} else {
this.useDedicatedDOM = true;
this.innerContainer = DOM.$('.output-inner-container');
private _generateInnerOutputContainer(previousSibling: HTMLElement | undefined, pickedMimeTypeRenderer: IOrderedMimeType) {
this.innerContainer = DOM.$('.output-inner-container');
if (previousSibling && previousSibling.nextElementSibling) {
this.outputContainer.domNode.insertBefore(this.innerContainer, previousSibling.nextElementSibling);
} else if (this.useDedicatedDOM) {
this.outputContainer.domNode.appendChild(this.innerContainer);
}
if (previousSibling && previousSibling.nextElementSibling) {
this.outputContainer.domNode.insertBefore(this.innerContainer, previousSibling.nextElementSibling);
} else {
this.outputContainer.domNode.appendChild(this.innerContainer);
}
this.innerContainer.setAttribute('output-mime-type', pickedMimeTypeRenderer.mimeType);
return this.innerContainer;
}
private _initHeightChecked = false;
probeHeight(index: number) {
if (!this._initHeightChecked && this.renderResult?.type === RenderOutputType.Mainframe) {
// postponed DOM read
const offsetHeight = this.domOffsetHeight;
this.viewCell.updateOutputHeight(index, offsetHeight, 'CellOutputElement#renderResultInitHeight');
}
}
render(previousSibling: HTMLElement | undefined, forceBreakStreaming: boolean = false): IRenderResult | undefined {
render(previousSibling: HTMLElement | undefined): IRenderResult | undefined {
const index = this.viewCell.outputsViewModels.indexOf(this.output);
if (this.viewCell.isOutputCollapsed || !this.notebookEditor.hasModel()) {
@ -259,21 +189,15 @@ export class CellOutputElement extends Disposable {
}
const pickedMimeTypeRenderer = mimeTypes[pick];
// generate an innerOutputContainer only when needed, for text streaming, it will reuse the previous element's container
const innerContainer = this._generateInnerOutputContainer(previousSibling, pickedMimeTypeRenderer, forceBreakStreaming);
const innerContainer = this._generateInnerOutputContainer(previousSibling, pickedMimeTypeRenderer);
this._attachToolbar(innerContainer, notebookTextModel, this.notebookEditor.activeKernel, index, mimeTypes);
this.renderedOutputContainer = DOM.append(innerContainer, DOM.$('.rendered-output'));
if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) {
const renderer = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId);
this.renderResult = renderer
? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType }
: this.notebookEditor.getOutputRenderer().render(this.output, this.renderedOutputContainer, pickedMimeTypeRenderer.mimeType, notebookUri);
} else {
this.renderResult = this.notebookEditor.getOutputRenderer().render(this.output, this.renderedOutputContainer, pickedMimeTypeRenderer.mimeType, notebookUri);
}
const renderer = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId);
this.renderResult = renderer
? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType }
: this._renderMissingRenderer(this.output, pickedMimeTypeRenderer.mimeType);
this.output.pickedMimeType = pickedMimeTypeRenderer;
@ -282,92 +206,38 @@ export class CellOutputElement extends Disposable {
return undefined;
}
if (this.renderResult.type !== RenderOutputType.Mainframe) {
this.notebookEditor.createOutput(this.viewCell, this.renderResult, this.viewCell.getOutputOffset(index));
innerContainer.classList.add('background');
} else {
innerContainer.classList.add('foreground', 'output-element');
innerContainer.style.position = 'absolute';
this.notebookEditor.createOutput(this.viewCell, this.renderResult, this.viewCell.getOutputOffset(index));
innerContainer.classList.add('background');
return { initRenderIsSynchronous: false };
}
private _renderMissingRenderer(viewModel: ICellOutputViewModel, preferredMimeType: string | undefined): IInsetRenderOutput {
if (!viewModel.model.outputs.length) {
return this._renderMessage(viewModel, nls.localize('empty', "Cell has no output"));
}
if (this.renderResult.type === RenderOutputType.Html || this.renderResult.type === RenderOutputType.Extension) {
// the output is rendered in the webview, which has resize listener internally
// no-op
return { initRenderIsSynchronous: false };
if (!preferredMimeType) {
const mimeTypes = viewModel.model.outputs.map(op => op.mime);
const mimeTypesMessage = mimeTypes.join(', ');
return this._renderMessage(viewModel, nls.localize('noRenderer.2', "No renderer could be found for output. It has the following mimetypes: {0}", mimeTypesMessage));
}
if (!this.useDedicatedDOM) {
// we only support text streaming, which is sync.
return { initRenderIsSynchronous: true };
}
return this._renderSearchForMimetype(viewModel, preferredMimeType);
}
let offsetHeight = 0;
if (this.renderResult?.initHeight) {
offsetHeight = this.renderResult.initHeight;
this._initHeightChecked = true;
} else {
const outputIndex = this.viewCell.outputsViewModels.indexOf(this.output);
const oldHeight = this.viewCell.getOutputHeight(outputIndex);
if (oldHeight > 0) {
offsetHeight = oldHeight;
this._initHeightChecked = true;
} else {
this._initHeightChecked = false;
}
}
const dimension = {
width: this.viewCell.layoutInfo.editorWidth,
height: offsetHeight
private _renderSearchForMimetype(viewModel: ICellOutputViewModel, mimeType: string): IInsetRenderOutput {
const query = `@tag:notebookRenderer ${mimeType}`;
return {
type: RenderOutputType.Html,
source: viewModel,
htmlContent: `<p>No renderer could be found for mimetype "${mimeType}", but one might be available on the Marketplace.</p>
<a href="command:workbench.extensions.search?%22${query}%22" class="monaco-button monaco-text-button" tabindex="0" role="button" style="padding: 8px; text-decoration: none; color: rgb(255, 255, 255); background-color: rgb(14, 99, 156); max-width: 200px;">Search Marketplace</a>`
};
// let's use resize listener for them
this._bindResizeListener(innerContainer, dimension);
if (this._initHeightChecked) {
this.viewCell.updateOutputHeight(index, offsetHeight, 'CellOutputElement#renderResultInitHeight');
}
const top = this.viewCell.getOutputOffsetInContainer(index);
innerContainer.style.top = `${top}px`;
return { initRenderIsSynchronous: this._initHeightChecked };
}
private _bindResizeListener(innerContainer: HTMLElement, dimension: DOM.IDimension) {
const elementSizeObserver = getResizesObserver(innerContainer, dimension, () => {
if (this.outputContainer && document.body.contains(this.outputContainer.domNode)) {
const height = elementSizeObserver.getHeight() + OutputInnerContainerTopPadding * 2;
if (dimension.height === height) {
return;
}
const currIndex = this.viewCell.outputsViewModels.indexOf(this.output);
if (currIndex < 0) {
return;
}
dimension = {
width: this.viewCell.layoutInfo.editorWidth,
height: height
};
this._initHeightChecked = true;
this._height = height;
this._validateFinalOutputHeight(true);
this.viewCell.updateOutputHeight(currIndex, height, 'CellOutputElement#outputResize');
this._relayoutCell();
}
});
elementSizeObserver.startObserving();
this._renderDisposableStore.add(elementSizeObserver);
}
private _divSupportAppend(element: HTMLElement | null, mimeType: string) {
if (element) {
return element.getAttribute('output-mime-type') === mimeType;
}
return false;
private _renderMessage(viewModel: ICellOutputViewModel, message: string): IInsetRenderOutput {
return { type: RenderOutputType.Html, source: viewModel, htmlContent: `<p>${message}</p>` };
}
private async _attachToolbar(outputItemDiv: HTMLElement, notebookTextModel: NotebookTextModel, kernel: INotebookKernel | undefined, index: number, mimeTypes: readonly IOrderedMimeType[]) {
@ -500,11 +370,7 @@ export class CellOutputElement extends Disposable {
view?.search(`@id:${JUPYTER_EXTENSION_ID}`);
}
private _generateRendererInfo(renderId: string | undefined): string {
if (renderId === undefined || renderId === BUILTIN_RENDERER_ID) {
return nls.localize('builtinRenderInfo', "built-in");
}
private _generateRendererInfo(renderId: string): string {
const renderInfo = this.notebookService.getRendererInfo(renderId);
if (renderInfo) {
@ -541,10 +407,6 @@ export class CellOutputElement extends Disposable {
clearTimeout(this._outputHeightTimer);
}
if (this.renderResult && this.renderResult.type === RenderOutputType.Mainframe) {
this.renderResult.disposable?.dispose();
}
super.dispose();
}
}
@ -602,12 +464,6 @@ export class CellOutputContainer extends CellPart {
}
prepareLayout() {
this._outputEntries.forEach(entry => {
const index = this.viewCell.outputsViewModels.indexOf(entry.model);
if (index >= 0) {
entry.element.probeHeight(index);
}
});
}
@ -659,13 +515,7 @@ export class CellOutputContainer extends CellPart {
const viewHandler = this._outputEntries[index];
const outputEntry = viewHandler.element;
if (outputEntry.renderResult) {
if (outputEntry.renderResult.type !== RenderOutputType.Mainframe) {
this.notebookEditor.createOutput(this.viewCell, outputEntry.renderResult as IInsetRenderOutput, this.viewCell.getOutputOffset(index));
} else if (!initRendering) {
// force read otherwise the real height is updated in next frame through resize observer
outputEntry.forceReadDOM();
this.viewCell.updateOutputHeight(index, outputEntry.domOffsetHeight, 'CellOutputContainer#viewUpdateShowOutputs');
}
this.notebookEditor.createOutput(this.viewCell, outputEntry.renderResult as IInsetRenderOutput, this.viewCell.getOutputOffset(index));
} else {
outputEntry.render(undefined);
}
@ -723,8 +573,6 @@ export class CellOutputContainer extends CellPart {
const secondGroupEntries = this._outputEntries.slice(splice.start + splice.deleteCount);
let newlyInserted = this.viewCell.outputsViewModels.slice(splice.start, splice.start + splice.newOutputs.length);
let outputHasDynamicHeight = false;
// [...firstGroup, ...deletedEntries, ...secondGroupEntries] [...restInModel]
// [...firstGroup, ...newlyInserted, ...secondGroupEntries, restInModel]
if (firstGroupEntries.length + newlyInserted.length + secondGroupEntries.length > this.options.limit) {
@ -744,10 +592,7 @@ export class CellOutputContainer extends CellPart {
// render newly inserted outputs
for (let i = firstGroupEntries.length; i < this._outputEntries.length; i++) {
const renderResult = this._outputEntries[i].element.render(undefined, i >= 1 && !this._outputEntries[i - 1].element.innerContainer);
if (renderResult) {
outputHasDynamicHeight = outputHasDynamicHeight || !renderResult.initRenderIsSynchronous;
}
this._outputEntries[i].element.render(undefined);
}
} else {
// part of secondGroupEntries are pushed out of view
@ -761,18 +606,6 @@ export class CellOutputContainer extends CellPart {
// exclusive
let reRenderRightBoundary = firstGroupEntries.length + newlyInserted.length;
for (let j = 0; j < secondGroupEntries.length; j++) {
const entry = secondGroupEntries[j];
if (!entry.element.useDedicatedDOM) {
entry.element.detach();
entry.element.dispose();
secondGroupEntries[j] = new OutputEntryViewHandler(entry.model, this.instantiationService.createInstance(CellOutputElement, this.notebookEditor, this.viewCell, this, this.templateData.outputContainer, entry.model));
reRenderRightBoundary++;
} else {
break;
}
}
const newlyInsertedEntries = newlyInserted.map(insert => {
return new OutputEntryViewHandler(insert, this.instantiationService.createInstance(CellOutputElement, this.notebookEditor, this.viewCell, this, this.templateData.outputContainer, insert));
});
@ -781,10 +614,7 @@ export class CellOutputContainer extends CellPart {
for (let i = firstGroupEntries.length; i < reRenderRightBoundary; i++) {
const previousSibling = i - 1 >= 0 && this._outputEntries[i - 1] && !!(this._outputEntries[i - 1].element.innerContainer?.parentElement) ? this._outputEntries[i - 1].element.innerContainer : undefined;
const renderResult = this._outputEntries[i].element.render(previousSibling, i >= 1 && !this._outputEntries[i - 1].element.innerContainer);
if (renderResult) {
outputHasDynamicHeight = outputHasDynamicHeight || !renderResult.initRenderIsSynchronous;
}
this._outputEntries[i].element.render(previousSibling);
}
}
} else {
@ -796,18 +626,6 @@ export class CellOutputContainer extends CellPart {
let reRenderRightBoundary = firstGroupEntries.length + newlyInserted.length;
for (let j = 0; j < secondGroupEntries.length; j++) {
const entry = secondGroupEntries[j];
if (!entry.element.useDedicatedDOM) {
entry.element.detach();
entry.element.dispose();
secondGroupEntries[j] = new OutputEntryViewHandler(entry.model, this.instantiationService.createInstance(CellOutputElement, this.notebookEditor, this.viewCell, this, this.templateData.outputContainer, entry.model));
reRenderRightBoundary++;
} else {
break;
}
}
const newlyInsertedEntries = newlyInserted.map(insert => {
return new OutputEntryViewHandler(insert, this.instantiationService.createInstance(CellOutputElement, this.notebookEditor, this.viewCell, this, this.templateData.outputContainer, insert));
});
@ -823,30 +641,14 @@ export class CellOutputContainer extends CellPart {
this._outputEntries = [...firstGroupEntries, ...newlyInsertedEntries, ...secondGroupEntries, ...outputsNewlyAvailable];
// if (firstGroupEntries.length + newlyInserted.length === this._outputEntries.length) {
// // inserted at the very end
// for (let i = firstGroupEntries.length; i < this._outputEntries.length; i++) {
// const renderResult = this._outputEntries[i].entry.render();
// if (renderResult) {
// outputHasDynamicHeight = outputHasDynamicHeight || !renderResult.initRenderIsSynchronous;
// }
// }
// } else {
for (let i = firstGroupEntries.length; i < reRenderRightBoundary; i++) {
const previousSibling = i - 1 >= 0 && this._outputEntries[i - 1] && !!(this._outputEntries[i - 1].element.innerContainer?.parentElement) ? this._outputEntries[i - 1].element.innerContainer : undefined;
const renderResult = this._outputEntries[i].element.render(previousSibling, i >= 1 && !this._outputEntries[i - 1].element.innerContainer);
if (renderResult) {
outputHasDynamicHeight = outputHasDynamicHeight || !renderResult.initRenderIsSynchronous;
}
this._outputEntries[i].element.render(previousSibling);
}
for (let i = 0; i < outputsNewlyAvailable.length; i++) {
const renderResult = this._outputEntries[firstGroupEntries.length + newlyInserted.length + secondGroupEntries.length + i].element.render(undefined);
if (renderResult) {
outputHasDynamicHeight = outputHasDynamicHeight || !renderResult.initRenderIsSynchronous;
}
this._outputEntries[firstGroupEntries.length + newlyInserted.length + secondGroupEntries.length + i].element.render(undefined);
}
// }
}
if (this.viewCell.outputsViewModels.length > this.options.limit) {
@ -865,7 +667,7 @@ export class CellOutputContainer extends CellPart {
this._relayoutCell();
// if it's clearing all outputs, or outputs are all rendered synchronously
// shrink immediately as the final output height will be zero.
this._validateFinalOutputHeight(!outputHasDynamicHeight || this.viewCell.outputsViewModels.length === 0);
this._validateFinalOutputHeight(false || this.viewCell.outputsViewModels.length === 0);
}
private _generateShowMoreElement(disposables: DisposableStore): HTMLElement {
@ -879,7 +681,7 @@ export class CellOutputContainer extends CellPart {
actionHandler: {
callback: (content) => {
if (content === 'command:workbench.action.openLargeOutput') {
this.openerService.open(CellUri.generateCellOutputUri(this.notebookEditor.textModel!.uri, this.viewCell.handle));
this.openerService.open(CellUri.generateCellOutputUri(this.notebookEditor.textModel!.uri));
}
return;

View file

@ -14,7 +14,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICellOutputViewModel, ICellViewModel, IGenericCellViewModel, INotebookCellOutputLayoutInfo, INotebookEditorCreationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { ICellOutputViewModel, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellExecutionPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution';
import { CellFocusIndicator } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellFocusIndicator';
import { CellProgressBar } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar';
@ -129,15 +129,3 @@ export interface CodeCellRenderTemplate extends BaseCellRenderTemplate {
progressBar: CellProgressBar;
cellExecution: CellExecutionPart;
}
export function isCodeCellRenderTemplate(templateData: BaseCellRenderTemplate): templateData is CodeCellRenderTemplate {
return !!(templateData as CodeCellRenderTemplate).runToolbar;
}
/**
* Notebook Editor Delegate for output rendering
*/
export interface INotebookDelegateForOutput {
readonly creationOptions: INotebookEditorCreationOptions;
getCellOutputLayoutInfo(cell: IGenericCellViewModel): INotebookCellOutputLayoutInfo;
}

View file

@ -1,105 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Button } from 'vs/base/browser/ui/button/button';
import { onUnexpectedError } from 'vs/base/common/errors';
import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICellOutputViewModel, IOutputRenderer, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookDelegateForOutput } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon';
import { OutputRendererRegistry } from 'vs/workbench/contrib/notebook/browser/view/output/rendererRegistry';
export class OutputRenderer implements IOutputRenderer {
private readonly _richMimeTypeRenderers = new Map<string, IOutputTransformContribution>();
constructor(
private readonly notebookEditor: INotebookDelegateForOutput,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICommandService private readonly commandservice: ICommandService,
) {
}
dispose(): void {
dispose(this._richMimeTypeRenderers.values());
this._richMimeTypeRenderers.clear();
}
getContribution(preferredMimeType: string): IOutputTransformContribution | undefined {
this._initialize();
return this._richMimeTypeRenderers.get(preferredMimeType);
}
private _initialize() {
if (this._richMimeTypeRenderers.size) {
return;
}
for (const desc of OutputRendererRegistry.getOutputTransformContributions()) {
try {
const contribution = this.instantiationService.createInstance(desc.ctor, this.notebookEditor);
contribution.getMimetypes().forEach(mimetype => { this._richMimeTypeRenderers.set(mimetype, contribution); });
} catch (err) {
onUnexpectedError(err);
}
}
}
private _renderMessage(container: HTMLElement, message: string): IRenderOutput {
const contentNode = document.createElement('p');
contentNode.innerText = message;
container.appendChild(contentNode);
return { type: RenderOutputType.Mainframe };
}
private _renderSearchForMimetype(container: HTMLElement, mimeType: string): IRenderOutput {
const disposable = new DisposableStore();
const contentNode = document.createElement('p');
contentNode.innerText = localize('noRenderer.1', "No renderer could be found for mimetype \"{0}\", but one might be available on the Marketplace.", mimeType);
const button = new Button(container);
button.label = localize('noRenderer.search', 'Search Marketplace');
button.element.style.maxWidth = `200px`;
disposable.add(button.onDidClick(() => this.commandservice.executeCommand('workbench.extensions.search', `@tag:notebookRenderer ${mimeType}`)));
disposable.add(button);
container.appendChild(contentNode);
container.appendChild(button.element);
return {
type: RenderOutputType.Mainframe,
disposable,
};
}
render(viewModel: ICellOutputViewModel, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI): IRenderOutput {
this._initialize();
if (!viewModel.model.outputs.length) {
return this._renderMessage(container, localize('empty', "Cell has no output"));
}
if (!preferredMimeType) {
const mimeTypes = viewModel.model.outputs.map(op => op.mime);
const mimeTypesMessage = mimeTypes.join(', ');
return this._renderMessage(container, localize('noRenderer.2', "No renderer could be found for output. It has the following mimetypes: {0}", mimeTypesMessage));
}
if (!preferredMimeType || !this._richMimeTypeRenderers.has(preferredMimeType)) {
if (preferredMimeType) {
return this._renderSearchForMimetype(container, preferredMimeType);
}
}
const renderer = this._richMimeTypeRenderers.get(preferredMimeType);
if (!renderer) {
return this._renderSearchForMimetype(container, preferredMimeType);
}
const first = viewModel.model.outputs.find(op => op.mime === preferredMimeType);
if (!first) {
return this._renderMessage(container, localize('empty', "Cell has no output"));
}
return renderer.render(viewModel, first, container, notebookUri);
}
}

View file

@ -1,27 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { BrandedService, IConstructorSignature } from 'vs/platform/instantiation/common/instantiation';
import { IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookDelegateForOutput } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon';
export type IOutputTransformCtor = IConstructorSignature<IOutputTransformContribution, [INotebookDelegateForOutput]>;
export interface IOutputTransformDescription {
ctor: IOutputTransformCtor;
}
export const OutputRendererRegistry = new class NotebookRegistryImpl {
readonly #outputTransforms: IOutputTransformDescription[] = [];
registerOutputTransform<Services extends BrandedService[]>(ctor: { new(editor: INotebookDelegateForOutput, ...services: Services): IOutputTransformContribution }): void {
this.#outputTransforms.push({ ctor: ctor as IOutputTransformCtor });
}
getOutputTransformContributions(): IOutputTransformDescription[] {
return this.#outputTransforms.slice(0);
}
};

View file

@ -1,112 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Mimes } from 'vs/base/common/mime';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { ICellOutputViewModel, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookDelegateForOutput } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon';
import { OutputRendererRegistry } from 'vs/workbench/contrib/notebook/browser/view/output/rendererRegistry';
import { truncatedArrayOfString } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper';
import { IOutputItemDto, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
class StreamRendererContrib extends Disposable implements IOutputTransformContribution {
getType() {
return RenderOutputType.Mainframe;
}
getMimetypes() {
return ['application/vnd.code.notebook.stdout', 'application/x.notebook.stdout', 'application/x.notebook.stream'];
}
constructor(
public notebookEditor: INotebookDelegateForOutput,
@IOpenerService private readonly openerService: IOpenerService,
@IThemeService private readonly themeService: IThemeService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
}
render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput {
const disposables = new DisposableStore();
const linkDetector = this.instantiationService.createInstance(LinkDetector);
const text = getStringValue(item);
const contentNode = DOM.$('span.output-stream');
const lineLimit = this.configurationService.getValue<number>(NotebookSetting.textOutputLineLimit) ?? 30;
truncatedArrayOfString(notebookUri, output.cellViewModel, output.model.outputId, Math.max(lineLimit, 6), contentNode, [text], disposables, linkDetector, this.openerService, this.themeService);
container.appendChild(contentNode);
return { type: RenderOutputType.Mainframe, disposable: disposables };
}
}
class StderrRendererContrib extends StreamRendererContrib {
override getType() {
return RenderOutputType.Mainframe;
}
override getMimetypes() {
return ['application/vnd.code.notebook.stderr', 'application/x.notebook.stderr'];
}
override render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput {
const result = super.render(output, item, container, notebookUri);
container.classList.add('error');
return result;
}
}
class PlainTextRendererContrib extends Disposable implements IOutputTransformContribution {
getType() {
return RenderOutputType.Mainframe;
}
getMimetypes() {
return [Mimes.text];
}
constructor(
public notebookEditor: INotebookDelegateForOutput,
@IOpenerService private readonly openerService: IOpenerService,
@IThemeService private readonly themeService: IThemeService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
}
render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput {
const disposables = new DisposableStore();
const linkDetector = this.instantiationService.createInstance(LinkDetector);
const str = getStringValue(item);
const contentNode = DOM.$('.output-plaintext');
const lineLimit = this.configurationService.getValue<number>(NotebookSetting.textOutputLineLimit) ?? 30;
truncatedArrayOfString(notebookUri, output.cellViewModel, output.model.outputId, Math.max(lineLimit, 6), contentNode, [str], disposables, linkDetector, this.openerService, this.themeService);
container.appendChild(contentNode);
return { type: RenderOutputType.Mainframe, supportAppend: true, disposable: disposables };
}
}
OutputRendererRegistry.registerOutputTransform(PlainTextRendererContrib);
OutputRendererRegistry.registerOutputTransform(StreamRendererContrib);
OutputRendererRegistry.registerOutputTransform(StderrRendererContrib);
// --- utils ---
export function getStringValue(item: IOutputItemDto): string {
return item.data.toString();
}

View file

@ -1,104 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
import { Codicon } from 'vs/base/common/codicons';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { DefaultEndOfLine, EndOfLinePreference, ITextBuffer } from 'vs/editor/common/model';
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { IGenericCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
const SIZE_LIMIT = 65535;
function generateViewMoreElement(notebookUri: URI, cellViewModel: IGenericCellViewModel, outputId: string, disposables: DisposableStore, openerService: IOpenerService): HTMLElement {
const md: IMarkdownString = {
value: `Output exceeds the [size limit](command:workbench.action.openSettings?["notebook.output.textLineLimit"]). Open the full output data[ in a text editor](command:workbench.action.openLargeOutput?${outputId})`,
isTrusted: true,
supportThemeIcons: true
};
const rendered = disposables.add(renderMarkdown(md, {
actionHandler: {
callback: (content) => {
const ret = /command\:workbench\.action\.openLargeOutput\?(.*)/.exec(content);
if (ret && ret.length === 2) {
const outputId = ret[1];
openerService.open(CellUri.generateCellOutputUri(notebookUri, cellViewModel.handle, outputId));
}
if (content.startsWith('command:workbench.action.openSettings')) {
openerService.open(content, { allowCommands: true });
}
return;
},
disposables: disposables
}
}));
rendered.element.classList.add('output-show-more');
return rendered.element;
}
export function truncatedArrayOfString(notebookUri: URI, cellViewModel: IGenericCellViewModel, outputId: string, linesLimit: number, container: HTMLElement, outputs: string[], disposables: DisposableStore, linkDetector: LinkDetector, openerService: IOpenerService, themeService: IThemeService) {
const fullLen = outputs.reduce((p, c) => {
return p + c.length;
}, 0);
let buffer: ITextBuffer | undefined = undefined;
if (fullLen > SIZE_LIMIT) {
// it's too large and we should find min(maxSizeLimit, maxLineLimit)
const bufferBuilder = new PieceTreeTextBufferBuilder();
outputs.forEach(output => bufferBuilder.acceptChunk(output));
const factory = bufferBuilder.finish();
buffer = factory.create(DefaultEndOfLine.LF).textBuffer;
const sizeBufferLimitPosition = buffer.getPositionAt(SIZE_LIMIT);
if (sizeBufferLimitPosition.lineNumber < linesLimit) {
const truncatedText = buffer.getValueInRange(new Range(1, 1, sizeBufferLimitPosition.lineNumber, sizeBufferLimitPosition.column), EndOfLinePreference.TextDefined);
container.appendChild(handleANSIOutput(truncatedText, linkDetector, themeService, undefined));
// view more ...
container.appendChild(generateViewMoreElement(notebookUri, cellViewModel, outputId, disposables, openerService));
return;
}
}
if (!buffer) {
const bufferBuilder = new PieceTreeTextBufferBuilder();
outputs.forEach(output => bufferBuilder.acceptChunk(output));
const factory = bufferBuilder.finish();
buffer = factory.create(DefaultEndOfLine.LF).textBuffer;
}
if (buffer.getLineCount() < linesLimit) {
const lineCount = buffer.getLineCount();
const fullRange = new Range(1, 1, lineCount, Math.max(1, buffer.getLineLastNonWhitespaceColumn(lineCount)));
container.appendChild(handleANSIOutput(buffer.getValueInRange(fullRange, EndOfLinePreference.TextDefined), linkDetector, themeService, undefined));
return;
}
container.appendChild(generateViewMoreElement(notebookUri, cellViewModel, outputId, disposables, openerService));
const div = DOM.$('div');
container.appendChild(div);
div.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(1, 1, linesLimit - 5, buffer.getLineLastNonWhitespaceColumn(linesLimit - 5)), EndOfLinePreference.TextDefined), linkDetector, themeService, undefined));
// view more ...
DOM.append(container, DOM.$('span' + Codicon.toolBarMore.cssSelector));
const lineCount = buffer.getLineCount();
const div2 = DOM.$('div');
container.appendChild(div2);
div2.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(lineCount - 5, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)), EndOfLinePreference.TextDefined), linkDetector, themeService, undefined));
}

View file

@ -35,11 +35,12 @@ import { CellEditState, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, I
import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads';
import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping';
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
import { INotebookRendererInfo, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IWebviewElement, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, IContentWidgetTopRequest, IControllerPreload, ICreationRequestMessage, IFindMatch, IMarkupCellInitialization, ToWebviewMessage } from './webviewMessages';
@ -126,6 +127,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
@IConfigurationService private readonly configurationService: IConfigurationService,
@ILanguageService private readonly languageService: ILanguageService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
) {
super();
@ -216,6 +218,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
{ dragAndDropEnabled: this.options.dragAndDropEnabled },
renderersData,
this.workspaceTrustManagementService.isWorkspaceTrusted(),
this.configurationService.getValue<number>(NotebookSetting.textOutputLineLimit) ?? 30,
this.nonce);
const enableCsp = this.configurationService.getValue('notebook.experimental.enableCsp');
@ -527,6 +530,12 @@ var requirejs = (function() {
}
if (matchesScheme(link, Schemas.command)) {
const ret = /command\:workbench\.action\.openLargeOutput\?(.*)/.exec(link);
if (ret && ret.length === 2) {
const outputId = ret[1];
this.openerService.open(CellUri.generateCellOutputUri(this.documentUri, outputId));
return;
}
console.warn('Command links are deprecated and will be removed, use messag passing instead: https://github.com/microsoft/vscode/issues/123601');
}
@ -653,6 +662,22 @@ var requirejs = (function() {
case 'clicked-link':
{
let linkToOpen: URI | string | undefined;
if (matchesScheme(data.href, Schemas.command)) {
const ret = /command\:workbench\.action\.openLargeOutput\?(.*)/.exec(data.href);
if (ret && ret.length === 2) {
const outputId = ret[1];
const group = this.editorGroupService.activeGroup;
if (group) {
if (group.activeEditor) {
group.pinEditor(group.activeEditor);
}
}
this.openerService.open(CellUri.generateCellOutputUri(this.documentUri, outputId));
return;
}
}
if (matchesSomeScheme(data.href, Schemas.http, Schemas.https, Schemas.mailto, Schemas.command, Schemas.vscodeNotebookCell, Schemas.vscodeNotebook)) {
linkToOpen = data.href;
} else if (!/^[\w\-]+:/.test(data.href)) {

View file

@ -64,6 +64,7 @@ interface PreloadContext {
readonly options: PreloadOptions;
readonly rendererData: readonly RendererMetadata[];
readonly isWorkspaceTrusted: boolean;
readonly lineLimit: number;
}
declare function __import(path: string): Promise<any>;
@ -74,6 +75,7 @@ async function webviewPreloads(ctx: PreloadContext) {
let currentOptions = ctx.options;
let isWorkspaceTrusted = ctx.isWorkspaceTrusted;
let lineLimit = ctx.lineLimit;
const acquireVsCodeApi = globalThis.acquireVsCodeApi;
const vscode = acquireVsCodeApi();
@ -202,6 +204,7 @@ async function webviewPreloads(ctx: PreloadContext) {
postMessage?(message: unknown): void;
onDidReceiveMessage?: Event<unknown>;
readonly workspace: { readonly isTrusted: boolean };
readonly settings: { readonly lineLimit: number };
}
interface RendererModule {
@ -1208,6 +1211,9 @@ async function webviewPreloads(ctx: PreloadContext) {
getRenderer: async (id: string) => renderers.getRenderer(id)?.api,
workspace: {
get isTrusted() { return isWorkspaceTrusted; }
},
settings: {
get lineLimit() { return lineLimit; },
}
};
@ -2103,12 +2109,13 @@ export interface RendererMetadata {
readonly isBuiltin: boolean;
}
export function preloadsScriptStr(styleValues: PreloadStyles, options: PreloadOptions, renderers: readonly RendererMetadata[], isWorkspaceTrusted: boolean, nonce: string) {
export function preloadsScriptStr(styleValues: PreloadStyles, options: PreloadOptions, renderers: readonly RendererMetadata[], isWorkspaceTrusted: boolean, lineLimit: number, nonce: string) {
const ctx: PreloadContext = {
style: styleValues,
options,
rendererData: renderers,
isWorkspaceTrusted,
lineLimit,
nonce,
};
// TS will try compiling `import()` in webviewPreloads, so use a helper function instead

View file

@ -6,7 +6,7 @@
import { Disposable } from 'vs/base/common/lifecycle';
import { ICellOutputViewModel, IGenericCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { ICellOutput, IOrderedMimeType, mimeTypeIsMergeable, RENDERER_NOT_AVAILABLE } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellOutput, IOrderedMimeType, RENDERER_NOT_AVAILABLE } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
let handle = 0;
@ -42,11 +42,6 @@ export class CellOutputViewModel extends Disposable implements ICellOutputViewMo
return this._outputRawData.outputs.some(output => output.mime !== firstMimeType);
}
supportAppend() {
// if there is any mime type that's not mergeable then the whole output is not mergeable.
return this._outputRawData.outputs.every(op => mimeTypeIsMergeable(op.mime));
}
resolveMimeTypes(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined): [readonly IOrderedMimeType[], number] {
const mimeTypes = this._notebookService.getOutputMimeTypeInfo(textModel, kernelProvides, this.model);
let index = -1;

View file

@ -72,7 +72,6 @@ export const RENDERER_EQUIVALENT_EXTENSIONS: ReadonlyMap<string, ReadonlySet<str
['ms-toolsai.jupyter-renderers', new Set(['jupyter-notebook', 'interactive'])],
]);
export const BUILTIN_RENDERER_ID = '_builtin';
export const RENDERER_NOT_AVAILABLE = '_notAvailable';
export type NotebookRendererEntrypoint = string | { extends: string; path: string };
@ -535,27 +534,26 @@ export namespace CellUri {
};
}
export function generateCellOutputUri(notebook: URI, handle: number, outputId?: string) {
export function generateCellOutputUri(notebook: URI, outputId?: string) {
return notebook.with({
scheme: Schemas.vscodeNotebookCellOutput,
fragment: `ch${handle.toString().padStart(7, '0')},${outputId ?? ''},${notebook.scheme !== Schemas.file ? notebook.scheme : ''}`
fragment: `op${outputId ?? ''},${notebook.scheme !== Schemas.file ? notebook.scheme : ''}`
});
}
export function parseCellOutputUri(uri: URI): { notebook: URI; handle: number; outputId?: string } | undefined {
export function parseCellOutputUri(uri: URI): { notebook: URI; outputId?: string } | undefined {
if (uri.scheme !== Schemas.vscodeNotebookCellOutput) {
return;
}
const match = /^ch(\d{7,})\,([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})?\,(.*)$/i.exec(uri.fragment);
const match = /^op([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})?\,(.*)$/i.exec(uri.fragment);
if (!match) {
return undefined;
}
const handle = Number(match[1]);
const outputId = (match[2] && match[2] !== '') ? match[2] : undefined;
const scheme = match[3];
const outputId = (match[1] && match[1] !== '') ? match[1] : undefined;
const scheme = match[2];
return {
handle,
outputId,
notebook: uri.with({
scheme: scheme || Schemas.file,
@ -590,33 +588,6 @@ export namespace CellUri {
}
}
type MimeTypeInfo = {
alwaysSecure?: boolean;
supportedByCore?: boolean;
mergeable?: boolean;
};
const _mimeTypeInfo = new Map<string, MimeTypeInfo>([
['image/git', { alwaysSecure: true, supportedByCore: true }],
['application/json', { alwaysSecure: true, supportedByCore: true }],
[Mimes.text, { alwaysSecure: true, supportedByCore: true }],
['text/x-javascript', { alwaysSecure: true, supportedByCore: true }], // secure because rendered as text, not executed
['application/vnd.code.notebook.stdout', { alwaysSecure: true, supportedByCore: true, mergeable: true }],
['application/vnd.code.notebook.stderr', { alwaysSecure: true, supportedByCore: true, mergeable: true }],
]);
export function mimeTypeIsAlwaysSecure(mimeType: string): boolean {
return _mimeTypeInfo.get(mimeType)?.alwaysSecure ?? false;
}
export function mimeTypeSupportedByCore(mimeType: string) {
return _mimeTypeInfo.get(mimeType)?.supportedByCore ?? false;
}
export function mimeTypeIsMergeable(mimeType: string): boolean {
return _mimeTypeInfo.get(mimeType)?.mergeable ?? false;
}
const normalizeSlashes = (str: string) => isWindows ? str.replace(/\//g, '\\') : str;
interface IMimeTypeWithMatcher {

View file

@ -144,7 +144,7 @@ export class NotebookOptions extends Disposable {
cellBottomMargin: 6,
cellRightMargin: 16,
cellStatusBarHeight: 22,
cellOutputPadding: 12,
cellOutputPadding: 8,
markdownPreviewPadding: 8,
// bottomToolbarHeight: bottomToolbarHeight,
// bottomToolbarGap: bottomToolbarGap,

View file

@ -1,244 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as DOM from 'vs/base/browser/dom';
import { FastDomNode } from 'vs/base/browser/fastDomNode';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { mock } from 'vs/base/test/common/mock';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ICellOutputViewModel, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellOutputContainer } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput';
import { CodeCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon';
import { OutputRendererRegistry } from 'vs/workbench/contrib/notebook/browser/view/output/rendererRegistry';
import { getStringValue } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { BUILTIN_RENDERER_ID, CellEditType, CellKind, IOutputDto, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { setupInstantiationService, valueBytesFromString, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor';
OutputRendererRegistry.registerOutputTransform(class implements IOutputTransformContribution {
getType() { return RenderOutputType.Mainframe; }
getMimetypes() {
return ['application/vnd.code.notebook.stdout', 'application/x.notebook.stdout', 'application/x.notebook.stream'];
}
constructor() { }
render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement): IRenderOutput {
const text = getStringValue(item);
const contentNode = DOM.$('span.output-stream');
contentNode.textContent = text;
container.appendChild(contentNode);
return { type: RenderOutputType.Mainframe };
}
dispose() { }
});
suite('NotebookViewModel Outputs', async () => {
let disposables: DisposableStore;
let instantiationService: TestInstantiationService;
let openerService: IOpenerService;
suiteSetup(() => {
disposables = new DisposableStore();
instantiationService = setupInstantiationService(disposables);
instantiationService.stub(INotebookService, new class extends mock<INotebookService>() {
override getOutputMimeTypeInfo(textModel: NotebookTextModel, kernelProvides: [], output: IOutputDto) {
if (output.outputId === 'output_id_err') {
return [{
mimeType: 'application/vnd.code.notebook.stderr',
rendererId: BUILTIN_RENDERER_ID,
isTrusted: true
}];
}
return [{
mimeType: 'application/vnd.code.notebook.stdout',
rendererId: BUILTIN_RENDERER_ID,
isTrusted: true
}];
}
});
instantiationService.stub(IMenuService, new class extends mock<IMenuService>() {
override createMenu(arg: any, context: any): any {
return {
onDidChange: () => { },
getActions: (arg: any) => {
return [];
}
};
}
});
instantiationService.stub(IKeybindingService, new class extends mock<IKeybindingService>() {
override lookupKeybinding(arg: any): any {
return null;
}
});
openerService = instantiationService.stub(IOpenerService, {});
});
suiteTeardown(() => disposables.dispose());
test('stream outputs reuse output container', async () => {
await withTestNotebook(
[
['var a = 1;', 'javascript', CellKind.Code, [
{ outputId: 'output_id_1', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('1') }] },
{ outputId: 'output_id_2', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('2') }] },
{ outputId: 'output_id_err', outputs: [{ mime: 'application/vnd.code.notebook.stderr', data: valueBytesFromString('1000') }] },
{ outputId: 'output_id_3', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('3') }] },
], {}]
],
(editor, viewModel, accessor) => {
const container = new CellOutputContainer(editor, viewModel.viewCells[0] as CodeCellViewModel, {
outputContainer: new FastDomNode(document.createElement('div')),
outputShowMoreContainer: new FastDomNode(document.createElement('div')),
editor: {
getContentHeight: () => {
return 100;
}
},
templateDisposables: new DisposableStore(),
} as unknown as CodeCellRenderTemplate, { limit: 5 }, openerService, instantiationService);
container.render(100);
assert.strictEqual(container.renderedOutputEntries.length, 4);
assert.strictEqual(container.renderedOutputEntries[0].element.useDedicatedDOM, true);
assert.strictEqual(container.renderedOutputEntries[1].element.useDedicatedDOM, false);
assert.strictEqual(container.renderedOutputEntries[2].element.useDedicatedDOM, true);
assert.strictEqual(container.renderedOutputEntries[3].element.useDedicatedDOM, true);
assert.strictEqual(container.renderedOutputEntries[0].element.innerContainer, container.renderedOutputEntries[1].element.innerContainer);
assert.notStrictEqual(container.renderedOutputEntries[1].element.innerContainer, container.renderedOutputEntries[2].element.innerContainer);
assert.notStrictEqual(container.renderedOutputEntries[2].element.innerContainer, container.renderedOutputEntries[3].element.innerContainer);
editor.textModel.applyEdits([{
index: 0,
editType: CellEditType.Output,
outputs: [
{
outputId: 'output_id_4',
outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('4') }]
},
{
outputId: 'output_id_5',
outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('5') }]
}
],
append: true
}], true, undefined, () => undefined, undefined);
assert.strictEqual(container.renderedOutputEntries.length, 5);
// last one is merged with previous one
assert.strictEqual(container.renderedOutputEntries[3].element.innerContainer, container.renderedOutputEntries[4].element.innerContainer);
editor.textModel.applyEdits([{
index: 0,
editType: CellEditType.Output,
outputs: [
{ outputId: 'output_id_1', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('1') }] },
{ outputId: 'output_id_2', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('2') }] },
{ outputId: 'output_id_err', outputs: [{ mime: 'application/vnd.code.notebook.stderr', data: valueBytesFromString('1000') }] },
{
outputId: 'output_id_5',
outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('5') }]
}
],
}], true, undefined, () => undefined, undefined);
assert.strictEqual(container.renderedOutputEntries.length, 4);
assert.strictEqual(container.renderedOutputEntries[0].model.model.outputId, 'output_id_1');
assert.strictEqual(container.renderedOutputEntries[0].element.useDedicatedDOM, true);
assert.strictEqual(container.renderedOutputEntries[1].model.model.outputId, 'output_id_2');
assert.strictEqual(container.renderedOutputEntries[1].element.useDedicatedDOM, false);
assert.strictEqual(container.renderedOutputEntries[2].model.model.outputId, 'output_id_err');
assert.strictEqual(container.renderedOutputEntries[2].element.useDedicatedDOM, true);
assert.strictEqual(container.renderedOutputEntries[3].model.model.outputId, 'output_id_5');
assert.strictEqual(container.renderedOutputEntries[3].element.useDedicatedDOM, true);
},
instantiationService
);
});
test('stream outputs reuse output container 2', async () => {
await withTestNotebook(
[
['var a = 1;', 'javascript', CellKind.Code, [
{ outputId: 'output_id_1', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('1') }] },
{ outputId: 'output_id_2', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('2') }] },
{ outputId: 'output_id_err', outputs: [{ mime: 'application/vnd.code.notebook.stderr', data: valueBytesFromString('1000') }] },
{ outputId: 'output_id_4', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('4') }] },
{ outputId: 'output_id_5', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('5') }] },
{ outputId: 'output_id_6', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('6') }] },
], {}]
],
(editor, viewModel, accessor) => {
const container = new CellOutputContainer(editor, viewModel.viewCells[0] as CodeCellViewModel, {
outputContainer: new FastDomNode(document.createElement('div')),
outputShowMoreContainer: new FastDomNode(document.createElement('div')),
editor: {
getContentHeight: () => {
return 100;
}
},
templateDisposables: new DisposableStore(),
} as unknown as CodeCellRenderTemplate, { limit: 5 }, openerService, instantiationService);
container.render(100);
assert.strictEqual(container.renderedOutputEntries.length, 5);
assert.strictEqual(container.renderedOutputEntries[0].element.useDedicatedDOM, true);
assert.strictEqual(container.renderedOutputEntries[1].element.useDedicatedDOM, false);
assert.strictEqual(container.renderedOutputEntries[0].element.innerContainer?.innerText, '12');
assert.strictEqual(container.renderedOutputEntries[2].element.useDedicatedDOM, true);
assert.strictEqual(container.renderedOutputEntries[2].element.innerContainer?.innerText, '1000');
assert.strictEqual(container.renderedOutputEntries[3].element.useDedicatedDOM, true);
assert.strictEqual(container.renderedOutputEntries[4].element.useDedicatedDOM, false);
assert.strictEqual(container.renderedOutputEntries[3].element.innerContainer?.innerText, '45');
editor.textModel.applyEdits([{
index: 0,
editType: CellEditType.Output,
outputs: [
{ outputId: 'output_id_1', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('1') }] },
{ outputId: 'output_id_2', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('2') }] },
{ outputId: 'output_id_7', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('7') }] },
{ outputId: 'output_id_5', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('5') }] },
{ outputId: 'output_id_6', outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('6') }] },
]
}], true, undefined, () => undefined, undefined);
assert.strictEqual(container.renderedOutputEntries.length, 5);
assert.strictEqual(container.renderedOutputEntries[0].model.model.outputId, 'output_id_1');
assert.strictEqual(container.renderedOutputEntries[1].model.model.outputId, 'output_id_2');
assert.strictEqual(container.renderedOutputEntries[2].model.model.outputId, 'output_id_7');
assert.strictEqual(container.renderedOutputEntries[3].model.model.outputId, 'output_id_5');
assert.strictEqual(container.renderedOutputEntries[4].model.model.outputId, 'output_id_6');
assert.strictEqual(container.renderedOutputEntries[0].element.useDedicatedDOM, true);
assert.strictEqual(container.renderedOutputEntries[1].element.useDedicatedDOM, false);
assert.strictEqual(container.renderedOutputEntries[2].element.useDedicatedDOM, false);
assert.strictEqual(container.renderedOutputEntries[3].element.useDedicatedDOM, false);
assert.strictEqual(container.renderedOutputEntries[4].element.useDedicatedDOM, false);
assert.strictEqual(container.renderedOutputEntries[0].element.innerContainer, container.renderedOutputEntries[1].element.innerContainer);
assert.strictEqual(container.renderedOutputEntries[0].element.innerContainer, container.renderedOutputEntries[2].element.innerContainer);
assert.strictEqual(container.renderedOutputEntries[0].element.innerContainer, container.renderedOutputEntries[3].element.innerContainer);
assert.strictEqual(container.renderedOutputEntries[0].element.innerContainer, container.renderedOutputEntries[4].element.innerContainer);
assert.strictEqual(container.renderedOutputEntries[0].element.innerContainer?.innerText, '12756');
},
instantiationService
);
});
});

View file

@ -12,8 +12,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import { Mimes } from 'vs/base/common/mime';
import { URI } from 'vs/base/common/uri';
import { mock } from 'vs/base/test/common/mock';
import { EditorFontLigatures } from 'vs/editor/common/config/editorOptions';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { LanguageService } from 'vs/editor/common/services/languageService';
@ -22,7 +20,6 @@ import { ModelService } from 'vs/editor/common/services/modelService';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { NullCommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
@ -42,7 +39,6 @@ import { EditorModel } from 'vs/workbench/common/editor/editorModel';
import { CellFindMatchWithIndex, IActiveNotebookEditorDelegate, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { ListViewInfoAccessor } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher';
import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl';
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
@ -253,34 +249,6 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic
override focusElement() { }
override setCellEditorSelection() { }
override async revealRangeInCenterIfOutsideViewportAsync() { }
override getOutputRenderer() {
return new OutputRenderer({
creationOptions: notebookEditor.creationOptions,
getCellOutputLayoutInfo() {
return {
height: 100,
width: 100,
fontInfo: new FontInfo({
pixelRatio: 1,
fontFamily: 'mockFont',
fontWeight: 'normal',
fontSize: 14,
fontFeatureSettings: EditorFontLigatures.OFF,
lineHeight: 19,
letterSpacing: 1.5,
isMonospace: true,
typicalHalfwidthCharacterWidth: 10,
typicalFullwidthCharacterWidth: 20,
canUseHalfwidthRightwardsArrow: true,
spaceWidth: 10,
middotWidth: 10,
wsmiddotWidth: 10,
maxDigitWidth: 10,
}, true)
};
}
}, instantiationService, NullCommandService);
}
override async layoutNotebookCell() { }
override async removeInset() { }
override async focusNotebookCell() { }