Batch all code block updates together and emit event ASAP (#155629)

Fixes #154577: Batch all code block updates together and emit event ASAP (ref #152010)
This commit is contained in:
Alexandru Dima 2022-07-19 17:56:51 +02:00 committed by GitHub
parent 828993be96
commit ebbde05965
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 23 additions and 32 deletions

View file

@ -9,8 +9,6 @@ import { DomEmitter } from 'vs/base/browser/event';
import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { raceCancellation } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event';
import { IMarkdownString, escapeDoubleQuotes, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
@ -44,8 +42,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
const disposables = new DisposableStore();
let isDisposed = false;
const cts = disposables.add(new CancellationTokenSource());
const element = createElement(options);
const _uriMassage = function (part: string): string {
@ -96,11 +92,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
return uri.toString();
};
// signal to code-block render that the
// element has been created
let signalInnerHTML: () => void;
const withInnerHTML = new Promise<void>(c => signalInnerHTML = c);
const renderer = new marked.Renderer();
renderer.image = (href: string, title: string, text: string) => {
@ -146,24 +137,14 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
return `<p>${text}</p>`;
};
// Will collect [id, renderedElement] tuples
const codeBlocks: Promise<[string, HTMLElement]>[] = [];
if (options.codeBlockRenderer) {
renderer.code = (code, lang) => {
const value = options.codeBlockRenderer!(lang ?? '', code);
// when code-block rendering is async we return sync
// but update the node with the real result later.
const id = defaultGenerator.nextId();
raceCancellation(Promise.all([value, withInnerHTML]), cts.token).then(values => {
if (!isDisposed && values) {
const span = element.querySelector<HTMLDivElement>(`div[data-code="${id}"]`);
if (span) {
DOM.reset(span, values[0]);
}
options.asyncRenderCallback?.();
}
}).catch(() => {
// ignore
});
const value = options.codeBlockRenderer!(lang ?? '', code);
codeBlocks.push(value.then(element => [id, element]));
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
};
}
@ -277,8 +258,22 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
element.innerHTML = sanitizeRenderedMarkdown(markdown, markdownHtmlDoc.body.innerHTML) as unknown as string;
// signal that async code blocks can be now be inserted
signalInnerHTML!();
if (codeBlocks.length > 0) {
Promise.all(codeBlocks).then((tuples) => {
if (isDisposed) {
return;
}
const renderedElements = new Map(tuples);
const placeholderElements = element.querySelectorAll<HTMLDivElement>(`div[data-code]`);
for (const placeholderElement of placeholderElements) {
const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? '');
if (renderedElement) {
DOM.reset(placeholderElement, renderedElement);
}
}
options.asyncRenderCallback?.();
});
}
// signal size changes for image tags
if (options.asyncRenderCallback) {
@ -294,7 +289,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
element,
dispose: () => {
isDisposed = true;
cts.cancel();
disposables.dispose();
}
};

View file

@ -10,7 +10,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language';
import { onUnexpectedError } from 'vs/base/common/errors';
import { tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { DebounceEmitter } from 'vs/base/common/event';
import { Emitter } from 'vs/base/common/event';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
@ -38,10 +38,7 @@ export class MarkdownRenderer {
}
});
private readonly _onDidRenderAsync = new DebounceEmitter<void>({
delay: 50,
merge: arr => { }
});
private readonly _onDidRenderAsync = new Emitter<void>();
readonly onDidRenderAsync = this._onDidRenderAsync.event;
constructor(