mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 05:19:39 +00:00
Move stream renderer to extension, support merging.
This commit is contained in:
parent
91f7694e68
commit
d5d7e03cfc
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
38
extensions/notebook-renderers/src/textHelper.ts
Normal file
38
extensions/notebook-renderers/src/textHelper.ts
Normal 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')));
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
Loading…
Reference in a new issue