Skip to content

Commit

Permalink
Create HTML with dom.ts#$ function calls instead of string concatenation
Browse files Browse the repository at this point in the history
  • Loading branch information
annkamsk committed Aug 27, 2020
1 parent 3c80a82 commit 0cdb5b1
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 57 deletions.
12 changes: 12 additions & 0 deletions src/vs/base/browser/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,18 @@ export function prepend<T extends Node>(parent: HTMLElement, child: T): T {

const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;

export function reset<T extends Node>(parent: HTMLElement, ...children: Array<Node | string>) {
parent.innerText = '';
coalesce(children)
.forEach(child => {
if (child instanceof Node) {
parent.appendChild(child);
} else {
parent.appendChild(document.createTextNode(child as string));
}
});
}

export enum Namespace {
HTML = 'http://www.w3.org/1999/xhtml',
SVG = 'http://www.w3.org/2000/svg'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { SemanticTokenRule, TokenStyleData, TokenStyle } from 'vs/platform/theme
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { SEMANTIC_HIGHLIGHTING_SETTING_ID, IEditorSemanticHighlightingOptions } from 'vs/editor/common/services/modelServiceImpl';

const $ = dom.$;

class InspectEditorTokensController extends Disposable implements IEditorContribution {

public static readonly ID = 'editor.contrib.inspectEditorTokens';
Expand Down Expand Up @@ -151,23 +153,11 @@ function renderTokenText(tokenText: string): string {
let charCode = tokenText.charCodeAt(charIndex);
switch (charCode) {
case CharCode.Tab:
result += '&rarr;';
result += '\u2192'; // &rarr;
break;

case CharCode.Space:
result += '&middot;';
break;

case CharCode.LessThan:
result += '&lt;';
break;

case CharCode.GreaterThan:
result += '&gt;';
break;

case CharCode.Ampersand:
result += '&amp;';
result += '\u00B7'; // &middot;
break;

default:
Expand Down Expand Up @@ -246,8 +236,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
if (this._isDisposed) {
return;
}
let text = this._compute(grammar, semanticTokens, position);
this._domNode.innerHTML = text;
this._compute(grammar, semanticTokens, position);
this._domNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`;
this._editor.layoutContentWidget(this);
}, (err) => {
Expand All @@ -268,11 +257,12 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
return this._themeService.getColorTheme().semanticHighlighting;
}

private _compute(grammar: IGrammar | null, semanticTokens: SemanticTokensResult | null, position: Position): string {
private _compute(grammar: IGrammar | null, semanticTokens: SemanticTokensResult | null, position: Position) {
const textMateTokenInfo = grammar && this._getTokensAtPosition(grammar, position);
const semanticTokenInfo = semanticTokens && this._getSemanticTokenAtPosition(semanticTokens, position);
if (!textMateTokenInfo && !semanticTokenInfo) {
return 'No grammar or semantic tokens available.';
dom.reset(this._domNode, 'No grammar or semantic tokens available.');
return;
}

let tmMetadata = textMateTokenInfo?.metadata;
Expand All @@ -283,23 +273,38 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {

const tokenText = semTokenText || tmTokenText || '';

let result = '';
result += `<h2 class="tiw-token">${tokenText}<span class="tiw-token-length">(${tokenText.length} ${tokenText.length === 1 ? 'char' : 'chars'})</span></h2>`;
result += `<hr class="tiw-metadata-separator" style="clear:both"/>`;

result += `<table class="tiw-metadata-table"><tbody>`;
result += `<tr><td class="tiw-metadata-key">language</td><td class="tiw-metadata-value">${escape(tmMetadata?.languageIdentifier.language || '')}</td></tr>`;
result += `<tr><td class="tiw-metadata-key">standard token type</td><td class="tiw-metadata-value">${this._tokenTypeToString(tmMetadata?.tokenType || StandardTokenType.Other)}</td></tr>`;

result += this._formatMetadata(semMetadata, tmMetadata);
result += `</tbody></table>`;
dom.reset(this._domNode,
$('h2.tiw-token', undefined,
$('span.tiw-token-length', undefined, `${tokenText.length} ${tokenText.length === 1 ? 'char' : 'chars'}`)));
dom.append(this._domNode, $('hr.tiw-metadata-separator', { 'style': 'clear:both' }));
dom.append(this._domNode, $('table.tiw-metadata-table', undefined,
$('tbody', undefined,
$('tr', undefined,
$('td.tiw-metadata-key', undefined, 'language'),
$('td.tiw-metadata-value', undefined, tmMetadata?.languageIdentifier.language || '')
),
$('tr', undefined,
$('td.tiw-metadata-key', undefined, 'standard token type' as string),
$('td.tiw-metadata-value', undefined, this._tokenTypeToString(tmMetadata?.tokenType || StandardTokenType.Other))
),
...this._formatMetadata(semMetadata, tmMetadata)
)
));

if (semanticTokenInfo) {
result += `<hr class="tiw-metadata-separator"/>`;
result += `<table class="tiw-metadata-table"><tbody>`;
result += `<tr><td class="tiw-metadata-key">semantic token type</td><td class="tiw-metadata-value">${semanticTokenInfo.type}</td></tr>`;
dom.append(this._domNode, $('hr.tiw-metadata-separator'));
const table = dom.append(this._domNode, $('table.tiw-metadata-table', undefined));
const tbody = dom.append(table, $('tbody', undefined,
$('tr', undefined,
$('td.tiw-metadata-key', undefined, 'semantic token type' as string),
$('td.tiw-metadata-value', undefined, semanticTokenInfo.type)
)
));
if (semanticTokenInfo.modifiers.length) {
result += `<tr><td class="tiw-metadata-key">modifiers</td><td class="tiw-metadata-value">${semanticTokenInfo.modifiers.join(' ')}</td></tr>`;
dom.append(tbody, $('tr', undefined,
$('td.tiw-metadata-key', undefined, 'modifiers'),
$('td.tiw-metadata-value', undefined, semanticTokenInfo.modifiers.join(' ')),
));
}
if (semanticTokenInfo.metadata) {
const properties: (keyof TokenStyleData)[] = ['foreground', 'bold', 'italic', 'underline'];
Expand All @@ -319,55 +324,72 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
}
}
for (let defValue of allDefValues) {
result += `<tr><td class="tiw-metadata-key">${propertiesByDefValue[defValue].join(', ')}</td><td class="tiw-metadata-value">${defValue}</td></tr>`;
dom.append(tbody, $('tr', undefined,
$('td.tiw-metadata-key', undefined, propertiesByDefValue[defValue].join(', ')),
$('td.tiw-metadata-value', undefined, defValue)
));
}
}
result += `</tbody></table>`;
}

if (textMateTokenInfo) {
let theme = this._themeService.getColorTheme();
result += `<hr class="tiw-metadata-separator"/>`;
result += `<table class="tiw-metadata-table"><tbody>`;
dom.append(this._domNode, $('hr.tiw-metadata-separator'));
const table = dom.append(this._domNode, $('table.tiw-metadata-table'));
const tbody = dom.append(table, $('tbody'));

if (tmTokenText && tmTokenText !== tokenText) {
result += `<tr><td class="tiw-metadata-key">textmate token</td><td class="tiw-metadata-value">${tmTokenText} (${tmTokenText.length})</td></tr>`;
dom.append(tbody, $('tr', undefined,
$('td.tiw-metadata-key', undefined, 'textmate token' as string),
$('td.tiw-metadata-value', undefined, `${tmTokenText} (${tmTokenText.length})`)
));
}
let scopes = '';
const scopes = new Array<HTMLElement | string>();
for (let i = textMateTokenInfo.token.scopes.length - 1; i >= 0; i--) {
scopes += escape(textMateTokenInfo.token.scopes[i]);
scopes.push(textMateTokenInfo.token.scopes[i]);
if (i > 0) {
scopes += '<br>';
scopes.push($('br'));
}
}
result += `<tr><td class="tiw-metadata-key">textmate scopes</td><td class="tiw-metadata-value tiw-metadata-scopes">${scopes}</td></tr>`;
dom.append(tbody, $('tr', undefined,
$('td.tiw-metadata-key', undefined, 'textmate scopes' as string),
$('td.tiw-metadata-value.tiw-metadata-scopes', undefined, ...scopes),
));

let matchingRule = findMatchingThemeRule(theme, textMateTokenInfo.token.scopes, false);
const semForeground = semanticTokenInfo?.metadata?.foreground;
if (matchingRule) {
let defValue = `<code class="tiw-theme-selector">${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}</code>`;
if (semForeground !== textMateTokenInfo.metadata.foreground) {
let defValue = $('code.tiw-theme-selector', undefined,
matchingRule.rawSelector, $('br'), JSON.stringify(matchingRule.settings, null, '\t'));
if (semForeground) {
defValue = `<s>${defValue}</s>`;
defValue = $('s', undefined, defValue);
}
result += `<tr><td class="tiw-metadata-key">foreground</td><td class="tiw-metadata-value">${defValue}</td></tr>`;
dom.append(tbody, $('tr', undefined,
$('td.tiw-metadata-key', undefined, 'foreground'),
$('td.tiw-metadata-value', undefined, defValue),
));
}
} else if (!semForeground) {
result += `<tr><td class="tiw-metadata-key">foreground</td><td class="tiw-metadata-value">No theme selector</td></tr>`;
dom.append(tbody, $('tr', undefined,
$('td.tiw-metadata-key', undefined, 'foreground'),
$('td.tiw-metadata-value', undefined, 'No theme selector' as string),
));
}
result += `</tbody></table>`;
}
return result;
}

private _formatMetadata(semantic?: IDecodedMetadata, tm?: IDecodedMetadata) {
let result = '';
private _formatMetadata(semantic?: IDecodedMetadata, tm?: IDecodedMetadata): Array<HTMLElement | string> {
const elements = new Array<HTMLElement | string>();

function render(property: 'foreground' | 'background') {
let value = semantic?.[property] || tm?.[property];
if (value !== undefined) {
const semanticStyle = semantic?.[property] ? 'tiw-metadata-semantic' : '';
result += `<tr><td class="tiw-metadata-key">${property}</td><td class="tiw-metadata-value ${semanticStyle}">${value}</td></tr>`;

elements.push($('tr', undefined,
$('td.tiw-metadata-key', undefined, property),
$(`td.tiw-metadata-value.${semanticStyle}`, undefined, value)
));
}
return value;
}
Expand All @@ -377,17 +399,23 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
if (foreground && background) {
const backgroundColor = Color.fromHex(background), foregroundColor = Color.fromHex(foreground);
if (backgroundColor.isOpaque()) {
result += `<tr><td class="tiw-metadata-key">contrast ratio</td><td class="tiw-metadata-value">${backgroundColor.getContrastRatio(foregroundColor.makeOpaque(backgroundColor)).toFixed(2)}</td></tr>`;
elements.push($('tr', undefined,
$('td.tiw-metadata-key', undefined, 'contrast ratio' as string),
$('td.tiw-metadata-value', undefined, backgroundColor.getContrastRatio(foregroundColor.makeOpaque(backgroundColor)).toFixed(2))
));
} else {
result += '<tr><td class="tiw-metadata-key">Contrast ratio cannot be precise for background colors that use transparency</td><td class="tiw-metadata-value"></td></tr>';
elements.push($('tr', undefined,
$('td.tiw-metadata-key', undefined, 'Contrast ratio cannot be precise for background colors that use transparency' as string),
$('td.tiw-metadata-value')
));
}
}

let fontStyleLabels: string[] = [];
const fontStyleLabels = new Array<HTMLElement | string>();

function addStyle(key: 'bold' | 'italic' | 'underline') {
if (semantic && semantic[key]) {
fontStyleLabels.push(`<span class='tiw-metadata-semantic'>${key}</span>`);
fontStyleLabels.push($('span.tiw-metadata-semantic', undefined, key));
} else if (tm && tm[key]) {
fontStyleLabels.push(key);
}
Expand All @@ -396,9 +424,12 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
addStyle('italic');
addStyle('underline');
if (fontStyleLabels.length) {
result += `<tr><td class="tiw-metadata-key">font style</td><td class="tiw-metadata-value">${fontStyleLabels.join(' ')}</td></tr>`;
elements.push($('tr', undefined,
$('td.tiw-metadata-key', undefined, 'font style' as string),
$('td.tiw-metadata-value', undefined, fontStyleLabels.join(' '))
));
}
return result;
return elements;
}

private _decodeMetadata(metadata: number): IDecodedMetadata {
Expand Down

0 comments on commit 0cdb5b1

Please sign in to comment.