From 13eaadc104f332bd2f6bc8a365751846dcda50ea Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 21 Jan 2016 16:34:19 +0100 Subject: [PATCH] simplify content renderer --- src/vs/base/browser/htmlContentRenderer.ts | 127 +++++++++--------- src/vs/base/test/browser/htmlContent.test.ts | 16 ++- .../contrib/gotoError/browser/gotoError.ts | 4 +- .../hover/browser/modesContentHover.ts | 14 +- 4 files changed, 84 insertions(+), 77 deletions(-) diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index 0732b6752e8..a7de1cefb84 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -6,23 +6,14 @@ 'use strict'; import DOM = require('vs/base/browser/dom'); -import htmlContent = require('vs/base/common/htmlContent'); +import {IHTMLContentElement} from 'vs/base/common/htmlContent'; -/** - * Deal with different types of content. See @renderHtml - */ -export function renderHtml2(content:string, actionCallback?:(index:number, event:DOM.IMouseEvent)=>void):Node[]; -export function renderHtml2(content:htmlContent.IHTMLContentElement, actionCallback?:(index:number, event:DOM.IMouseEvent)=>void):Node[]; -export function renderHtml2(content:htmlContent.IHTMLContentElement[], actionCallback?:(index:number, event:DOM.IMouseEvent)=>void):Node[]; -export function renderHtml2(content:any, actionCallback?:(index:number, event:DOM.IMouseEvent)=>void):Node[] { - if (typeof content === 'string') { - return [document.createTextNode(content)]; - } else if (Array.isArray(content)) { - return (content).map((piece) => renderHtml(piece, actionCallback)); - } else if (content) { - return [renderHtml(content, actionCallback)]; - } - return []; + +export type RenderableContent = string | IHTMLContentElement | IHTMLContentElement[]; + +export interface RenderOptions { + actionCallback?: (index: number, event: DOM.IMouseEvent) => void; + codeBlockRenderer?: (modeId: string, value: string) => IHTMLContentElement; } /** @@ -31,39 +22,51 @@ export function renderHtml2(content:any, actionCallback?:(index:number, event:DO * @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 renderHtml(content:htmlContent.IHTMLContentElement, actionCallback?:(index:number, event:DOM.IMouseEvent)=>void, codeBlockRenderer?:(modeId:string, value:string) => htmlContent.IHTMLContentElement):Node { +export function renderHtml(content: RenderableContent, options: RenderOptions = {}): Node { + if (typeof content === 'string') { + return _renderHtml({ text: content }, options); + } else if (Array.isArray(content)) { + return _renderHtml({ children: content }, options); + } else if (content) { + return _renderHtml(content, options); + } +} - if(content.isText) { +function _renderHtml(content: IHTMLContentElement, options: RenderOptions = {}): Node { + + let {codeBlockRenderer, actionCallback} = options; + + if (content.isText) { return document.createTextNode(content.text); } var tagName = getSafeTagName(content.tagName) || 'div'; var element = document.createElement(tagName); - if(content.className) { + if (content.className) { element.className = content.className; } - if(content.text) { + if (content.text) { element.textContent = content.text; } - if(content.style) { + if (content.style) { element.setAttribute('style', content.style); } - if(content.customStyle) { + if (content.customStyle) { Object.keys(content.customStyle).forEach((key) => { element.style[key] = content.customStyle[key]; }); } if (content.code && codeBlockRenderer) { let child = codeBlockRenderer(content.code.language, content.code.value); - element.appendChild(renderHtml(child, actionCallback, codeBlockRenderer)); + element.appendChild(renderHtml(child, options)); } - if(content.children) { + if (content.children) { content.children.forEach((child) => { - element.appendChild(renderHtml(child, actionCallback, codeBlockRenderer)); + element.appendChild(renderHtml(child, options)); }); } - if(content.formattedText) { + if (content.formattedText) { renderFormattedText(element, parseFormattedText(content.formattedText), actionCallback); } @@ -110,29 +113,29 @@ function getSafeTagName(tagName: string): string { } class StringStream { - private source:string; - private index:number; + private source: string; + private index: number; - constructor(source:string) { + constructor(source: string) { this.source = source; this.index = 0; } - public eos():boolean { + public eos(): boolean { return this.index >= this.source.length; } - public next():string { + public next(): string { var next = this.peek(); this.advance(); return next; } - public peek():string { + public peek(): string { return this.source[this.index]; } - public advance():void { + public advance(): void { this.index++; } } @@ -150,24 +153,24 @@ enum FormatType { interface IFormatParseTree { type: FormatType; - content?:string; - index?:number; + content?: string; + index?: number; children?: IFormatParseTree[]; } -function renderFormattedText(element:Node, treeNode:IFormatParseTree, actionCallback?:(index:number, event:DOM.IMouseEvent)=>void) { +function renderFormattedText(element: Node, treeNode: IFormatParseTree, actionCallback?: (index: number, event: DOM.IMouseEvent) => void) { var child: Node; - if(treeNode.type === FormatType.Text) { + if (treeNode.type === FormatType.Text) { child = document.createTextNode(treeNode.content); } - else if(treeNode.type === FormatType.Bold) { + else if (treeNode.type === FormatType.Bold) { child = document.createElement('b'); } - else if(treeNode.type === FormatType.Italics) { + else if (treeNode.type === FormatType.Italics) { child = document.createElement('i'); } - else if(treeNode.type === FormatType.Action) { + else if (treeNode.type === FormatType.Action) { var a = document.createElement('a'); a.href = '#'; DOM.addStandardDisposableListener(a, 'click', (event) => { @@ -176,37 +179,37 @@ function renderFormattedText(element:Node, treeNode:IFormatParseTree, actionCall child = a; } - else if(treeNode.type === FormatType.NewLine) { + else if (treeNode.type === FormatType.NewLine) { child = document.createElement('br'); } - else if(treeNode.type === FormatType.Root) { + else if (treeNode.type === FormatType.Root) { child = element; } - if(element !== child) { + if (element !== child) { element.appendChild(child); } - if(Array.isArray(treeNode.children)) { + if (Array.isArray(treeNode.children)) { treeNode.children.forEach((nodeChild) => { renderFormattedText(child, nodeChild, actionCallback); }); } } -function parseFormattedText(content:string):IFormatParseTree { +function parseFormattedText(content: string): IFormatParseTree { - var root:IFormatParseTree = { + var root: IFormatParseTree = { type: FormatType.Root, children: [] }; var actionItemIndex = 0; var current = root; - var stack:IFormatParseTree[] = []; + var stack: IFormatParseTree[] = []; var stream = new StringStream(content); - while(!stream.eos()) { + while (!stream.eos()) { var next = stream.next(); var isEscapedFormatType = (next === '\\' && formatTagType(stream.peek()) !== FormatType.Invalid); @@ -214,23 +217,23 @@ function parseFormattedText(content:string):IFormatParseTree { next = stream.next(); // unread the backslash if it escapes a format tag type } - if(!isEscapedFormatType && isFormatTag(next) && next === stream.peek()) { + if (!isEscapedFormatType && isFormatTag(next) && next === stream.peek()) { stream.advance(); - if(current.type === FormatType.Text) { + if (current.type === FormatType.Text) { current = stack.pop(); } var type = formatTagType(next); - if(current.type === type || (current.type === FormatType.Action && type === FormatType.ActionClose)) { + if (current.type === type || (current.type === FormatType.Action && type === FormatType.ActionClose)) { current = stack.pop(); } else { - var newCurrent:IFormatParseTree = { + var newCurrent: IFormatParseTree = { type: type, children: [] }; - if(type === FormatType.Action) { + if (type === FormatType.Action) { newCurrent.index = actionItemIndex; actionItemIndex++; } @@ -239,8 +242,8 @@ function parseFormattedText(content:string):IFormatParseTree { stack.push(current); current = newCurrent; } - } else if(next === '\n') { - if(current.type === FormatType.Text) { + } else if (next === '\n') { + if (current.type === FormatType.Text) { current = stack.pop(); } @@ -249,8 +252,8 @@ function parseFormattedText(content:string):IFormatParseTree { }); } else { - if(current.type !== FormatType.Text) { - var textCurrent:IFormatParseTree = { + if (current.type !== FormatType.Text) { + var textCurrent: IFormatParseTree = { type: FormatType.Text, content: next }; @@ -264,23 +267,23 @@ function parseFormattedText(content:string):IFormatParseTree { } } - if(current.type === FormatType.Text) { + if (current.type === FormatType.Text) { current = stack.pop(); } - if(stack.length) { + if (stack.length) { // incorrectly formatted string literal } return root; } -function isFormatTag(char:string):boolean { +function isFormatTag(char: string): boolean { return formatTagType(char) !== FormatType.Invalid; } -function formatTagType(char:string):FormatType { - switch(char) { +function formatTagType(char: string): FormatType { + switch (char) { case '*': return FormatType.Bold; case '_': diff --git a/src/vs/base/test/browser/htmlContent.test.ts b/src/vs/base/test/browser/htmlContent.test.ts index c473ed8504b..8171bfecedb 100644 --- a/src/vs/base/test/browser/htmlContent.test.ts +++ b/src/vs/base/test/browser/htmlContent.test.ts @@ -116,9 +116,11 @@ suite("HtmlContent", () => { var callbackCalled = false; var result:HTMLElement = renderHtml({ formattedText: '[[action]]' - }, (index) => { - assert.strictEqual(index, 0); - callbackCalled = true; + }, { + actionCallback(index) { + assert.strictEqual(index, 0); + callbackCalled = true; + } }); assert.strictEqual(result.innerHTML, 'action'); @@ -132,9 +134,11 @@ suite("HtmlContent", () => { var callbackCalled = false; var result:HTMLElement = renderHtml({ formattedText: '__**[[action]]**__' - }, (index) => { - assert.strictEqual(index, 0); - callbackCalled = true; + }, { + actionCallback(index) { + assert.strictEqual(index, 0); + callbackCalled = true; + } }); assert.strictEqual(result.innerHTML, 'action'); diff --git a/src/vs/editor/contrib/gotoError/browser/gotoError.ts b/src/vs/editor/contrib/gotoError/browser/gotoError.ts index 6268a98d582..7cab4ce0561 100644 --- a/src/vs/editor/contrib/gotoError/browser/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/browser/gotoError.ts @@ -255,9 +255,7 @@ class MarkerNavigationWidget extends ZoneWidget.ZoneWidget { } this._element.text(text); var htmlElem = this._element.getHTMLElement(); - HtmlContentRenderer.renderHtml2(marker.message).forEach((node) => { - htmlElem.appendChild(node); - }); + htmlElem.appendChild(HtmlContentRenderer.renderHtml(marker.message)); var mode = this.editor.getModel().getMode(); this._quickFixSection.hide(); diff --git a/src/vs/editor/contrib/hover/browser/modesContentHover.ts b/src/vs/editor/contrib/hover/browser/modesContentHover.ts index e0e92c72331..7b6d9a40b37 100644 --- a/src/vs/editor/contrib/hover/browser/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/browser/modesContentHover.ts @@ -236,13 +236,15 @@ export class ModesContentHoverWidget extends HoverWidget.ContentHoverWidget { if(msg.htmlContent && msg.htmlContent.length > 0) { msg.htmlContent.forEach((content) => { - container.appendChild(renderHtml(content, undefined, (modeId, value) => { - let mode: Modes.IMode; - let model = this._editor.getModel(); - if (!model.isDisposed()) { - mode = model.getMode(); + container.appendChild(renderHtml(content, { + codeBlockRenderer: (modeId, value) => { + let mode: Modes.IMode; + let model = this._editor.getModel(); + if (!model.isDisposed()) { + mode = model.getMode(); + } + return tokenizeToHtmlContent(value, model.getMode()); } - return tokenizeToHtmlContent(value, model.getMode()); })); }); } else {