Skip to content

Commit 5b78562

Browse files
committed
truncate long outputs
1 parent 77a8acf commit 5b78562

File tree

4 files changed

+95
-19
lines changed

4 files changed

+95
-19
lines changed

extensions/notebook-renderers/src/index.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import type { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer';
7-
import { appendOutput, createOutputContent, scrollableClass } from './textHelper';
8-
import { HtmlRenderingHook, IDisposable, IRichRenderContext, JavaScriptRenderingHook, RenderOptions } from './rendererTypes';
7+
import { appendScrollableOutput, createOutputContent, scrollableClass } from './textHelper';
8+
import { HtmlRenderingHook, IDisposable, IRichRenderContext, JavaScriptRenderingHook, OutputWithAppend, RenderOptions } from './rendererTypes';
99
import { ttPolicy } from './htmlHelper';
1010

1111
function clearContainer(container: HTMLElement) {
@@ -265,10 +265,6 @@ function scrollingEnabled(output: OutputItem, options: RenderOptions) {
265265
metadata.scrollable : options.outputScrolling;
266266
}
267267

268-
interface OutputWithAppend extends OutputItem {
269-
appendedText?(): string | undefined;
270-
}
271-
272268
// div.cell_container
273269
// div.output_container
274270
// div.output.output-stream <-- outputElement parameter
@@ -302,7 +298,7 @@ function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement,
302298
if (existingContent && contentParent) {
303299
// appending output only in scrollable ouputs currently
304300
if (appendedText && outputScrolling) {
305-
appendOutput(existingContent, appendedText, false);
301+
appendScrollableOutput(existingContent, outputInfo.id, appendedText, outputInfo.text(), false);
306302
}
307303
else {
308304
const newContent = createContent(outputInfo, ctx, outputScrolling, error);

extensions/notebook-renderers/src/rendererTypes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ export interface RenderOptions {
3535
}
3636

3737
export type IRichRenderContext = RendererContext<void> & { readonly settings: RenderOptions; readonly onDidChangeSettings: Event<RenderOptions> };
38+
39+
export interface OutputWithAppend extends OutputItem {
40+
appendedText?(): string | undefined;
41+
}

extensions/notebook-renderers/src/test/notebookRenderer.test.ts

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
import * as assert from 'assert';
77
import { activate } from '..';
8-
import { OutputItem, RendererApi } from 'vscode-notebook-renderer';
9-
import { IDisposable, IRichRenderContext, RenderOptions } from '../rendererTypes';
8+
import { RendererApi } from 'vscode-notebook-renderer';
9+
import { IDisposable, IRichRenderContext, OutputWithAppend, RenderOptions } from '../rendererTypes';
1010
import { JSDOM } from "jsdom";
1111

1212
const dom = new JSDOM();
@@ -116,10 +116,13 @@ suite('Notebook builtin output renderer', () => {
116116
}
117117
}
118118

119-
function createOutputItem(text: string, mime: string, id: string = '123'): OutputItem {
119+
function createOutputItem(text: string, mime: string, id: string = '123', appendedText?: string): OutputWithAppend {
120120
return {
121121
id: id,
122122
mime: mime,
123+
appendedText() {
124+
return appendedText;
125+
},
123126
text() {
124127
return text;
125128
},
@@ -177,9 +180,9 @@ suite('Notebook builtin output renderer', () => {
177180
assert.ok(renderer, 'Renderer not created');
178181

179182
const outputElement = new OutputHtml().getFirstOuputElement();
180-
const outputItem = createOutputItem('content', 'text/plain');
183+
const outputItem = createOutputItem('content', mimeType);
181184
await renderer!.renderOutputItem(outputItem, outputElement);
182-
const outputItem2 = createOutputItem('replaced content', 'text/plain');
185+
const outputItem2 = createOutputItem('replaced content', mimeType);
183186
await renderer!.renderOutputItem(outputItem2, outputElement);
184187

185188
const inserted = outputElement.firstChild as HTMLElement;
@@ -189,6 +192,59 @@ suite('Notebook builtin output renderer', () => {
189192

190193
});
191194

195+
test('Append streaming output', async () => {
196+
const context = createContext({ outputWordWrap: false, outputScrolling: false });
197+
const renderer = await activate(context);
198+
assert.ok(renderer, 'Renderer not created');
199+
200+
const outputElement = new OutputHtml().getFirstOuputElement();
201+
const outputItem = createOutputItem('content', stdoutMimeType, '123', 'ignoredAppend');
202+
await renderer!.renderOutputItem(outputItem, outputElement);
203+
const outputItem2 = createOutputItem('content\nappended', stdoutMimeType, '\nappended');
204+
await renderer!.renderOutputItem(outputItem2, outputElement);
205+
206+
const inserted = outputElement.firstChild as HTMLElement;
207+
assert.ok(inserted.innerHTML.indexOf('>content</') !== -1, `Previous content should still exist: ${outputElement.innerHTML}`);
208+
assert.ok(inserted.innerHTML.indexOf('ignoredAppend') === -1, `Append value should not be used on first render: ${outputElement.innerHTML}`);
209+
assert.ok(inserted.innerHTML.indexOf('>appended</') !== -1, `Content was not appended to output element: ${outputElement.innerHTML}`);
210+
assert.ok(inserted.innerHTML.indexOf('>content</') === inserted.innerHTML.lastIndexOf('>content</'), `Original content should not be duplicated: ${outputElement.innerHTML}`);
211+
});
212+
213+
test('Append large streaming outputs', async () => {
214+
const context = createContext({ outputWordWrap: false, outputScrolling: false });
215+
const renderer = await activate(context);
216+
assert.ok(renderer, 'Renderer not created');
217+
218+
const outputElement = new OutputHtml().getFirstOuputElement();
219+
const lotsOfLines = new Array(4998).fill('line').join('\n') + 'endOfInitialContent';
220+
const firstOuput = lotsOfLines + 'expected1';
221+
const outputItem = createOutputItem(firstOuput, stdoutMimeType, '123');
222+
await renderer!.renderOutputItem(outputItem, outputElement);
223+
const appended = '\n' + lotsOfLines + 'expectedAppend';
224+
const outputItem2 = createOutputItem(firstOuput + appended, stdoutMimeType, appended);
225+
await renderer!.renderOutputItem(outputItem2, outputElement);
226+
227+
const inserted = outputElement.firstChild as HTMLElement;
228+
assert.ok(inserted.innerHTML.indexOf('>expected1</') !== -1, `Last bit of previous content should still exist: ${outputElement.innerHTML}`);
229+
assert.ok(inserted.innerHTML.indexOf('>expectedAppend</') !== -1, `Content was not appended to output element: ${outputElement.innerHTML}`);
230+
});
231+
232+
test('Streaming outputs larger than the line limit are truncated', async () => {
233+
const context = createContext({ outputWordWrap: false, outputScrolling: false });
234+
const renderer = await activate(context);
235+
assert.ok(renderer, 'Renderer not created');
236+
237+
const outputElement = new OutputHtml().getFirstOuputElement();
238+
const lotsOfLines = new Array(11000).fill('line').join('\n') + 'endOfInitialContent';
239+
const firstOuput = 'shouldBeTruncated' + lotsOfLines + 'expected1';
240+
const outputItem = createOutputItem(firstOuput, stdoutMimeType, '123');
241+
await renderer!.renderOutputItem(outputItem, outputElement);
242+
243+
const inserted = outputElement.firstChild as HTMLElement;
244+
assert.ok(inserted.innerHTML.indexOf('>endOfInitialContent</') !== -1, `Last bit of content should exist: ${outputElement.innerHTML}`);
245+
assert.ok(inserted.innerHTML.indexOf('>shouldBeTruncated</') === -1, `Beginning content should be truncated: ${outputElement.innerHTML}`);
246+
});
247+
192248
test(`Render with wordwrap and scrolling for error output`, async () => {
193249
const context = createContext({ outputWordWrap: true, outputScrolling: true });
194250
const renderer = await activate(context);

extensions/notebook-renderers/src/textHelper.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { handleANSIOutput } from './ansi';
7-
87
export const scrollableClass = 'scrollable';
98

9+
const softScrollableLineLimit = 5000;
10+
const hardScrollableLineLimit = 8000;
11+
1012
/**
1113
* Output is Truncated. View as a [scrollable element] or open in a [text editor]. Adjust cell output [settings...]
1214
*/
@@ -91,11 +93,11 @@ function truncatedArrayOfString(id: string, buffer: string[], linesLimit: number
9193

9294
function scrollableArrayOfString(id: string, buffer: string[], trustHtml: boolean) {
9395
const element = document.createElement('div');
94-
if (buffer.length > 5000) {
96+
if (buffer.length > softScrollableLineLimit) {
9597
element.appendChild(generateNestedViewAllElement(id));
9698
}
9799

98-
element.appendChild(handleANSIOutput(buffer.slice(-5000).join('\n'), trustHtml));
100+
element.appendChild(handleANSIOutput(buffer.slice(-1 * softScrollableLineLimit).join('\n'), trustHtml));
99101

100102
return element;
101103
}
@@ -111,8 +113,26 @@ export function createOutputContent(id: string, outputText: string, linesLimit:
111113
}
112114
}
113115

114-
export function appendOutput(element: HTMLElement, outputText: string, trustHtml: boolean) {
115-
const buffer = outputText.split(/\r\n|\r|\n/g);
116-
const newContent = handleANSIOutput(buffer.join('\n'), trustHtml);
117-
element.appendChild(newContent);
116+
const outputLengths: Record<string, number> = {};
117+
118+
export function appendScrollableOutput(element: HTMLElement, id: string, appended: string, fullText: string, trustHtml: boolean) {
119+
if (!outputLengths[id]) {
120+
outputLengths[id] = 0;
121+
}
122+
123+
const buffer = appended.split(/\r\n|\r|\n/g);
124+
const appendedLength = buffer.length + outputLengths[id];
125+
// Allow the output to grow to the hard limit then replace it with the last softLimit number of lines if it grows too large
126+
if (buffer.length + outputLengths[id] > hardScrollableLineLimit) {
127+
const fullBuffer = fullText.split(/\r\n|\r|\n/g);
128+
outputLengths[id] = Math.min(fullBuffer.length, softScrollableLineLimit);
129+
const newElement = scrollableArrayOfString(id, fullBuffer.slice(-1 * softScrollableLineLimit), trustHtml);
130+
newElement.setAttribute('output-item-id', id);
131+
element.replaceWith();
132+
}
133+
else {
134+
element.appendChild(handleANSIOutput(buffer.join('\n'), trustHtml));
135+
outputLengths[id] = appendedLength;
136+
}
118137
}
138+

0 commit comments

Comments
 (0)