mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-14 20:34:30 +01:00
simplify content renderer
This commit is contained in:
@@ -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 (<htmlContent.IHTMLContentElement[]>content).map((piece) => renderHtml(piece, actionCallback));
|
||||
} else if (content) {
|
||||
return [renderHtml(<htmlContent.IHTMLContentElement>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 '_':
|
||||
|
||||
@@ -116,9 +116,11 @@ suite("HtmlContent", () => {
|
||||
var callbackCalled = false;
|
||||
var result:HTMLElement = <any> renderHtml({
|
||||
formattedText: '[[action]]'
|
||||
}, (index) => {
|
||||
assert.strictEqual(index, 0);
|
||||
callbackCalled = true;
|
||||
}, {
|
||||
actionCallback(index) {
|
||||
assert.strictEqual(index, 0);
|
||||
callbackCalled = true;
|
||||
}
|
||||
});
|
||||
assert.strictEqual(result.innerHTML, '<a href="#">action</a>');
|
||||
|
||||
@@ -132,9 +134,11 @@ suite("HtmlContent", () => {
|
||||
var callbackCalled = false;
|
||||
var result:HTMLElement = <any> renderHtml({
|
||||
formattedText: '__**[[action]]**__'
|
||||
}, (index) => {
|
||||
assert.strictEqual(index, 0);
|
||||
callbackCalled = true;
|
||||
}, {
|
||||
actionCallback(index) {
|
||||
assert.strictEqual(index, 0);
|
||||
callbackCalled = true;
|
||||
}
|
||||
});
|
||||
assert.strictEqual(result.innerHTML, '<i><b><a href="#">action</a></b></i>');
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user