mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 21:55:38 +00:00
Merge pull request #183361 from microsoft/aamunger/outputStreaming
re-add events and apply settings each time the output ID is rendered
This commit is contained in:
commit
82c9994e6d
|
@ -277,43 +277,50 @@ function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error:
|
|||
outputElement.classList.add('output-stream');
|
||||
|
||||
const text = outputInfo.text();
|
||||
const content = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false);
|
||||
content.setAttribute('output-item-id', outputInfo.id);
|
||||
const newContent = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false);
|
||||
newContent.setAttribute('output-item-id', outputInfo.id);
|
||||
if (error) {
|
||||
content.classList.add('error');
|
||||
newContent.classList.add('error');
|
||||
}
|
||||
|
||||
const scrollTop = outputScrolling ? findScrolledHeight(outputElement) : undefined;
|
||||
|
||||
const existingContent = outputElement.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null;
|
||||
const previousOutputParent = getPreviousMatchingContentGroup(outputElement);
|
||||
|
||||
// If the previous output item for the same cell was also a stream, append this output to the previous
|
||||
const existingContentParent = getPreviousMatchingContentGroup(outputElement) || outputElement.querySelector('div');
|
||||
if (existingContentParent) {
|
||||
const existing = existingContentParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null;
|
||||
if (existing) {
|
||||
existing.replaceWith(content);
|
||||
while (content.nextSibling) {
|
||||
if (previousOutputParent) {
|
||||
if (existingContent) {
|
||||
existingContent.replaceWith(newContent);
|
||||
|
||||
} else {
|
||||
previousOutputParent.appendChild(newContent);
|
||||
}
|
||||
previousOutputParent.classList.toggle('scrollbar-visible', previousOutputParent.scrollHeight > previousOutputParent.clientHeight);
|
||||
previousOutputParent.scrollTop = scrollTop !== undefined ? scrollTop : previousOutputParent.scrollHeight;
|
||||
} else {
|
||||
let contentParent = existingContent?.parentElement;
|
||||
if (existingContent && contentParent) {
|
||||
existingContent.replaceWith(newContent);
|
||||
while (newContent.nextSibling) {
|
||||
// clear out any stale content if we had previously combined streaming outputs into this one
|
||||
content.nextSibling.remove();
|
||||
newContent.nextSibling.remove();
|
||||
}
|
||||
} else {
|
||||
existingContentParent.appendChild(content);
|
||||
}
|
||||
existingContentParent.classList.toggle('scrollbar-visible', existingContentParent.scrollHeight > existingContentParent.clientHeight);
|
||||
existingContentParent.scrollTop = scrollTop !== undefined ? scrollTop : existingContentParent.scrollHeight;
|
||||
} else {
|
||||
const contentParent = document.createElement('div');
|
||||
contentParent.appendChild(content);
|
||||
contentParent.classList.toggle('scrollable', outputScrolling);
|
||||
contentParent.classList.toggle('word-wrap', ctx.settings.outputWordWrap);
|
||||
disposableStore.push(ctx.onDidChangeSettings(e => {
|
||||
contentParent.classList.toggle('word-wrap', e.outputWordWrap);
|
||||
}));
|
||||
|
||||
|
||||
contentParent = document.createElement('div');
|
||||
contentParent.appendChild(newContent);
|
||||
while (outputElement.firstChild) {
|
||||
outputElement.removeChild(outputElement.firstChild);
|
||||
}
|
||||
outputElement.appendChild(contentParent);
|
||||
}
|
||||
|
||||
contentParent.classList.toggle('scrollable', outputScrolling);
|
||||
contentParent.classList.toggle('word-wrap', ctx.settings.outputWordWrap);
|
||||
disposableStore.push(ctx.onDidChangeSettings(e => {
|
||||
contentParent!.classList.toggle('word-wrap', e.outputWordWrap);
|
||||
}));
|
||||
|
||||
initializeScroll(contentParent, disposableStore, scrollTop);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import * as assert from 'assert';
|
||||
import { activate } from '..';
|
||||
import { OutputItem, RendererApi } from 'vscode-notebook-renderer';
|
||||
import { IRichRenderContext, RenderOptions } from '../rendererTypes';
|
||||
import { IDisposable, IRichRenderContext, RenderOptions } from '../rendererTypes';
|
||||
import { JSDOM } from "jsdom";
|
||||
|
||||
const dom = new JSDOM();
|
||||
|
@ -37,7 +37,15 @@ suite('Notebook builtin output renderer', () => {
|
|||
|
||||
type optionalRenderOptions = { [k in keyof RenderOptions]?: RenderOptions[k] };
|
||||
|
||||
type handler = (e: RenderOptions) => any;
|
||||
|
||||
const settingsChangedHandlers: handler[] = [];
|
||||
function fireSettingsChange(options: optionalRenderOptions) {
|
||||
settingsChangedHandlers.forEach((handler) => handler(options as RenderOptions));
|
||||
}
|
||||
|
||||
function createContext(settings?: optionalRenderOptions): IRichRenderContext {
|
||||
settingsChangedHandlers.length = 0;
|
||||
return {
|
||||
setState(_value: void) { },
|
||||
getState() { return undefined; },
|
||||
|
@ -48,9 +56,16 @@ suite('Notebook builtin output renderer', () => {
|
|||
lineLimit: 30,
|
||||
...settings
|
||||
} as RenderOptions,
|
||||
onDidChangeSettings<T>(_listener: (e: T) => any, _thisArgs?: any, _disposables?: any) {
|
||||
onDidChangeSettings(listener: handler, _thisArgs?: any, disposables?: IDisposable[]) {
|
||||
settingsChangedHandlers.push(listener);
|
||||
|
||||
const dispose = () => {
|
||||
settingsChangedHandlers.splice(settingsChangedHandlers.indexOf(listener), 1);
|
||||
};
|
||||
|
||||
disposables?.push({ dispose });
|
||||
return {
|
||||
dispose(): void { }
|
||||
dispose
|
||||
};
|
||||
},
|
||||
workspace: {
|
||||
|
@ -250,5 +265,33 @@ suite('Notebook builtin output renderer', () => {
|
|||
assert.ok(inserted.innerHTML.indexOf('>second stream content</') === -1, `Content was not cleared: ${outputElement.innerHTML}`);
|
||||
assert.ok(inserted.innerHTML.indexOf('>third stream content</') === -1, `Content was not cleared: ${outputElement.innerHTML}`);
|
||||
});
|
||||
|
||||
test(`Rendered output will wrap on settings change event`, async () => {
|
||||
const context = createContext({ outputWordWrap: false, outputScrolling: true });
|
||||
const renderer = await activate(context);
|
||||
assert.ok(renderer, 'Renderer not created');
|
||||
|
||||
const outputElement = new OutputHtml().getFirstOuputElement();
|
||||
const outputItem = createOutputItem('content', stdoutMimeType);
|
||||
await renderer!.renderOutputItem(outputItem, outputElement);
|
||||
fireSettingsChange({ outputWordWrap: true, outputScrolling: true });
|
||||
|
||||
const inserted = outputElement.firstChild as HTMLElement;
|
||||
assert.ok(inserted.classList.contains('word-wrap') && inserted.classList.contains('scrollable'),
|
||||
`output content classList should contain word-wrap and scrollable ${inserted.classList}`);
|
||||
});
|
||||
|
||||
test(`Settings event change listeners should not grow if output is re-rendered`, async () => {
|
||||
const context = createContext({ outputWordWrap: false });
|
||||
const renderer = await activate(context);
|
||||
assert.ok(renderer, 'Renderer not created');
|
||||
|
||||
const outputElement = new OutputHtml().getFirstOuputElement();
|
||||
await renderer!.renderOutputItem(createOutputItem('content', stdoutMimeType), outputElement);
|
||||
const handlerCount = settingsChangedHandlers.length;
|
||||
await renderer!.renderOutputItem(createOutputItem('content', stdoutMimeType), outputElement);
|
||||
|
||||
assert.equal(settingsChangedHandlers.length, handlerCount);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue