Merge from vscode a4177f50c475fc0fa278a78235e3bee9ffdec781 (#8649)

* Merge from vscode a4177f50c475fc0fa278a78235e3bee9ffdec781

* distro

* fix tests
This commit is contained in:
Anthony Dresser
2019-12-11 22:42:23 -08:00
committed by GitHub
parent 82974a2135
commit 4ba6a979ba
280 changed files with 10898 additions and 14231 deletions

View File

@@ -15,6 +15,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
import { escape } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/common/codicons';
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
@@ -118,7 +119,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
}
};
renderer.paragraph = (text): string => {
return `<p>${text}</p>`;
return `<p>${markdown.supportThemeIcons ? renderCodicons(text) : text}</p>`;
};
if (options.codeBlockRenderer) {
@@ -192,7 +193,13 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
allowedSchemes.push(Schemas.command);
}
const renderedMarkdown = marked.parse(markdown.value, markedOptions);
const renderedMarkdown = marked.parse(
markdown.supportThemeIcons
? markdownEscapeEscapedCodicons(markdown.value)
: markdown.value,
markedOptions
);
element.innerHTML = insane(renderedMarkdown, {
allowedSchemes,
allowedAttributes: {

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 3.76345L5.80687 11.9351L5.08584 11.8927L1 7.29614L1.76345 6.61752L5.50997 10.8324L14.3214 3L15 3.76345Z" fill="#C5C5C5"/>
</svg>

Before

Width:  |  Height:  |  Size: 278 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 3.76345L5.80687 11.9351L5.08584 11.8927L1 7.29614L1.76345 6.61752L5.50997 10.8324L14.3214 3L15 3.76345Z" fill="#424242"/>
</svg>

Before

Width:  |  Height:  |  Size: 278 B

View File

@@ -44,10 +44,7 @@
background-size: 16px !important;
}
.monaco-custom-checkbox.monaco-simple-checkbox.checked {
background: url('check-light.svg') center center no-repeat;
}
.monaco-custom-checkbox.monaco-simple-checkbox.checked {
background: url('check-dark.svg') center center no-repeat;
/* hide check when unchecked */
.monaco-custom-checkbox.monaco-simple-checkbox.unchecked:not(.checked)::before {
visibility: hidden;;
}

View File

@@ -192,7 +192,7 @@ export class SimpleCheckbox extends Widget {
constructor(private title: string, private isChecked: boolean) {
super();
this.checkbox = new Checkbox({ title: this.title, isChecked: this.isChecked, actionClassName: 'monaco-simple-checkbox' });
this.checkbox = new Checkbox({ title: this.title, isChecked: this.isChecked, actionClassName: 'monaco-simple-checkbox codicon-check' });
this.domNode = this.checkbox.domNode;

View File

@@ -395,9 +395,9 @@
.codicon-debug-breakpoint-function-unverified:before { content: "\eb87" }
.codicon-debug-breakpoint-function:before { content: "\eb88" }
.codicon-debug-breakpoint-function-disabled:before { content: "\eb88" }
.codicon-debug-breakpoint-stackframe-active:before { content: "\eb89" }
.codicon-debug-breakpoint-stackframe:before { content: "\eb8b" }
.codicon-debug-breakpoint-stackframe-focused:before { content: "\eb8b" }
.codicon-debug-stackframe-active:before { content: "\eb89" }
.codicon-debug-stackframe:before { content: "\eb8b" }
.codicon-debug-stackframe-focused:before { content: "\eb8b" }
.codicon-debug-breakpoint-unsupported:before { content: "\eb8c" }
.codicon-symbol-string:before { content: "\eb8d" }
.codicon-debug-reverse-continue:before { content: "\eb8e" }

View File

@@ -6,16 +6,7 @@
import 'vs/css!./codicon/codicon';
import 'vs/css!./codicon/codicon-animations';
import { escape } from 'vs/base/common/strings';
function expand(text: string): string {
return text.replace(/\$\((([a-z0-9\-]+?)(~([a-z0-9\-]*?))?)\)/gi, (_match, _g1, name, _g3, animation) => {
return `<span class="codicon codicon-${name} ${animation ? `codicon-animation-${animation}` : ''}"></span>`;
});
}
export function renderCodicons(label: string): string {
return expand(escape(label));
}
import { renderCodicons } from 'vs/base/common/codicons';
export class CodiconLabel {
@@ -24,7 +15,7 @@ export class CodiconLabel {
) { }
set text(text: string) {
this._container.innerHTML = renderCodicons(text || '');
this._container.innerHTML = renderCodicons(escape(text ?? ''));
}
set title(title: string) {

View File

@@ -5,5 +5,5 @@
.context-view {
position: absolute;
z-index: 2000;
}
z-index: 2500;
}

View File

@@ -28,6 +28,7 @@
max-width: 90%;
min-height: 75px;
padding: 10px;
transform: translate3d(0px, 0px, 0px);
}
/** Dialog: Title Actions Row */

View File

@@ -46,6 +46,7 @@ interface ButtonMapEntry {
export class Dialog extends Disposable {
private element: HTMLElement | undefined;
private shadowElement: HTMLElement | undefined;
private modal: HTMLElement | undefined;
private buttonsContainer: HTMLElement | undefined;
private messageDetailElement: HTMLElement | undefined;
@@ -61,7 +62,8 @@ export class Dialog extends Disposable {
constructor(private container: HTMLElement, private message: string, buttons: string[], private options: IDialogOptions) {
super();
this.modal = this.container.appendChild($(`.dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`));
this.element = this.modal.appendChild($('.dialog-box'));
this.shadowElement = this.modal.appendChild($('.dialog-shadow'));
this.element = this.shadowElement.appendChild($('.dialog-box'));
hide(this.element);
// If no button is provided, default to OK
@@ -249,10 +251,13 @@ export class Dialog extends Disposable {
const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : '';
const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : '';
if (this.shadowElement) {
this.shadowElement.style.boxShadow = shadowColor;
}
if (this.element) {
this.element.style.color = fgColor;
this.element.style.backgroundColor = bgColor;
this.element.style.boxShadow = shadowColor;
this.element.style.border = border;
if (this.buttonGroup) {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as objects from 'vs/base/common/objects';
import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
import { renderCodicons } from 'vs/base/common/codicons';
import { escape } from 'vs/base/common/strings';
export interface IHighlight {
@@ -65,13 +65,13 @@ export class HighlightedLabel {
if (pos < highlight.start) {
htmlContent += '<span>';
const substring = this.text.substring(pos, highlight.start);
htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring);
htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring);
htmlContent += '</span>';
pos = highlight.end;
}
htmlContent += '<span class="highlight">';
const substring = this.text.substring(highlight.start, highlight.end);
htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring);
htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring);
htmlContent += '</span>';
pos = highlight.end;
}
@@ -79,7 +79,7 @@ export class HighlightedLabel {
if (pos < this.text.length) {
htmlContent += '<span>';
const substring = this.text.substring(pos);
htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring);
htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring);
htmlContent += '</span>';
}

View File

@@ -593,7 +593,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem {
const isSelected = this.element && hasClass(this.element, 'focused');
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : this.menuStyle.backgroundColor;
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : undefined;
const border = isSelected && this.menuStyle.selectionBorderColor ? `thin solid ${this.menuStyle.selectionBorderColor}` : '';
if (this.item) {

View File

@@ -1298,7 +1298,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
onDidModelSplice(() => null, null, this.disposables);
// Active nodes can change when the model changes or when focus or selection change.
// We debouce it with 0 delay since these events may fire in the same stack and we only
// We debounce it with 0 delay since these events may fire in the same stack and we only
// want to run this once. It also doesn't matter if it runs on the next tick since it's only
// a nice to have UI feature.
onDidChangeActiveNodes.input = Event.chain(Event.any<any>(onDidModelSplice, this.focus.onDidChange, this.selection.onDidChange))

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const escapeCodiconsRegex = /(?<!\\)\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
export function escapeCodicons(text: string): string {
return text.replace(escapeCodiconsRegex, match => `\\${match}`);
}
const markdownEscapedCodiconsRegex = /\\\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
export function markdownEscapeEscapedCodicons(text: string): string {
// Need to add an extra \ for escaping in markdown
return text.replace(markdownEscapedCodiconsRegex, match => `\\${match}`);
}
const markdownUnescapeCodiconsRegex = /(?<!\\)\$\\\(([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?)\\\)/gi;
export function markdownUnescapeCodicons(text: string): string {
return text.replace(markdownUnescapeCodiconsRegex, (_, codicon) => `$(${codicon})`);
}
const renderCodiconsRegex = /(\\)?\$\((([a-z0-9\-]+?)(?:~([a-z0-9\-]*?))?)\)/gi;
export function renderCodicons(text: string): string {
return text.replace(renderCodiconsRegex, (_, escape, codicon, name, animation) => {
return escape
? `$(${codicon})`
: `<span class="codicon codicon-${name}${animation ? ` codicon-animation-${animation}` : ''}"></span>`;
});
}

View File

@@ -84,11 +84,11 @@ export function memoize(target: any, key: string, descriptor: any) {
return createMemoizer()(target, key, descriptor);
}
export interface IDebouceReducer<T> {
export interface IDebounceReducer<T> {
(previousValue: T, ...args: any[]): T;
}
export function debounce<T>(delay: number, reducer?: IDebouceReducer<T>, initialValueProvider?: () => T): Function {
export function debounce<T>(delay: number, reducer?: IDebounceReducer<T>, initialValueProvider?: () => T): Function {
return createDecorator((fn, key) => {
const timerKey = `$debounce$${key}`;
const resultKey = `$debounce$result$${key}`;
@@ -112,3 +112,44 @@ export function debounce<T>(delay: number, reducer?: IDebouceReducer<T>, initial
};
});
}
export function throttle<T>(delay: number, reducer?: IDebounceReducer<T>, initialValueProvider?: () => T): Function {
return createDecorator((fn, key) => {
const timerKey = `$throttle$timer$${key}`;
const resultKey = `$throttle$result$${key}`;
const lastRunKey = `$throttle$lastRun$${key}`;
const pendingKey = `$throttle$pending$${key}`;
return function (this: any, ...args: any[]) {
if (!this[resultKey]) {
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
}
if (this[lastRunKey] === null || this[lastRunKey] === undefined) {
this[lastRunKey] = -Number.MAX_VALUE;
}
if (reducer) {
this[resultKey] = reducer(this[resultKey], ...args);
}
if (this[pendingKey]) {
return;
}
const nextTime = this[lastRunKey] + delay;
if (nextTime <= Date.now()) {
this[lastRunKey] = Date.now();
fn.apply(this, [this[resultKey]]);
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
} else {
this[pendingKey] = true;
this[timerKey] = setTimeout(() => {
this[pendingKey] = false;
this[lastRunKey] = Date.now();
fn.apply(this, [this[resultKey]]);
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
}, nextTime - Date.now());
}
};
});
}

View File

@@ -8,15 +8,11 @@ import * as types from 'vs/base/common/types';
import * as arrays from 'vs/base/common/arrays';
function exceptionToErrorMessage(exception: any, verbose: boolean): string {
if (exception.message) {
if (verbose && (exception.stack || exception.stacktrace)) {
return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), stackToString(exception.stack) || stackToString(exception.stacktrace));
}
return detectSystemErrorMessage(exception);
if (verbose && (exception.stack || exception.stacktrace)) {
return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), stackToString(exception.stack) || stackToString(exception.stacktrace));
}
return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
return detectSystemErrorMessage(exception);
}
function stackToString(stack: string[] | string | undefined): string | undefined {
@@ -34,7 +30,7 @@ function detectSystemErrorMessage(exception: any): string {
return nls.localize('nodeExceptionMessage', "A system error occurred ({0})", exception.message);
}
return exception.message;
return exception.message || nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
}
/**

View File

@@ -31,7 +31,7 @@ export class ErrorHandler {
};
}
public addListener(listener: ErrorListenerCallback): ErrorListenerUnbind {
addListener(listener: ErrorListenerCallback): ErrorListenerUnbind {
this.listeners.push(listener);
return () => {
@@ -49,21 +49,21 @@ export class ErrorHandler {
this.listeners.splice(this.listeners.indexOf(listener), 1);
}
public setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
this.unexpectedErrorHandler = newUnexpectedErrorHandler;
}
public getUnexpectedErrorHandler(): (e: any) => void {
getUnexpectedErrorHandler(): (e: any) => void {
return this.unexpectedErrorHandler;
}
public onUnexpectedError(e: any): void {
onUnexpectedError(e: any): void {
this.unexpectedErrorHandler(e);
this.emit(e);
}
// For external errors, we don't want the listeners to be called
public onUnexpectedExternalError(e: any): void {
onUnexpectedExternalError(e: any): void {
this.unexpectedErrorHandler(e);
}
}

View File

@@ -5,37 +5,51 @@
import { equals } from 'vs/base/common/arrays';
import { UriComponents } from 'vs/base/common/uri';
import { escapeCodicons, markdownUnescapeCodicons } from 'vs/base/common/codicons';
export interface IMarkdownString {
readonly value: string;
readonly isTrusted?: boolean;
readonly supportThemeIcons?: boolean;
uris?: { [href: string]: UriComponents };
}
export class MarkdownString implements IMarkdownString {
private readonly _isTrusted: boolean;
private readonly _supportThemeIcons: boolean;
private _value: string;
private _isTrusted: boolean;
constructor(
private _value: string = '',
isTrustedOrOptions: boolean | { isTrusted?: boolean, supportThemeIcons?: boolean } = false,
) {
if (typeof isTrustedOrOptions === 'boolean') {
this._isTrusted = isTrustedOrOptions;
this._supportThemeIcons = false;
}
else {
this._isTrusted = isTrustedOrOptions.isTrusted ?? false;
this._supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false;
}
constructor(value: string = '', isTrusted = false) {
this._value = value;
this._isTrusted = isTrusted;
}
get value() { return this._value; }
get isTrusted() { return this._isTrusted; }
get supportThemeIcons() { return this._supportThemeIcons; }
appendText(value: string): MarkdownString {
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
this._value += value
value = value
.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
.replace('\n', '\n\n');
this._value += this.supportThemeIcons ? markdownUnescapeCodicons(value) : value;
return this;
}
appendMarkdown(value: string): MarkdownString {
this._value += value;
return this;
}
@@ -47,6 +61,10 @@ export class MarkdownString implements IMarkdownString {
this._value += '\n```\n';
return this;
}
static escapeThemeIcons(value: string): string {
return escapeCodicons(value);
}
}
export function isEmptyMarkdownString(oneOrMany: IMarkdownString | IMarkdownString[] | null | undefined): boolean {
@@ -64,7 +82,8 @@ export function isMarkdownString(thing: any): thing is IMarkdownString {
return true;
} else if (thing && typeof thing === 'object') {
return typeof (<IMarkdownString>thing).value === 'string'
&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === undefined);
&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === undefined)
&& (typeof (<IMarkdownString>thing).supportThemeIcons === 'boolean' || (<IMarkdownString>thing).supportThemeIcons === undefined);
}
return false;
}
@@ -89,7 +108,7 @@ function markdownStringEqual(a: IMarkdownString, b: IMarkdownString): boolean {
} else if (!a || !b) {
return false;
} else {
return a.value === b.value && a.isTrusted === b.isTrusted;
return a.value === b.value && a.isTrusted === b.isTrusted && a.supportThemeIcons === b.supportThemeIcons;
}
}

View File

@@ -227,7 +227,7 @@ export class Client implements IChannelClient, IDisposable {
this.child.on('error', err => console.warn('IPC "' + this.options.serverName + '" errored with ' + err));
this.child.on('exit', (code: any, signal: any) => {
process.removeListener('exit', onExit);
process.removeListener('exit' as 'loaded', onExit); // https://github.com/electron/electron/issues/21475
this.activeRequests.forEach(r => dispose(r));
this.activeRequests.clear();

View File

@@ -6,42 +6,104 @@
import * as assert from 'assert';
import * as marked from 'vs/base/common/marked/marked';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
import { MarkdownString } from 'vs/base/common/htmlContent';
suite('MarkdownRenderer', () => {
test('image rendering conforms to default', () => {
const markdown = { value: `![image](someimageurl 'caption')` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
suite('Images', () => {
test('image rendering conforms to default', () => {
const markdown = { value: `![image](someimageurl 'caption')` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image rendering conforms to default without title', () => {
const markdown = { value: `![image](someimageurl)` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image width from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
});
test('image height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
});
test('image width and height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
});
});
test('image rendering conforms to default without title', () => {
const markdown = { value: `![image](someimageurl)` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
suite('ThemeIcons Support On', () => {
test('render appendText', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendText('$(zap) $(dont match me)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-zap"></span> $(dont match me)</p>`);
});
test('render appendText escaped', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendText(MarkdownString.escapeThemeIcons('$(zap) $(dont match me)'));
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
});
test('render appendMarkdown', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendMarkdown('$(zap) $(dont match me)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-zap"></span> $(dont match me)</p>`);
});
test('render appendMarkdown escaped', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendMarkdown(MarkdownString.escapeThemeIcons('$(zap) $(dont match me)'));
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
});
});
test('image width from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
suite('ThemeIcons Support Off', () => {
test('render appendText', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
mds.appendText('$(zap) $(dont match me)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
});
test('render appendMarkdown', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
mds.appendMarkdown('$(zap) $(dont match me)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
});
});
test('image height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
});
test('image width and height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
});
});

View File

@@ -3,8 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as sinon from 'sinon';
import * as assert from 'assert';
import { memoize, createMemoizer } from 'vs/base/common/decorators';
import { memoize, createMemoizer, throttle } from 'vs/base/common/decorators';
suite('Decorators', () => {
test('memoize should memoize methods', () => {
@@ -100,7 +101,9 @@ suite('Decorators', () => {
test('memoized property should not be enumerable', () => {
class Foo {
@memoize
get answer() { return 42; }
get answer() {
return 42;
}
}
const foo = new Foo();
@@ -112,7 +115,9 @@ suite('Decorators', () => {
test('memoized property should not be writable', () => {
class Foo {
@memoize
get answer() { return 42; }
get answer() {
return 42;
}
}
const foo = new Foo();
@@ -131,7 +136,9 @@ suite('Decorators', () => {
let counter = 0;
class Foo {
@memoizer
get answer() { return ++counter; }
get answer() {
return ++counter;
}
}
const foo = new Foo();
@@ -145,4 +152,49 @@ suite('Decorators', () => {
assert.equal(foo.answer, 3);
assert.equal(foo.answer, 3);
});
test('throttle', () => {
const spy = sinon.spy();
const clock = sinon.useFakeTimers();
try {
class ThrottleTest {
private _handle: Function;
constructor(fn: Function) {
this._handle = fn;
}
@throttle(
100,
(a: number, b: number) => a + b,
() => 0
)
report(p: number): void {
this._handle(p);
}
}
const t = new ThrottleTest(spy);
t.report(1);
t.report(2);
t.report(3);
assert.deepEqual(spy.args, [[1]]);
clock.tick(200);
assert.deepEqual(spy.args, [[1], [5]]);
spy.reset();
t.report(4);
t.report(5);
clock.tick(50);
t.report(6);
assert.deepEqual(spy.args, [[4]]);
clock.tick(60);
assert.deepEqual(spy.args, [[4], [11]]);
} finally {
clock.restore();
}
});
});

View File

@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { toErrorMessage } from 'vs/base/common/errorMessage';
@@ -16,9 +17,17 @@ suite('Errors', () => {
error.detail.exception = {};
error.detail.exception.message = 'Foo Bar';
assert.strictEqual(toErrorMessage(error), 'Foo Bar');
assert.strictEqual(toErrorMessage(error, true), 'Foo Bar');
assert(toErrorMessage());
assert(toErrorMessage(null));
assert(toErrorMessage({}));
try {
throw new Error();
} catch (error) {
assert.strictEqual(toErrorMessage(error), 'An unknown error occurred. Please consult the log for more details.');
assert.ok(toErrorMessage(error, true).length > 'An unknown error occurred. Please consult the log for more details.'.length);
}
});
});
});

View File

@@ -43,4 +43,14 @@ suite('Hash', () => {
assert.notEqual(hash({ 'foo': 'bar' }), hash({ 'foo': 'bar2' }));
assert.notEqual(hash({}), hash([]));
});
});
test('array - unexpected collision', function () {
this.skip();
const a = hash([undefined, undefined, undefined, undefined, undefined]);
const b = hash([undefined, undefined, 'HHHHHH', [{ line: 0, character: 0 }, { line: 0, character: 0 }], undefined]);
// console.log(a);
// console.log(b);
assert.notEqual(a, b);
});
});

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert';
import { MarkdownString } from 'vs/base/common/htmlContent';
suite('markdownString', () => {
suite('MarkdownString', () => {
test('escape', () => {
@@ -16,4 +16,63 @@ suite('markdownString', () => {
assert.equal(mds.value, '\\# foo\n\n\\*bar\\*');
});
suite('ThemeIcons', () => {
test('escapeThemeIcons', () => {
assert.equal(
MarkdownString.escapeThemeIcons('$(zap) $(not an icon) foo$(bar)'),
'\\$(zap) $(not an icon) foo\\$(bar)'
);
});
suite('Support On', () => {
test('appendText', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendText('$(zap)');
assert.equal(mds.value, '$(zap)');
});
test('appendText escaped', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendText(MarkdownString.escapeThemeIcons('$(zap)'));
assert.equal(mds.value, '\\\\$\\(zap\\)');
});
test('appendMarkdown', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendMarkdown('$(zap)');
assert.equal(mds.value, '$(zap)');
});
test('appendMarkdown escaped', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendMarkdown(MarkdownString.escapeThemeIcons('$(zap)'));
assert.equal(mds.value, '\\$(zap)');
});
});
suite('Support Off', () => {
test('appendText', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
mds.appendText('$(zap)');
assert.equal(mds.value, '$\\(zap\\)');
});
test('appendMarkdown', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
mds.appendMarkdown('$(zap)');
assert.equal(mds.value, '$(zap)');
});
});
});
});