Move stream renderer to extension, support merging.

This commit is contained in:
rebornix 2022-02-08 12:54:56 -08:00
parent 91f7694e68
commit d5d7e03cfc
No known key found for this signature in database
GPG key ID: 181FC90D15393C20
9 changed files with 118 additions and 228 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

@ -5,6 +5,7 @@
import type { ActivationFunction, OutputItem } from 'vscode-notebook-renderer';
import { handleANSIOutput } from './ansi';
import { truncatedArrayOfString } from './textHelper';
interface IDisposable {
dispose(): void;
@ -76,21 +77,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,6 +108,50 @@ function renderError(outputIfo: OutputItem, container: HTMLElement): void {
container.classList.add('error');
}
function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boolean): 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([text], 30, element);
outputElement.appendChild(element);
return;
}
}
const element = document.createElement('span');
element.classList.add('output-stream');
const text = outputInfo.text();
truncatedArrayOfString([text], 30, element);
container.appendChild(element);
container.setAttribute('output-mime-type', outputInfo.mime);
if (error) {
container.classList.add('error');
}
}
function renderText(outputInfo: OutputItem, container: HTMLElement): void {
const contentNode = document.createElement('div');
contentNode.classList.add('.output-plaintext');
const text = outputInfo.text();
truncatedArrayOfString([text], 30, contentNode);
container.appendChild(contentNode);
}
export const activate: ActivationFunction<void> = (ctx) => {
const disposables = new Map<string, IDisposable>();
@ -137,6 +180,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 +190,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);
}
break;
case 'application/vnd.code.notebook.stderr':
case 'application/x.notebook.stderr':
{
renderStream(outputInfo, element, true);
}
break;
case 'text/plain':
{
renderText(outputInfo, element);
}
break;
default:
break;
}

View file

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* 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';
// const SIZE_LIMIT = 65535;
export function truncatedArrayOfString(outputs: string[], linesLimit: number, container: HTMLElement) {
// const fullLen = outputs.reduce((p, c) => {
// return p + c.length;
// }, 0);
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(notebookUri, cellViewModel, outputId, disposables, openerService));
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(linesLimit - 5).join('\n')));
}

View file

@ -95,7 +95,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';

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

@ -597,12 +597,8 @@ type MimeTypeInfo = {
};
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 {

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

@ -16,13 +16,16 @@ import { ICellOutputViewModel, IOutputTransformContribution, IRenderOutput, Rend
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';
export function getStringValue(item: IOutputItemDto): string {
return item.data.toString();
}
OutputRendererRegistry.registerOutputTransform(class implements IOutputTransformContribution {
getType() { return RenderOutputType.Mainframe; }