diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 98ff488bd82e0a827eb5e592c1369c7adfe9bf46..173bf1baccec0fab3cf93d23fcb06188c2c86be2 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -25,13 +25,16 @@ export interface MarkedOptions extends marked.MarkedOptions { } export interface MarkdownRenderOptions extends FormattedTextRenderOptions { - codeBlockRenderer?: (modeId: string, value: string) => Promise; + codeBlockRenderer?: (modeId: string, value: string) => Promise; codeBlockRenderCallback?: () => void; baseUrl?: URI; } /** - * Create html nodes for the given content element. + * Low-level way create a html element from a markdown string. + * + * **Note** that for most cases you should be using [`MarkdownRenderer`](./src/vs/editor/browser/core/markdownRenderer.ts) + * which comes with support for pretty code block rendering and which uses the default way of handling links. */ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: MarkedOptions = {}): HTMLElement { const element = createElement(options); @@ -158,12 +161,11 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende // but update the node with the real result later. const id = defaultGenerator.nextId(); const promise = Promise.all([value, withInnerHTML]).then(values => { - const strValue = values[0]; - const span = element.querySelector(`div[data-code="${id}"]`); + const span = element.querySelector(`div[data-code="${id}"]`); if (span) { - span.innerHTML = strValue; + DOM.reset(span, values[0]); } - }).catch(err => { + }).catch(_err => { // ignore }); diff --git a/src/vs/editor/contrib/markdown/markdownRenderer.ts b/src/vs/editor/browser/core/markdownRenderer.ts similarity index 68% rename from src/vs/editor/contrib/markdown/markdownRenderer.ts rename to src/vs/editor/browser/core/markdownRenderer.ts index 000218829a79db90c487f3ac8603755805d04316..a518ec550010e56177598bc829ab6b0263e53030 100644 --- a/src/vs/editor/contrib/markdown/markdownRenderer.ts +++ b/src/vs/editor/browser/core/markdownRenderer.ts @@ -12,7 +12,7 @@ import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Emitter } from 'vs/base/common/event'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { TokenizationRegistry } from 'vs/editor/common/modes'; +import { ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/modes'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { URI } from 'vs/base/common/uri'; @@ -22,11 +22,22 @@ export interface IMarkdownRenderResult extends IDisposable { export interface IMarkdownRendererOptions { editor?: ICodeEditor; - baseUrl?: URI + baseUrl?: URI; + codeBlockFontFamily?: string; } +/** + * Markdown renderer that can render codeblocks with the editor mechanics. This + * renderer should always be preferred. + */ export class MarkdownRenderer { + private static _ttpTokenizer = window.trustedTypes?.createPolicy('tokenizeToString', { + createHTML(value: string, tokenizer: ITokenizationSupport | undefined) { + return tokenizeToString(value, tokenizer); + } + }); + private readonly _onDidRenderCodeBlock = new Emitter(); readonly onDidRenderCodeBlock = this._onDidRenderCodeBlock.event; @@ -47,7 +58,7 @@ export class MarkdownRenderer { if (!markdown) { element = document.createElement('span'); } else { - element = renderMarkdown(markdown, this._getOptions(disposeables), markedOptions); + element = renderMarkdown(markdown, this._getRenderOptions(disposeables), markedOptions); } return { @@ -56,7 +67,7 @@ export class MarkdownRenderer { }; } - protected _getOptions(disposeables: DisposableStore): MarkdownRenderOptions { + protected _getRenderOptions(disposeables: DisposableStore): MarkdownRenderOptions { return { baseUrl: this._options.baseUrl, codeBlockRenderer: async (languageAlias, value) => { @@ -74,16 +85,27 @@ export class MarkdownRenderer { } this._modeService.triggerMode(modeId); const tokenization = await TokenizationRegistry.getPromise(modeId) ?? undefined; - const code = tokenizeToString(value, tokenization); - return this._options.editor - ? `${code}` - : `${code}`; + + const element = document.createElement('span'); + + element.innerHTML = MarkdownRenderer._ttpTokenizer + ? MarkdownRenderer._ttpTokenizer.createHTML(value, tokenization) as unknown as string + : tokenizeToString(value, tokenization); + + // use "good" font + let fontFamily = this._options.codeBlockFontFamily; + if (this._options.editor) { + fontFamily = this._options.editor.getOption(EditorOption.fontInfo).fontFamily; + } + if (fontFamily) { + element.style.fontFamily = fontFamily; + } + + return element; }, codeBlockRenderCallback: () => this._onDidRenderCodeBlock.fire(), actionHandler: { - callback: (content) => { - this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError); - }, + callback: (content) => this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError), disposeables } }; diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 8e25b8447211afd21fc84ccfcd2ec78558d6d5b3..2cb7e7938c53b580f93b3e1ea61a532309ca752f 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -21,7 +21,7 @@ import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/colorPickerWidg import { getHover } from 'vs/editor/contrib/hover/getHover'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/hoverOperation'; import { ContentHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { coalesce, isNonEmptyArray, asArray } from 'vs/base/common/arrays'; import { IMarker, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; diff --git a/src/vs/editor/contrib/hover/modesGlyphHover.ts b/src/vs/editor/contrib/hover/modesGlyphHover.ts index bc95a5223f65ac668028ce3325b534f4dca519c9..54d0c58c7321b829c3cba3b1e3882f6caa042361 100644 --- a/src/vs/editor/contrib/hover/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/modesGlyphHover.ts @@ -9,7 +9,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/hoverOperation'; import { GlyphHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; import { asArray } from 'vs/base/common/arrays'; diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 4ae4cda26e242f6c4070ad4538a52730b13b07c2..e2235a361b1188946ef077b7414f3662b1d08d15 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -14,7 +14,7 @@ import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentW import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import * as modes from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { Context } from 'vs/editor/contrib/parameterHints/provideSignatureHelp'; import * as nls from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 41a04895c603386f4f57ed80dda606593a88bd94..5d42225f42a33e629d827bfac77e031c85a89761 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -25,7 +25,7 @@ import { attachListStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor, editorWidgetBackground, listFocusBackground, activeContrastBorder, listHighlightForeground, editorForeground, editorWidgetBorder, focusBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { TimeoutTimer, CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async'; diff --git a/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts index b52731256598faf85f6e64e0570dc7040fbfbcee..8d1834e7f96259eb88a9b261c62ff9d86e1e170c 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts @@ -10,7 +10,7 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CompletionItem } from './suggest'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 0a1ab70e9d6847c3698341a2d491aecc19eee008..249fd9b106988a25bc4bde228c7fe463e9930731 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 6b31a942ed5d532ac72f01657d592b9545812c77..d9dac1fcc97ceb45931a16efff263293e6bb9778 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -22,7 +22,7 @@ import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { peekViewBorder } from 'vs/editor/contrib/peekView/peekView'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import * as nls from 'vs/nls'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index 51bedd5d88e1248a5d87f12a4175f738000d4d7c..617323bad98e880dde73549dee1b98c54361ee92 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -14,7 +14,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { URI } from 'vs/base/common/uri'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { handleANSIOutput } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform'; import { dirname } from 'vs/base/common/resources'; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts index 10bad0d63c0fb2fa9cc9c7831d9d6833052634a7..472f4a0a5b1b4e8a5f73e6b584a6cac8d805a13a 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -12,7 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; import { EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { CellFindMatch, ICellViewModel, MarkdownCellLayoutChangeEvent, MarkdownCellLayoutInfo, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; import { NotebookCellStateChangedEvent, NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index b6ece885ff122a4a769731350c0a5ae487342c3f..4ea7ba52c4f2636357373b1899ee96bf1bf32791 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -26,7 +26,7 @@ import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/mode import { CellKind, NotebookCellMetadata, INotebookSearchOptions, ICellRange, NotebookCellsChangeType, ICell, NotebookCellTextModelSplice, CellEditType, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { FoldingRegions } from 'vs/editor/contrib/folding/foldingRanges'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { dirname } from 'vs/base/common/resources'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { MultiModelEditStackElement, SingleModelEditStackElement } from 'vs/editor/common/model/editStack'; diff --git a/src/vs/workbench/services/hover/browser/hoverWidget.ts b/src/vs/workbench/services/hover/browser/hoverWidget.ts index 7d65e2fef387f057420d246ca2eae0b4b668c2cf..9ae8aed5d4f21536db25996082a8f6258393ee5e 100644 --- a/src/vs/workbench/services/hover/browser/hoverWidget.ts +++ b/src/vs/workbench/services/hover/browser/hoverWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore } from 'vs/base/common/lifecycle'; -import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; +import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { Event, Emitter } from 'vs/base/common/event'; import * as dom from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -18,6 +18,8 @@ import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { MarkdownString } from 'vs/base/common/htmlContent'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; const $ = dom.$; @@ -52,6 +54,7 @@ export class HoverWidget extends Widget { @IConfigurationService private readonly _configurationService: IConfigurationService, @IOpenerService private readonly _openerService: IOpenerService, @IWorkbenchLayoutService private readonly _workbenchLayoutService: IWorkbenchLayoutService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); @@ -81,22 +84,30 @@ export class HoverWidget extends Widget { const rowElement = $('div.hover-row.markdown-hover'); const contentsElement = $('div.hover-contents'); const markdown = typeof options.text === 'string' ? new MarkdownString().appendText(options.text) : options.text; - const markdownElement = renderMarkdown(markdown, { + + const myRenderOptions: MarkdownRenderOptions = { actionHandler: { callback: (content) => this._linkHandler(content), disposeables: this._messageListeners }, - codeBlockRenderer: async (_, value) => { - const fontFamily = this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; - return `${value.replace(/\n/g, '
')}
`; - }, codeBlockRenderCallback: () => { contentsElement.classList.add('code-hover-contents'); // This changes the dimensions of the hover so trigger a layout this._onRequestLayout.fire(); } + }; + + const mdRenderer = this._instantiationService.createInstance(class extends MarkdownRenderer { + _getRenderOptions(dispoables: DisposableStore) { + const value = super._getRenderOptions(dispoables); + return { ...value, ...myRenderOptions }; + } + }, { + codeBlockFontFamily: this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily }); - contentsElement.appendChild(markdownElement); + + const { element } = mdRenderer.render(markdown); + contentsElement.appendChild(element); rowElement.appendChild(contentsElement); this._hover.contentsDomNode.appendChild(rowElement);