diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index a49e4411274..7087efd5e66 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -47,7 +47,7 @@ export function renderFormattedText(formattedText: string, options: RenderOption * @param content a html element description * @param actionCallback a callback function for any action links in the string. Argument is the zero-based index of the clicked action. */ -export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions = {}): Node { +export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions = {}): HTMLElement { const element = createElement(options); const { codeBlockRenderer, actionCallback } = options; diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index c895d1c9888..c973826bfa7 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -23,6 +23,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorHoverHighlight, editorHoverBackground, editorHoverBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { MarkdownRenderer } from 'vs/editor/contrib/markdown/browser/markdownRenderer'; @editorContribution export class ModesHoverController implements editorCommon.IEditorContribution { @@ -64,9 +65,9 @@ export class ModesHoverController implements editorCommon.IEditorContribution { this._hideWidgets(); } })); - - this._contentWidget = new ModesContentHoverWidget(editor, openerService, modeService); - this._glyphWidget = new ModesGlyphHoverWidget(editor, openerService, modeService); + const renderer = new MarkdownRenderer(editor, modeService, openerService); + this._contentWidget = new ModesContentHoverWidget(editor, renderer); + this._glyphWidget = new ModesGlyphHoverWidget(editor, renderer); } } diff --git a/src/vs/editor/contrib/hover/browser/modesContentHover.ts b/src/vs/editor/contrib/hover/browser/modesContentHover.ts index d181229ba08..9a790a07550 100644 --- a/src/vs/editor/contrib/hover/browser/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/browser/modesContentHover.ts @@ -5,22 +5,17 @@ 'use strict'; import * as nls from 'vs/nls'; -import URI from 'vs/base/common/uri'; -import { onUnexpectedError } from 'vs/base/common/errors'; import * as dom from 'vs/base/browser/dom'; import { TPromise } from 'vs/base/common/winjs.base'; -import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; -import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; -import { IModeService } from 'vs/editor/common/services/modeService'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { HoverProviderRegistry, Hover, IColor, IColorFormatter } from 'vs/editor/common/modes'; -import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { getHover } from '../common/hover'; import { HoverOperation, IHoverComputer } from './hoverOperation'; import { ContentHoverWidget } from './hoverWidgets'; import { IMarkdownString, MarkdownString, isEmptyMarkdownString } from 'vs/base/common/htmlContent'; +import { MarkdownRenderer } from 'vs/editor/contrib/markdown/browser/markdownRenderer'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/browser/colorPickerModel'; import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/browser/colorPickerWidget'; @@ -166,22 +161,20 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private _hoverOperation: HoverOperation; private _highlightDecorations: string[]; private _isChangingDecorations: boolean; - private _openerService: IOpenerService; - private _modeService: IModeService; + private _markdownRenderer: MarkdownRenderer; private _shouldFocus: boolean; private _colorPicker: ColorPickerWidget; private renderDisposable: IDisposable = EmptyDisposable; private toDispose: IDisposable[]; - constructor(editor: ICodeEditor, openerService: IOpenerService, modeService: IModeService) { + constructor(editor: ICodeEditor, markdownRenderner: MarkdownRenderer) { super(ModesContentHoverWidget.ID, editor); this._computer = new ModesContentComputer(this._editor); this._highlightDecorations = []; this._isChangingDecorations = false; - this._openerService = openerService || NullOpenerService; - this._modeService = modeService; + this._markdownRenderer = markdownRenderner; this._hoverOperation = new HoverOperation( this._computer, @@ -312,24 +305,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { msg.contents .filter(contents => !isEmptyMarkdownString(contents)) .forEach(contents => { - const renderedContents = renderMarkdown(contents, { - actionCallback: (content) => { - this._openerService.open(URI.parse(content)).then(void 0, onUnexpectedError); - }, - codeBlockRenderer: (languageAlias, value): string | TPromise => { - // In markdown, - // it is possible that we stumble upon language aliases (e.g.js instead of javascript) - // it is possible no alias is given in which case we fall back to the current editor lang - const modeId = languageAlias - ? this._modeService.getModeIdForLanguageName(languageAlias) - : this._editor.getModel().getLanguageIdentifier().language; - - return this._modeService.getOrCreateMode(modeId).then(_ => { - return tokenizeToString(value, modeId); - }); - } - }); - + const renderedContents = this._markdownRenderer.render(contents); fragment.appendChild($('div.hover-row', null, renderedContents)); }); } else { diff --git a/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts b/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts index 75eeb5c7504..522abe7a08e 100644 --- a/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts @@ -8,13 +8,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { HoverOperation, IHoverComputer } from './hoverOperation'; import { GlyphHoverWidget } from './hoverWidgets'; import { $ } from 'vs/base/browser/dom'; -import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; -import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; -import URI from 'vs/base/common/uri'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; +import { MarkdownRenderer } from 'vs/editor/contrib/markdown/browser/markdownRenderer'; import { IMarkdownString, isEmptyMarkdownString } from 'vs/base/common/htmlContent'; export interface IHoverMessage { @@ -94,16 +88,16 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { private _messages: IHoverMessage[]; private _lastLineNumber: number; + private _markdownRenderer: MarkdownRenderer; private _computer: MarginComputer; private _hoverOperation: HoverOperation; - constructor(editor: ICodeEditor, private openerService: IOpenerService, private modeService: IModeService) { + constructor(editor: ICodeEditor, markdownRenderer: MarkdownRenderer) { super(ModesGlyphHoverWidget.ID, editor); - this.openerService = openerService || NullOpenerService; - this._lastLineNumber = -1; + this._markdownRenderer = markdownRenderer; this._computer = new MarginComputer(this._editor); this._hoverOperation = new HoverOperation( @@ -166,17 +160,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { const fragment = document.createDocumentFragment(); messages.forEach((msg) => { - const renderedContents = renderMarkdown(msg.value, { - actionCallback: content => this.openerService.open(URI.parse(content)).then(undefined, onUnexpectedError), - codeBlockRenderer: (languageAlias, value): string | TPromise => { - // In markdown, it is possible that we stumble upon language aliases (e.g. js instead of javascript) - const modeId = this.modeService.getModeIdForLanguageName(languageAlias); - return this.modeService.getOrCreateMode(modeId).then(_ => { - return tokenizeToString(value, modeId); - }); - } - }); - + const renderedContents = this._markdownRenderer.render(msg.value); fragment.appendChild($('div.hover-row', null, renderedContents)); }); diff --git a/src/vs/editor/contrib/markdown/browser/markdownRenderer.ts b/src/vs/editor/contrib/markdown/browser/markdownRenderer.ts new file mode 100644 index 00000000000..fe7f3ad7c2b --- /dev/null +++ b/src/vs/editor/contrib/markdown/browser/markdownRenderer.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { renderMarkdown, RenderOptions } from 'vs/base/browser/htmlContentRenderer'; +import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import URI from 'vs/base/common/uri'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { optional } from 'vs/platform/instantiation/common/instantiation'; + +export class MarkdownRenderer { + + private readonly _options: RenderOptions; + + constructor( + editor: ICodeEditor, + @IModeService private readonly _modeService: IModeService, + @optional(IOpenerService) private readonly _openerService: IOpenerService = NullOpenerService, + ) { + this._options = { + actionCallback: (content) => { + this._openerService.open(URI.parse(content)).then(void 0, onUnexpectedError); + }, + codeBlockRenderer: (languageAlias, value): string | TPromise => { + // In markdown, + // it is possible that we stumble upon language aliases (e.g.js instead of javascript) + // it is possible no alias is given in which case we fall back to the current editor lang + const modeId = languageAlias + ? this._modeService.getModeIdForLanguageName(languageAlias) + : editor.getModel().getLanguageIdentifier().language; + + return this._modeService.getOrCreateMode(modeId).then(_ => { + return tokenizeToString(value, modeId); + }); + } + }; + } + + render(markdown: IMarkdownString, options?: RenderOptions): HTMLElement { + if (options) { + return renderMarkdown(markdown, { ...options, ...this._options }); + } else { + return renderMarkdown(markdown, this._options); + } + } +}