mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
Merge pull request #166659 from amunger/scrollableBorder
Scrollable notebook output experiment
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer';
|
||||
import { truncatedArrayOfString } from './textHelper';
|
||||
import { insertOutput } from './textHelper';
|
||||
|
||||
interface IDisposable {
|
||||
dispose(): void;
|
||||
@@ -28,6 +28,11 @@ interface JavaScriptRenderingHook {
|
||||
preEvaluate(outputItem: OutputItem, element: HTMLElement, script: string, signal: AbortSignal): string | undefined | Promise<string | undefined>;
|
||||
}
|
||||
|
||||
interface RenderOptions {
|
||||
readonly lineLimit: number;
|
||||
readonly outputScrolling: boolean;
|
||||
}
|
||||
|
||||
function clearContainer(container: HTMLElement) {
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
@@ -120,7 +125,7 @@ async function renderJavascript(outputInfo: OutputItem, container: HTMLElement,
|
||||
domEval(element);
|
||||
}
|
||||
|
||||
function renderError(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext<void> & { readonly settings: { readonly lineLimit: number } }): void {
|
||||
function renderError(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext<void> & { readonly settings: RenderOptions }): void {
|
||||
const element = document.createElement('div');
|
||||
container.appendChild(element);
|
||||
type ErrorLike = Partial<Error>;
|
||||
@@ -138,7 +143,7 @@ function renderError(outputInfo: OutputItem, container: HTMLElement, ctx: Render
|
||||
stack.classList.add('traceback');
|
||||
stack.style.margin = '8px 0';
|
||||
const element = document.createElement('span');
|
||||
truncatedArrayOfString(outputInfo.id, [err.stack ?? ''], ctx.settings.lineLimit, element);
|
||||
insertOutput(outputInfo.id, [err.stack ?? ''], ctx.settings.lineLimit, false, element);
|
||||
stack.appendChild(element);
|
||||
container.appendChild(stack);
|
||||
} else {
|
||||
@@ -153,7 +158,7 @@ function renderError(outputInfo: OutputItem, container: HTMLElement, ctx: Render
|
||||
container.classList.add('error');
|
||||
}
|
||||
|
||||
function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boolean, ctx: RendererContext<void> & { readonly settings: { readonly lineLimit: number } }): void {
|
||||
function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boolean, ctx: RendererContext<void> & { readonly settings: RenderOptions }): void {
|
||||
const outputContainer = container.parentElement;
|
||||
if (!outputContainer) {
|
||||
// should never happen
|
||||
@@ -170,7 +175,7 @@ function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boo
|
||||
const text = outputInfo.text();
|
||||
|
||||
const element = document.createElement('span');
|
||||
truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, element);
|
||||
insertOutput(outputInfo.id, [text], ctx.settings.lineLimit, ctx.settings.outputScrolling, element);
|
||||
outputElement.appendChild(element);
|
||||
return;
|
||||
}
|
||||
@@ -180,7 +185,7 @@ function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boo
|
||||
element.classList.add('output-stream');
|
||||
|
||||
const text = outputInfo.text();
|
||||
truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, element);
|
||||
insertOutput(outputInfo.id, [text], ctx.settings.lineLimit, ctx.settings.outputScrolling, element);
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
@@ -191,12 +196,12 @@ function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boo
|
||||
}
|
||||
}
|
||||
|
||||
function renderText(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext<void> & { readonly settings: { readonly lineLimit: number } }): void {
|
||||
function renderText(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext<void> & { readonly settings: RenderOptions }): void {
|
||||
clearContainer(container);
|
||||
const contentNode = document.createElement('div');
|
||||
contentNode.classList.add('output-plaintext');
|
||||
const text = outputInfo.text();
|
||||
truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, contentNode);
|
||||
insertOutput(outputInfo.id, [text], ctx.settings.lineLimit, ctx.settings.outputScrolling, contentNode);
|
||||
container.appendChild(contentNode);
|
||||
}
|
||||
|
||||
@@ -205,26 +210,36 @@ export const activate: ActivationFunction<void> = (ctx) => {
|
||||
const htmlHooks = new Set<HtmlRenderingHook>();
|
||||
const jsHooks = new Set<JavaScriptRenderingHook>();
|
||||
|
||||
const latestContext = ctx as (RendererContext<void> & { readonly settings: { readonly lineLimit: number } });
|
||||
const latestContext = ctx as (RendererContext<void> & { readonly settings: RenderOptions });
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.output-plaintext,
|
||||
.output-stream,
|
||||
.traceback {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
line-height: var(--notebook-cell-output-line-height);
|
||||
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;
|
||||
}
|
||||
span.output-stream {
|
||||
display: inline-block;
|
||||
output-plaintext,
|
||||
.traceback {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.output > span.scrollable {
|
||||
overflow-y: scroll;
|
||||
max-height: var(--notebook-cell-output-max-height);
|
||||
border: var(--vscode-editorWidget-border);
|
||||
border-style: solid;
|
||||
padding-left: 4px;
|
||||
box-sizing: border-box;
|
||||
border-width: 1px;
|
||||
}
|
||||
.output-plaintext .code-bold,
|
||||
.output-stream .code-bold,
|
||||
|
||||
@@ -5,36 +5,35 @@
|
||||
|
||||
import { handleANSIOutput } from './ansi';
|
||||
|
||||
function generateViewMoreElement(outputId: string) {
|
||||
function generateViewMoreElement(outputId: string, adjustableSize: boolean) {
|
||||
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?%5B%22notebook.output.textLineLimit%22%5D`;
|
||||
|
||||
if (adjustableSize) {
|
||||
first.textContent = 'Output exceeds the ';
|
||||
const second = document.createElement('a');
|
||||
second.textContent = 'size limit';
|
||||
second.href = `command:workbench.action.openSettings?%5B%22notebook.output.textLineLimit%22%5D`;
|
||||
container.appendChild(first);
|
||||
container.appendChild(second);
|
||||
} else {
|
||||
first.textContent = 'Output exceeds the maximium size limit';
|
||||
container.appendChild(first);
|
||||
}
|
||||
|
||||
const third = document.createElement('span');
|
||||
third.textContent = '. Open the full output data';
|
||||
third.textContent = '. Open the full output data ';
|
||||
const forth = document.createElement('a');
|
||||
forth.textContent = ' in a text editor';
|
||||
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) {
|
||||
const buffer = outputs.join('\n').split(/\r\n|\r|\n/g);
|
||||
function truncatedArrayOfString(id: string, buffer: string[], linesLimit: number, container: HTMLElement) {
|
||||
const lineCount = buffer.length;
|
||||
|
||||
if (lineCount < linesLimit) {
|
||||
const spanElement = handleANSIOutput(buffer.slice(0, linesLimit).join('\n'));
|
||||
container.appendChild(spanElement);
|
||||
return;
|
||||
}
|
||||
|
||||
container.appendChild(generateViewMoreElement(id));
|
||||
container.appendChild(generateViewMoreElement(id, true));
|
||||
|
||||
const div = document.createElement('div');
|
||||
container.appendChild(div);
|
||||
@@ -49,3 +48,31 @@ export function truncatedArrayOfString(id: string, outputs: string[], linesLimit
|
||||
container.appendChild(div2);
|
||||
div2.appendChild(handleANSIOutput(buffer.slice(lineCount - 5).join('\n')));
|
||||
}
|
||||
|
||||
function scrollableArrayOfString(id: string, buffer: string[], container: HTMLElement) {
|
||||
container.classList.add('scrollable');
|
||||
|
||||
if (buffer.length > 5000) {
|
||||
container.appendChild(generateViewMoreElement(id, false));
|
||||
}
|
||||
const div = document.createElement('div');
|
||||
container.appendChild(div);
|
||||
div.appendChild(handleANSIOutput(buffer.slice(0, 5000).join('\n')));
|
||||
}
|
||||
|
||||
export function insertOutput(id: string, outputs: string[], linesLimit: number, scrollable: boolean, container: HTMLElement) {
|
||||
const buffer = outputs.join('\n').split(/\r\n|\r|\n/g);
|
||||
const lineCount = buffer.length;
|
||||
|
||||
if (lineCount < linesLimit) {
|
||||
const spanElement = handleANSIOutput(buffer.join('\n'));
|
||||
container.appendChild(spanElement);
|
||||
return;
|
||||
}
|
||||
|
||||
if (scrollable) {
|
||||
scrollableArrayOfString(id, buffer, container);
|
||||
} else {
|
||||
truncatedArrayOfString(id, buffer, linesLimit, container);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ import { NOTEBOOK_WEBVIEW_BOUNDARY } from 'vs/workbench/contrib/notebook/browser
|
||||
import { preloadsScriptStr } 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 { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellUri, INotebookRendererInfo, 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';
|
||||
@@ -99,6 +99,8 @@ interface BacklayerWebviewOptions {
|
||||
readonly outputFontFamily: string;
|
||||
readonly markupFontSize: number;
|
||||
readonly outputLineHeight: number;
|
||||
readonly outputScrolling: boolean;
|
||||
readonly outputLineLimit: number;
|
||||
}
|
||||
|
||||
const logChannelId = 'notebook.rendering';
|
||||
@@ -245,6 +247,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
|
||||
'notebook-markup-font-size': typeof this.options.markupFontSize === 'number' && this.options.markupFontSize > 0 ? `${this.options.markupFontSize}px` : `calc(${this.options.fontSize}px * 1.2)`,
|
||||
'notebook-cell-output-font-size': `${this.options.outputFontSize || this.options.fontSize}px`,
|
||||
'notebook-cell-output-line-height': `${this.options.outputLineHeight}px`,
|
||||
'notebook-cell-output-max-height': `${this.options.outputLineHeight * this.options.outputLineLimit}px`,
|
||||
'notebook-cell-output-font-family': this.options.outputFontFamily || this.options.fontFamily,
|
||||
'notebook-cell-markup-empty-content': nls.localize('notebook.emptyMarkdownPlaceholder', "Empty markdown cell, double click or press enter to edit."),
|
||||
'notebook-cell-renderer-not-found-error': nls.localize({
|
||||
@@ -257,13 +260,17 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
|
||||
private generateContent(coreDependencies: string, baseUrl: string) {
|
||||
const renderersData = this.getRendererData();
|
||||
const preloadsData = this.getStaticPreloadsData();
|
||||
const renderOptions = {
|
||||
lineLimit: this.options.outputLineLimit,
|
||||
outputScrolling: this.options.outputScrolling
|
||||
};
|
||||
const preloadScript = preloadsScriptStr(
|
||||
this.options,
|
||||
{ dragAndDropEnabled: this.options.dragAndDropEnabled },
|
||||
renderOptions,
|
||||
renderersData,
|
||||
preloadsData,
|
||||
this.workspaceTrustManagementService.isWorkspaceTrusted(),
|
||||
this.configurationService.getValue<number>(NotebookSetting.textOutputLineLimit) ?? 30,
|
||||
this.nonce);
|
||||
|
||||
const enableCsp = this.configurationService.getValue('notebook.experimental.enableCsp');
|
||||
|
||||
@@ -64,14 +64,19 @@ export interface PreloadOptions {
|
||||
dragAndDropEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface RenderOptions {
|
||||
readonly lineLimit: number;
|
||||
readonly outputScrolling: boolean;
|
||||
}
|
||||
|
||||
interface PreloadContext {
|
||||
readonly nonce: string;
|
||||
readonly style: PreloadStyles;
|
||||
readonly options: PreloadOptions;
|
||||
readonly renderOptions: RenderOptions;
|
||||
readonly rendererData: readonly webviewMessages.RendererMetadata[];
|
||||
readonly staticPreloadsData: readonly webviewMessages.StaticPreloadMetadata[];
|
||||
readonly isWorkspaceTrusted: boolean;
|
||||
readonly lineLimit: number;
|
||||
}
|
||||
|
||||
declare function __import(path: string): Promise<any>;
|
||||
@@ -82,7 +87,8 @@ async function webviewPreloads(ctx: PreloadContext) {
|
||||
|
||||
let currentOptions = ctx.options;
|
||||
let isWorkspaceTrusted = ctx.isWorkspaceTrusted;
|
||||
const lineLimit = ctx.lineLimit;
|
||||
const lineLimit = ctx.renderOptions.lineLimit;
|
||||
const outputScrolling = ctx.renderOptions.outputScrolling;
|
||||
|
||||
const acquireVsCodeApi = globalThis.acquireVsCodeApi;
|
||||
const vscode = acquireVsCodeApi();
|
||||
@@ -211,7 +217,7 @@ async function webviewPreloads(ctx: PreloadContext) {
|
||||
}
|
||||
|
||||
interface RendererContext extends rendererApi.RendererContext<unknown> {
|
||||
readonly settings: { readonly lineLimit: number };
|
||||
readonly settings: RenderOptions;
|
||||
}
|
||||
|
||||
interface RendererModule {
|
||||
@@ -1384,6 +1390,7 @@ async function webviewPreloads(ctx: PreloadContext) {
|
||||
},
|
||||
settings: {
|
||||
get lineLimit() { return lineLimit; },
|
||||
get outputScrolling() { return outputScrolling; },
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2473,14 +2480,14 @@ async function webviewPreloads(ctx: PreloadContext) {
|
||||
}();
|
||||
}
|
||||
|
||||
export function preloadsScriptStr(styleValues: PreloadStyles, options: PreloadOptions, renderers: readonly webviewMessages.RendererMetadata[], preloads: readonly webviewMessages.StaticPreloadMetadata[], isWorkspaceTrusted: boolean, lineLimit: number, nonce: string) {
|
||||
export function preloadsScriptStr(styleValues: PreloadStyles, options: PreloadOptions, renderOptions: RenderOptions, renderers: readonly webviewMessages.RendererMetadata[], preloads: readonly webviewMessages.StaticPreloadMetadata[], isWorkspaceTrusted: boolean, nonce: string) {
|
||||
const ctx: PreloadContext = {
|
||||
style: styleValues,
|
||||
options,
|
||||
renderOptions,
|
||||
rendererData: renderers,
|
||||
staticPreloadsData: preloads,
|
||||
isWorkspaceTrusted,
|
||||
lineLimit,
|
||||
nonce,
|
||||
};
|
||||
// TS will try compiling `import()` in webviewPreloads, so use a helper function instead
|
||||
|
||||
@@ -926,7 +926,8 @@ export const NotebookSetting = {
|
||||
outputLineHeight: 'notebook.outputLineHeight',
|
||||
outputFontSize: 'notebook.outputFontSize',
|
||||
outputFontFamily: 'notebook.outputFontFamily',
|
||||
kernelPickerType: 'notebook.kernelPicker.type'
|
||||
kernelPickerType: 'notebook.kernelPicker.type',
|
||||
outputScrolling: 'notebook.experimental.outputScrolling',
|
||||
} as const;
|
||||
|
||||
export const enum CellStatusbarAlignment {
|
||||
|
||||
@@ -70,6 +70,8 @@ export interface NotebookLayoutConfiguration {
|
||||
editorOptionsCustomizations: any | undefined;
|
||||
focusIndicatorGap: number;
|
||||
interactiveWindowCollapseCodeCells: InteractiveWindowCollapseCodeCells;
|
||||
outputScrolling: boolean;
|
||||
outputLineLimit: number;
|
||||
}
|
||||
|
||||
export interface NotebookOptionsChangeEvent {
|
||||
@@ -147,6 +149,8 @@ export class NotebookOptions extends Disposable {
|
||||
const editorOptionsCustomizations = this.configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations);
|
||||
const interactiveWindowCollapseCodeCells: InteractiveWindowCollapseCodeCells = this.configurationService.getValue(NotebookSetting.interactiveWindowCollapseCodeCells);
|
||||
const outputLineHeight = this._computeOutputLineHeight();
|
||||
const outputScrolling = this.configurationService.getValue<boolean>(NotebookSetting.outputScrolling);
|
||||
const outputLineLimit = this.configurationService.getValue<number>(NotebookSetting.textOutputLineLimit) ?? 30;
|
||||
|
||||
this._layoutConfiguration = {
|
||||
...(compactView ? compactConfigConstants : defaultConfigConstants),
|
||||
@@ -183,7 +187,9 @@ export class NotebookOptions extends Disposable {
|
||||
editorOptionsCustomizations,
|
||||
focusIndicatorGap: 3,
|
||||
interactiveWindowCollapseCodeCells,
|
||||
markdownFoldHintHeight: 22
|
||||
markdownFoldHintHeight: 22,
|
||||
outputScrolling: outputScrolling,
|
||||
outputLineLimit: outputLineLimit
|
||||
};
|
||||
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
@@ -566,6 +572,8 @@ export class NotebookOptions extends Disposable {
|
||||
outputFontFamily: this._layoutConfiguration.outputFontFamily,
|
||||
markupFontSize: this._layoutConfiguration.markupFontSize,
|
||||
outputLineHeight: this._layoutConfiguration.outputLineHeight,
|
||||
outputScrolling: this._layoutConfiguration.outputScrolling,
|
||||
outputLineLimit: this._layoutConfiguration.outputLineLimit,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -584,6 +592,8 @@ export class NotebookOptions extends Disposable {
|
||||
outputFontFamily: this._layoutConfiguration.outputFontFamily,
|
||||
markupFontSize: this._layoutConfiguration.markupFontSize,
|
||||
outputLineHeight: this._layoutConfiguration.outputLineHeight,
|
||||
outputScrolling: this._layoutConfiguration.outputScrolling,
|
||||
outputLineLimit: this._layoutConfiguration.outputLineLimit,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user