From ebbde059650a8f1451a0bee0d34aa10062ac1943 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 19 Jul 2022 17:56:51 +0200 Subject: [PATCH] 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) --- src/vs/base/browser/markdownRenderer.ts | 48 ++++++++----------- .../browser/markdownRenderer.ts | 7 +-- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 2e809e2ad48..e43d27ac623 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -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(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 `

${text}

`; }; + // 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(`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 `
${escape(code)}
`; }; } @@ -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(`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(); } }; diff --git a/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts index f72e2293aa7..7220dbf1a4f 100644 --- a/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts @@ -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({ - delay: 50, - merge: arr => { } - }); + private readonly _onDidRenderAsync = new Emitter(); readonly onDidRenderAsync = this._onDidRenderAsync.event; constructor(