mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-13 03:28:33 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
30
src/vs/base/common/OSSREADME.json
Normal file
30
src/vs/base/common/OSSREADME.json
Normal file
@@ -0,0 +1,30 @@
|
||||
// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS:
|
||||
[{
|
||||
"name": "string_scorer",
|
||||
"version": "0.1.20",
|
||||
"license": "MIT",
|
||||
"repositoryURL": "https://github.com/joshaven/string_score",
|
||||
"description": "The file scorer.ts was inspired by the string_score algorithm from Joshaven Potter.",
|
||||
"licenseDetail": [
|
||||
"This software is released under the Source EULA:",
|
||||
"",
|
||||
"Copyright (c) Joshaven Potter",
|
||||
"",
|
||||
"Permission is hereby granted, free of charge, to any person obtaining a copy of",
|
||||
"this software and associated documentation files (the \"Software\"), to deal in",
|
||||
"the Software without restriction, including without limitation the rights to",
|
||||
"use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of",
|
||||
"the Software, and to permit persons to whom the Software is furnished to do so,",
|
||||
"subject to the following conditions:",
|
||||
"",
|
||||
"The above copyright notice and this permission notice shall be included in all",
|
||||
"copies or substantial portions of the Software.",
|
||||
"",
|
||||
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR",
|
||||
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS",
|
||||
"FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR",
|
||||
"COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER",
|
||||
"IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN",
|
||||
"CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
|
||||
]
|
||||
}]
|
||||
244
src/vs/base/common/actions.ts
Normal file
244
src/vs/base/common/actions.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IEventEmitter, EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as Events from 'vs/base/common/events';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface ITelemetryData {
|
||||
from?: string;
|
||||
target?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface IAction extends IDisposable {
|
||||
id: string;
|
||||
label: string;
|
||||
tooltip: string;
|
||||
class: string;
|
||||
enabled: boolean;
|
||||
checked: boolean;
|
||||
radio: boolean;
|
||||
run(event?: any): TPromise<any>;
|
||||
}
|
||||
|
||||
export interface IActionRunner extends IEventEmitter {
|
||||
run(action: IAction, context?: any): TPromise<any>;
|
||||
}
|
||||
|
||||
export interface IActionItem extends IEventEmitter {
|
||||
actionRunner: IActionRunner;
|
||||
setActionContext(context: any): void;
|
||||
render(element: any /* HTMLElement */): void;
|
||||
isEnabled(): boolean;
|
||||
focus(): void;
|
||||
blur(): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided object is compatible
|
||||
* with the IAction interface.
|
||||
* @param thing an object
|
||||
*/
|
||||
export function isAction(thing: any): thing is IAction {
|
||||
if (!thing) {
|
||||
return false;
|
||||
} else if (thing instanceof Action) {
|
||||
return true;
|
||||
} else if (typeof thing.id !== 'string') {
|
||||
return false;
|
||||
} else if (typeof thing.label !== 'string') {
|
||||
return false;
|
||||
} else if (typeof thing.class !== 'string') {
|
||||
return false;
|
||||
} else if (typeof thing.enabled !== 'boolean') {
|
||||
return false;
|
||||
} else if (typeof thing.checked !== 'boolean') {
|
||||
return false;
|
||||
} else if (typeof thing.run !== 'function') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IActionChangeEvent {
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
class?: string;
|
||||
enabled?: boolean;
|
||||
checked?: boolean;
|
||||
radio?: boolean;
|
||||
}
|
||||
|
||||
export class Action implements IAction {
|
||||
|
||||
protected _onDidChange = new Emitter<IActionChangeEvent>();
|
||||
protected _id: string;
|
||||
protected _label: string;
|
||||
protected _tooltip: string;
|
||||
protected _cssClass: string;
|
||||
protected _enabled: boolean;
|
||||
protected _checked: boolean;
|
||||
protected _radio: boolean;
|
||||
protected _order: number;
|
||||
protected _actionCallback: (event?: any) => TPromise<any>;
|
||||
|
||||
constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => TPromise<any>) {
|
||||
this._id = id;
|
||||
this._label = label;
|
||||
this._cssClass = cssClass;
|
||||
this._enabled = enabled;
|
||||
this._actionCallback = actionCallback;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._onDidChange.dispose();
|
||||
}
|
||||
|
||||
public get onDidChange(): Event<IActionChangeEvent> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
public set label(value: string) {
|
||||
this._setLabel(value);
|
||||
}
|
||||
|
||||
protected _setLabel(value: string): void {
|
||||
if (this._label !== value) {
|
||||
this._label = value;
|
||||
this._onDidChange.fire({ label: value });
|
||||
}
|
||||
}
|
||||
|
||||
public get tooltip(): string {
|
||||
return this._tooltip;
|
||||
}
|
||||
|
||||
public set tooltip(value: string) {
|
||||
this._setTooltip(value);
|
||||
}
|
||||
|
||||
protected _setTooltip(value: string): void {
|
||||
if (this._tooltip !== value) {
|
||||
this._tooltip = value;
|
||||
this._onDidChange.fire({ tooltip: value });
|
||||
}
|
||||
}
|
||||
|
||||
public get class(): string {
|
||||
return this._cssClass;
|
||||
}
|
||||
|
||||
public set class(value: string) {
|
||||
this._setClass(value);
|
||||
}
|
||||
|
||||
protected _setClass(value: string): void {
|
||||
if (this._cssClass !== value) {
|
||||
this._cssClass = value;
|
||||
this._onDidChange.fire({ class: value });
|
||||
}
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
public set enabled(value: boolean) {
|
||||
this._setEnabled(value);
|
||||
}
|
||||
|
||||
protected _setEnabled(value: boolean): void {
|
||||
if (this._enabled !== value) {
|
||||
this._enabled = value;
|
||||
this._onDidChange.fire({ enabled: value });
|
||||
}
|
||||
}
|
||||
|
||||
public get checked(): boolean {
|
||||
return this._checked;
|
||||
}
|
||||
|
||||
public set checked(value: boolean) {
|
||||
this._setChecked(value);
|
||||
}
|
||||
|
||||
public get radio(): boolean {
|
||||
return this._radio;
|
||||
}
|
||||
|
||||
public set radio(value: boolean) {
|
||||
this._setRadio(value);
|
||||
}
|
||||
|
||||
protected _setChecked(value: boolean): void {
|
||||
if (this._checked !== value) {
|
||||
this._checked = value;
|
||||
this._onDidChange.fire({ checked: value });
|
||||
}
|
||||
}
|
||||
|
||||
protected _setRadio(value: boolean): void {
|
||||
if (this._radio !== value) {
|
||||
this._radio = value;
|
||||
this._onDidChange.fire({ radio: value });
|
||||
}
|
||||
}
|
||||
|
||||
public get order(): number {
|
||||
return this._order;
|
||||
}
|
||||
|
||||
public set order(value: number) {
|
||||
this._order = value;
|
||||
}
|
||||
|
||||
public run(event?: any, data?: ITelemetryData): TPromise<any> {
|
||||
if (this._actionCallback !== void 0) {
|
||||
return this._actionCallback(event);
|
||||
}
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IRunEvent {
|
||||
action: IAction;
|
||||
result?: any;
|
||||
error?: any;
|
||||
}
|
||||
|
||||
export class ActionRunner extends EventEmitter implements IActionRunner {
|
||||
|
||||
public run(action: IAction, context?: any): TPromise<any> {
|
||||
if (!action.enabled) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.emit(Events.EventType.BEFORE_RUN, { action: action });
|
||||
|
||||
return this.runAction(action, context).then((result: any) => {
|
||||
this.emit(Events.EventType.RUN, <IRunEvent>{ action: action, result: result });
|
||||
}, (error: any) => {
|
||||
this.emit(Events.EventType.RUN, <IRunEvent>{ action: action, error: error });
|
||||
});
|
||||
}
|
||||
|
||||
protected runAction(action: IAction, context?: any): TPromise<any> {
|
||||
return TPromise.as(context ? action.run(context) : action.run());
|
||||
}
|
||||
}
|
||||
344
src/vs/base/common/arrays.ts
Normal file
344
src/vs/base/common/arrays.ts
Normal file
@@ -0,0 +1,344 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Returns the last element of an array.
|
||||
* @param array The array.
|
||||
* @param n Which element from the end (default is zero).
|
||||
*/
|
||||
export function tail<T>(array: T[], n: number = 0): T {
|
||||
return array[array.length - (1 + n)];
|
||||
}
|
||||
|
||||
export function equals<T>(one: T[], other: T[], itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
|
||||
if (one.length !== other.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0, len = one.length; i < len; i++) {
|
||||
if (!itemEquals(one[i], other[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function binarySearch<T>(array: T[], key: T, comparator: (op1: T, op2: T) => number): number {
|
||||
let low = 0,
|
||||
high = array.length - 1;
|
||||
|
||||
while (low <= high) {
|
||||
let mid = ((low + high) / 2) | 0;
|
||||
let comp = comparator(array[mid], key);
|
||||
if (comp < 0) {
|
||||
low = mid + 1;
|
||||
} else if (comp > 0) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
return -(low + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false
|
||||
* are located before all elements where p(x) is true.
|
||||
* @returns the least x for which p(x) is true or array.length if no element fullfills the given function.
|
||||
*/
|
||||
export function findFirst<T>(array: T[], p: (x: T) => boolean): number {
|
||||
let low = 0, high = array.length;
|
||||
if (high === 0) {
|
||||
return 0; // no children
|
||||
}
|
||||
while (low < high) {
|
||||
let mid = Math.floor((low + high) / 2);
|
||||
if (p(array[mid])) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort`
|
||||
* so only use this when actually needing stable sort.
|
||||
*/
|
||||
export function mergeSort<T>(data: T[], compare: (a: T, b: T) => number): T[] {
|
||||
_divideAndMerge(data, compare);
|
||||
return data;
|
||||
}
|
||||
|
||||
function _divideAndMerge<T>(data: T[], compare: (a: T, b: T) => number): void {
|
||||
if (data.length <= 1) {
|
||||
// sorted
|
||||
return;
|
||||
}
|
||||
const p = (data.length / 2) | 0;
|
||||
const left = data.slice(0, p);
|
||||
const right = data.slice(p);
|
||||
|
||||
_divideAndMerge(left, compare);
|
||||
_divideAndMerge(right, compare);
|
||||
|
||||
let leftIdx = 0;
|
||||
let rightIdx = 0;
|
||||
let i = 0;
|
||||
while (leftIdx < left.length && rightIdx < right.length) {
|
||||
let ret = compare(left[leftIdx], right[rightIdx]);
|
||||
if (ret <= 0) {
|
||||
// smaller_equal -> take left to preserve order
|
||||
data[i++] = left[leftIdx++];
|
||||
} else {
|
||||
// greater -> take right
|
||||
data[i++] = right[rightIdx++];
|
||||
}
|
||||
}
|
||||
while (leftIdx < left.length) {
|
||||
data[i++] = left[leftIdx++];
|
||||
}
|
||||
while (rightIdx < right.length) {
|
||||
data[i++] = right[rightIdx++];
|
||||
}
|
||||
}
|
||||
|
||||
export function groupBy<T>(data: T[], compare: (a: T, b: T) => number): T[][] {
|
||||
const result: T[][] = [];
|
||||
let currentGroup: T[];
|
||||
for (const element of data.slice(0).sort(compare)) {
|
||||
if (!currentGroup || compare(currentGroup[0], element) !== 0) {
|
||||
currentGroup = [element];
|
||||
result.push(currentGroup);
|
||||
} else {
|
||||
currentGroup.push(element);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes two *sorted* arrays and computes their delta (removed, added elements).
|
||||
* Finishes in `Math.min(before.length, after.length)` steps.
|
||||
* @param before
|
||||
* @param after
|
||||
* @param compare
|
||||
*/
|
||||
export function delta<T>(before: T[], after: T[], compare: (a: T, b: T) => number) {
|
||||
|
||||
const removed: T[] = [];
|
||||
const added: T[] = [];
|
||||
|
||||
let beforeIdx = 0;
|
||||
let afterIdx = 0;
|
||||
|
||||
while (true) {
|
||||
if (beforeIdx === before.length) {
|
||||
added.push(...after.slice(afterIdx));
|
||||
break;
|
||||
}
|
||||
if (afterIdx === after.length) {
|
||||
removed.push(...before.slice(beforeIdx));
|
||||
break;
|
||||
}
|
||||
|
||||
const beforeElement = before[beforeIdx];
|
||||
const afterElement = after[afterIdx];
|
||||
const n = compare(beforeElement, afterElement);
|
||||
if (n === 0) {
|
||||
// equal
|
||||
beforeIdx += 1;
|
||||
afterIdx += 1;
|
||||
} else if (n < 0) {
|
||||
// beforeElement is smaller -> before element removed
|
||||
removed.push(beforeElement);
|
||||
beforeIdx += 1;
|
||||
} else if (n > 0) {
|
||||
// beforeElement is greater -> after element added
|
||||
added.push(afterElement);
|
||||
afterIdx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return { removed, added };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top N elements from the array.
|
||||
*
|
||||
* Faster than sorting the entire array when the array is a lot larger than N.
|
||||
*
|
||||
* @param array The unsorted array.
|
||||
* @param compare A sort function for the elements.
|
||||
* @param n The number of elements to return.
|
||||
* @return The first n elemnts from array when sorted with compare.
|
||||
*/
|
||||
export function top<T>(array: T[], compare: (a: T, b: T) => number, n: number): T[] {
|
||||
if (n === 0) {
|
||||
return [];
|
||||
}
|
||||
const result = array.slice(0, n).sort(compare);
|
||||
for (let i = n, m = array.length; i < m; i++) {
|
||||
const element = array[i];
|
||||
if (compare(element, result[n - 1]) < 0) {
|
||||
result.pop();
|
||||
const j = findFirst(result, e => compare(element, e) < 0);
|
||||
result.splice(j, 0, element);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns a new array with all undefined or null values removed. The original array is not modified at all.
|
||||
*/
|
||||
export function coalesce<T>(array: T[]): T[] {
|
||||
if (!array) {
|
||||
return array;
|
||||
}
|
||||
|
||||
return array.filter(e => !!e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the element in the array for the provided positions.
|
||||
*/
|
||||
export function move(array: any[], from: number, to: number): void {
|
||||
array.splice(to, 0, array.splice(from, 1)[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{false}} if the provided object is an array
|
||||
* and not empty.
|
||||
*/
|
||||
export function isFalsyOrEmpty(obj: any): boolean {
|
||||
return !Array.isArray(obj) || (<Array<any>>obj).length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes duplicates from the given array. The optional keyFn allows to specify
|
||||
* how elements are checked for equalness by returning a unique string for each.
|
||||
*/
|
||||
export function distinct<T>(array: T[], keyFn?: (t: T) => string): T[] {
|
||||
if (!keyFn) {
|
||||
return array.filter((element, position) => {
|
||||
return array.indexOf(element) === position;
|
||||
});
|
||||
}
|
||||
|
||||
const seen: { [key: string]: boolean; } = Object.create(null);
|
||||
return array.filter((elem) => {
|
||||
const key = keyFn(elem);
|
||||
if (seen[key]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
seen[key] = true;
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function uniqueFilter<T>(keyFn: (t: T) => string): (t: T) => boolean {
|
||||
const seen: { [key: string]: boolean; } = Object.create(null);
|
||||
|
||||
return element => {
|
||||
const key = keyFn(element);
|
||||
|
||||
if (seen[key]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
seen[key] = true;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
export function firstIndex<T>(array: T[], fn: (item: T) => boolean): number {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const element = array[i];
|
||||
|
||||
if (fn(element)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function first<T>(array: T[], fn: (item: T) => boolean, notFoundValue: T = null): T {
|
||||
const index = firstIndex(array, fn);
|
||||
return index < 0 ? notFoundValue : array[index];
|
||||
}
|
||||
|
||||
export function commonPrefixLength<T>(one: T[], other: T[], equals: (a: T, b: T) => boolean = (a, b) => a === b): number {
|
||||
let result = 0;
|
||||
|
||||
for (let i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) {
|
||||
result++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function flatten<T>(arr: T[][]): T[] {
|
||||
return arr.reduce((r, v) => r.concat(v), []);
|
||||
}
|
||||
|
||||
export function range(to: number, from = 0): number[] {
|
||||
const result: number[] = [];
|
||||
|
||||
for (let i = from; i < to; i++) {
|
||||
result.push(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function fill<T>(num: number, valueFn: () => T, arr: T[] = []): T[] {
|
||||
for (let i = 0; i < num; i++) {
|
||||
arr[i] = valueFn();
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
export function index<T>(array: T[], indexer: (t: T) => string): { [key: string]: T; };
|
||||
export function index<T, R>(array: T[], indexer: (t: T) => string, merger?: (t: T, r: R) => R): { [key: string]: R; };
|
||||
export function index<T, R>(array: T[], indexer: (t: T) => string, merger: (t: T, r: R) => R = t => t as any): { [key: string]: R; } {
|
||||
return array.reduce((r, t) => {
|
||||
const key = indexer(t);
|
||||
r[key] = merger(t, r[key]);
|
||||
return r;
|
||||
}, Object.create(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts an element into an array. Returns a function which, when
|
||||
* called, will remove that element from the array.
|
||||
*/
|
||||
export function insert<T>(array: T[], element: T): () => void {
|
||||
array.push(element);
|
||||
|
||||
return () => {
|
||||
const index = array.indexOf(element);
|
||||
if (index > -1) {
|
||||
array.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert `insertArr` inside `target` at `insertIndex`.
|
||||
* Please don't touch unless you understand https://jsperf.com/inserting-an-array-within-an-array
|
||||
*/
|
||||
export function arrayInsert<T>(target: T[], insertIndex: number, insertArr: T[]): T[] {
|
||||
const before = target.slice(0, insertIndex);
|
||||
const after = target.slice(insertIndex);
|
||||
return before.concat(insertArr, after);
|
||||
}
|
||||
14
src/vs/base/common/assert.ts
Normal file
14
src/vs/base/common/assert.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Throws an error with the provided message if the provided value does not evaluate to a true Javascript value.
|
||||
*/
|
||||
export function ok(value?: any, message?: string) {
|
||||
if (!value || value === null) {
|
||||
throw new Error(message ? 'Assertion failed (' + message + ')' : 'Assertion Failed');
|
||||
}
|
||||
}
|
||||
650
src/vs/base/common/async.ts
Normal file
650
src/vs/base/common/async.ts
Normal file
@@ -0,0 +1,650 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { Promise, TPromise, ValueCallback, ErrorCallback, ProgressCallback } from 'vs/base/common/winjs.base';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
function isThenable<T>(obj: any): obj is Thenable<T> {
|
||||
return obj && typeof (<Thenable<any>>obj).then === 'function';
|
||||
}
|
||||
|
||||
export function toThenable<T>(arg: T | Thenable<T>): Thenable<T> {
|
||||
if (isThenable(arg)) {
|
||||
return arg;
|
||||
} else {
|
||||
return TPromise.as(arg);
|
||||
}
|
||||
}
|
||||
|
||||
export function asWinJsPromise<T>(callback: (token: CancellationToken) => T | TPromise<T> | Thenable<T>): TPromise<T> {
|
||||
let source = new CancellationTokenSource();
|
||||
return new TPromise<T>((resolve, reject, progress) => {
|
||||
let item = callback(source.token);
|
||||
if (item instanceof TPromise) {
|
||||
item.then(resolve, reject, progress);
|
||||
} else if (isThenable<T>(item)) {
|
||||
item.then(resolve, reject);
|
||||
} else {
|
||||
resolve(item);
|
||||
}
|
||||
}, () => {
|
||||
source.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook a cancellation token to a WinJS Promise
|
||||
*/
|
||||
export function wireCancellationToken<T>(token: CancellationToken, promise: TPromise<T>, resolveAsUndefinedWhenCancelled?: boolean): Thenable<T> {
|
||||
const subscription = token.onCancellationRequested(() => promise.cancel());
|
||||
if (resolveAsUndefinedWhenCancelled) {
|
||||
promise = promise.then<T>(undefined, err => {
|
||||
if (!errors.isPromiseCanceledError(err)) {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
return always(promise, () => subscription.dispose());
|
||||
}
|
||||
|
||||
export interface ITask<T> {
|
||||
(): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to prevent accumulation of sequential async tasks.
|
||||
*
|
||||
* Imagine a mail man with the sole task of delivering letters. As soon as
|
||||
* a letter submitted for delivery, he drives to the destination, delivers it
|
||||
* and returns to his base. Imagine that during the trip, N more letters were submitted.
|
||||
* When the mail man returns, he picks those N letters and delivers them all in a
|
||||
* single trip. Even though N+1 submissions occurred, only 2 deliveries were made.
|
||||
*
|
||||
* The throttler implements this via the queue() method, by providing it a task
|
||||
* factory. Following the example:
|
||||
*
|
||||
* const throttler = new Throttler();
|
||||
* const letters = [];
|
||||
*
|
||||
* function deliver() {
|
||||
* const lettersToDeliver = letters;
|
||||
* letters = [];
|
||||
* return makeTheTrip(lettersToDeliver);
|
||||
* }
|
||||
*
|
||||
* function onLetterReceived(l) {
|
||||
* letters.push(l);
|
||||
* throttler.queue(deliver);
|
||||
* }
|
||||
*/
|
||||
export class Throttler {
|
||||
|
||||
private activePromise: Promise;
|
||||
private queuedPromise: Promise;
|
||||
private queuedPromiseFactory: ITask<Promise>;
|
||||
|
||||
constructor() {
|
||||
this.activePromise = null;
|
||||
this.queuedPromise = null;
|
||||
this.queuedPromiseFactory = null;
|
||||
}
|
||||
|
||||
queue<T>(promiseFactory: ITask<TPromise<T>>): TPromise<T> {
|
||||
if (this.activePromise) {
|
||||
this.queuedPromiseFactory = promiseFactory;
|
||||
|
||||
if (!this.queuedPromise) {
|
||||
const onComplete = () => {
|
||||
this.queuedPromise = null;
|
||||
|
||||
const result = this.queue(this.queuedPromiseFactory);
|
||||
this.queuedPromiseFactory = null;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
this.queuedPromise = new TPromise((c, e, p) => {
|
||||
this.activePromise.then(onComplete, onComplete, p).done(c);
|
||||
}, () => {
|
||||
this.activePromise.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
return new TPromise((c, e, p) => {
|
||||
this.queuedPromise.then(c, e, p);
|
||||
}, () => {
|
||||
// no-op
|
||||
});
|
||||
}
|
||||
|
||||
this.activePromise = promiseFactory();
|
||||
|
||||
return new TPromise((c, e, p) => {
|
||||
this.activePromise.done((result: any) => {
|
||||
this.activePromise = null;
|
||||
c(result);
|
||||
}, (err: any) => {
|
||||
this.activePromise = null;
|
||||
e(err);
|
||||
}, p);
|
||||
}, () => {
|
||||
this.activePromise.cancel();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO@Joao: can the previous throttler be replaced with this?
|
||||
export class SimpleThrottler {
|
||||
|
||||
private current = TPromise.as<any>(null);
|
||||
|
||||
queue<T>(promiseTask: ITask<TPromise<T>>): TPromise<T> {
|
||||
return this.current = this.current.then(() => promiseTask());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to delay execution of a task that is being requested often.
|
||||
*
|
||||
* Following the throttler, now imagine the mail man wants to optimize the number of
|
||||
* trips proactively. The trip itself can be long, so the he decides not to make the trip
|
||||
* as soon as a letter is submitted. Instead he waits a while, in case more
|
||||
* letters are submitted. After said waiting period, if no letters were submitted, he
|
||||
* decides to make the trip. Imagine that N more letters were submitted after the first
|
||||
* one, all within a short period of time between each other. Even though N+1
|
||||
* submissions occurred, only 1 delivery was made.
|
||||
*
|
||||
* The delayer offers this behavior via the trigger() method, into which both the task
|
||||
* to be executed and the waiting period (delay) must be passed in as arguments. Following
|
||||
* the example:
|
||||
*
|
||||
* const delayer = new Delayer(WAITING_PERIOD);
|
||||
* const letters = [];
|
||||
*
|
||||
* function letterReceived(l) {
|
||||
* letters.push(l);
|
||||
* delayer.trigger(() => { return makeTheTrip(); });
|
||||
* }
|
||||
*/
|
||||
export class Delayer<T> {
|
||||
|
||||
private timeout: number;
|
||||
private completionPromise: Promise;
|
||||
private onSuccess: ValueCallback;
|
||||
private task: ITask<T>;
|
||||
|
||||
constructor(public defaultDelay: number) {
|
||||
this.timeout = null;
|
||||
this.completionPromise = null;
|
||||
this.onSuccess = null;
|
||||
this.task = null;
|
||||
}
|
||||
|
||||
trigger(task: ITask<T>, delay: number = this.defaultDelay): TPromise<T> {
|
||||
this.task = task;
|
||||
this.cancelTimeout();
|
||||
|
||||
if (!this.completionPromise) {
|
||||
this.completionPromise = new TPromise((c) => {
|
||||
this.onSuccess = c;
|
||||
}, () => {
|
||||
// no-op
|
||||
}).then(() => {
|
||||
this.completionPromise = null;
|
||||
this.onSuccess = null;
|
||||
const task = this.task;
|
||||
this.task = null;
|
||||
|
||||
return task();
|
||||
});
|
||||
}
|
||||
|
||||
this.timeout = setTimeout(() => {
|
||||
this.timeout = null;
|
||||
this.onSuccess(null);
|
||||
}, delay);
|
||||
|
||||
return this.completionPromise;
|
||||
}
|
||||
|
||||
isTriggered(): boolean {
|
||||
return this.timeout !== null;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.cancelTimeout();
|
||||
|
||||
if (this.completionPromise) {
|
||||
this.completionPromise.cancel();
|
||||
this.completionPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
private cancelTimeout(): void {
|
||||
if (this.timeout !== null) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to delay execution of a task that is being requested often, while
|
||||
* preventing accumulation of consecutive executions, while the task runs.
|
||||
*
|
||||
* Simply combine the two mail man strategies from the Throttler and Delayer
|
||||
* helpers, for an analogy.
|
||||
*/
|
||||
export class ThrottledDelayer<T> extends Delayer<TPromise<T>> {
|
||||
|
||||
private throttler: Throttler;
|
||||
|
||||
constructor(defaultDelay: number) {
|
||||
super(defaultDelay);
|
||||
|
||||
this.throttler = new Throttler();
|
||||
}
|
||||
|
||||
trigger(promiseFactory: ITask<TPromise<T>>, delay?: number): Promise {
|
||||
return super.trigger(() => this.throttler.queue(promiseFactory), delay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to the ThrottledDelayer, except it also guarantees that the promise
|
||||
* factory doesn't get called more often than every `minimumPeriod` milliseconds.
|
||||
*/
|
||||
export class PeriodThrottledDelayer<T> extends ThrottledDelayer<T> {
|
||||
|
||||
private minimumPeriod: number;
|
||||
private periodThrottler: Throttler;
|
||||
|
||||
constructor(defaultDelay: number, minimumPeriod: number = 0) {
|
||||
super(defaultDelay);
|
||||
|
||||
this.minimumPeriod = minimumPeriod;
|
||||
this.periodThrottler = new Throttler();
|
||||
}
|
||||
|
||||
trigger(promiseFactory: ITask<TPromise<T>>, delay?: number): Promise {
|
||||
return super.trigger(() => {
|
||||
return this.periodThrottler.queue(() => {
|
||||
return Promise.join([
|
||||
TPromise.timeout(this.minimumPeriod),
|
||||
promiseFactory()
|
||||
]).then(r => r[1]);
|
||||
});
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
export class PromiseSource<T> {
|
||||
|
||||
private _value: TPromise<T>;
|
||||
private _completeCallback: Function;
|
||||
private _errorCallback: Function;
|
||||
|
||||
constructor() {
|
||||
this._value = new TPromise<T>((c, e) => {
|
||||
this._completeCallback = c;
|
||||
this._errorCallback = e;
|
||||
});
|
||||
}
|
||||
|
||||
get value(): TPromise<T> {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
complete(value?: T): void {
|
||||
this._completeCallback(value);
|
||||
}
|
||||
|
||||
error(err?: any): void {
|
||||
this._errorCallback(err);
|
||||
}
|
||||
}
|
||||
|
||||
export class ShallowCancelThenPromise<T> extends TPromise<T> {
|
||||
|
||||
constructor(outer: TPromise<T>) {
|
||||
|
||||
let completeCallback: ValueCallback,
|
||||
errorCallback: ErrorCallback,
|
||||
progressCallback: ProgressCallback;
|
||||
|
||||
super((c, e, p) => {
|
||||
completeCallback = c;
|
||||
errorCallback = e;
|
||||
progressCallback = p;
|
||||
}, () => {
|
||||
// cancel this promise but not the
|
||||
// outer promise
|
||||
errorCallback(errors.canceled());
|
||||
});
|
||||
|
||||
outer.then(completeCallback, errorCallback, progressCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new promise that joins the provided promise. Upon completion of
|
||||
* the provided promise the provided function will always be called. This
|
||||
* method is comparable to a try-finally code block.
|
||||
* @param promise a promise
|
||||
* @param f a function that will be call in the success and error case.
|
||||
*/
|
||||
export function always<T>(promise: TPromise<T>, f: Function): TPromise<T> {
|
||||
return new TPromise<T>((c, e, p) => {
|
||||
promise.done((result) => {
|
||||
try {
|
||||
f(result);
|
||||
} catch (e1) {
|
||||
errors.onUnexpectedError(e1);
|
||||
}
|
||||
c(result);
|
||||
}, (err) => {
|
||||
try {
|
||||
f(err);
|
||||
} catch (e1) {
|
||||
errors.onUnexpectedError(e1);
|
||||
}
|
||||
e(err);
|
||||
}, (progress) => {
|
||||
p(progress);
|
||||
});
|
||||
}, () => {
|
||||
promise.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the provided list of promise factories in sequential order. The returned
|
||||
* promise will complete to an array of results from each promise.
|
||||
*/
|
||||
export function sequence<T>(promiseFactories: ITask<TPromise<T>>[]): TPromise<T[]> {
|
||||
const results: T[] = [];
|
||||
|
||||
// reverse since we start with last element using pop()
|
||||
promiseFactories = promiseFactories.reverse();
|
||||
|
||||
function next(): Promise {
|
||||
if (promiseFactories.length) {
|
||||
return promiseFactories.pop()();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function thenHandler(result: any): Promise {
|
||||
if (result !== undefined && result !== null) {
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
const n = next();
|
||||
if (n) {
|
||||
return n.then(thenHandler);
|
||||
}
|
||||
|
||||
return TPromise.as(results);
|
||||
}
|
||||
|
||||
return TPromise.as(null).then(thenHandler);
|
||||
}
|
||||
|
||||
export function first<T>(promiseFactories: ITask<TPromise<T>>[], shouldStop: (t: T) => boolean = t => !!t): TPromise<T> {
|
||||
promiseFactories = [...promiseFactories.reverse()];
|
||||
|
||||
const loop: () => TPromise<T> = () => {
|
||||
if (promiseFactories.length === 0) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
const factory = promiseFactories.pop();
|
||||
const promise = factory();
|
||||
|
||||
return promise.then(result => {
|
||||
if (shouldStop(result)) {
|
||||
return TPromise.as(result);
|
||||
}
|
||||
|
||||
return loop();
|
||||
});
|
||||
};
|
||||
|
||||
return loop();
|
||||
}
|
||||
|
||||
interface ILimitedTaskFactory {
|
||||
factory: ITask<Promise>;
|
||||
c: ValueCallback;
|
||||
e: ErrorCallback;
|
||||
p: ProgressCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to queue N promises and run them all with a max degree of parallelism. The helper
|
||||
* ensures that at any time no more than M promises are running at the same time.
|
||||
*/
|
||||
export class Limiter<T> {
|
||||
private runningPromises: number;
|
||||
private maxDegreeOfParalellism: number;
|
||||
private outstandingPromises: ILimitedTaskFactory[];
|
||||
private _onFinished: Emitter<void>;
|
||||
|
||||
constructor(maxDegreeOfParalellism: number) {
|
||||
this.maxDegreeOfParalellism = maxDegreeOfParalellism;
|
||||
this.outstandingPromises = [];
|
||||
this.runningPromises = 0;
|
||||
this._onFinished = new Emitter<void>();
|
||||
}
|
||||
|
||||
public get onFinished(): Event<void> {
|
||||
return this._onFinished.event;
|
||||
}
|
||||
|
||||
queue(promiseFactory: ITask<Promise>): Promise;
|
||||
queue(promiseFactory: ITask<TPromise<T>>): TPromise<T> {
|
||||
return new TPromise<T>((c, e, p) => {
|
||||
this.outstandingPromises.push({
|
||||
factory: promiseFactory,
|
||||
c: c,
|
||||
e: e,
|
||||
p: p
|
||||
});
|
||||
|
||||
this.consume();
|
||||
});
|
||||
}
|
||||
|
||||
private consume(): void {
|
||||
while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) {
|
||||
const iLimitedTask = this.outstandingPromises.shift();
|
||||
this.runningPromises++;
|
||||
|
||||
const promise = iLimitedTask.factory();
|
||||
promise.done(iLimitedTask.c, iLimitedTask.e, iLimitedTask.p);
|
||||
promise.done(() => this.consumed(), () => this.consumed());
|
||||
}
|
||||
}
|
||||
|
||||
private consumed(): void {
|
||||
this.runningPromises--;
|
||||
|
||||
if (this.outstandingPromises.length > 0) {
|
||||
this.consume();
|
||||
} else {
|
||||
this._onFinished.fire();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._onFinished.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A queue is handles one promise at a time and guarantees that at any time only one promise is executing.
|
||||
*/
|
||||
export class Queue<T> extends Limiter<T> {
|
||||
|
||||
constructor() {
|
||||
super(1);
|
||||
}
|
||||
}
|
||||
|
||||
export function setDisposableTimeout(handler: Function, timeout: number, ...args: any[]): IDisposable {
|
||||
const handle = setTimeout(handler, timeout, ...args);
|
||||
return { dispose() { clearTimeout(handle); } };
|
||||
}
|
||||
|
||||
export class TimeoutTimer extends Disposable {
|
||||
private _token: platform.TimeoutToken;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._token = -1;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if (this._token !== -1) {
|
||||
platform.clearTimeout(this._token);
|
||||
this._token = -1;
|
||||
}
|
||||
}
|
||||
|
||||
cancelAndSet(runner: () => void, timeout: number): void {
|
||||
this.cancel();
|
||||
this._token = platform.setTimeout(() => {
|
||||
this._token = -1;
|
||||
runner();
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
setIfNotSet(runner: () => void, timeout: number): void {
|
||||
if (this._token !== -1) {
|
||||
// timer is already set
|
||||
return;
|
||||
}
|
||||
this._token = platform.setTimeout(() => {
|
||||
this._token = -1;
|
||||
runner();
|
||||
}, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
export class IntervalTimer extends Disposable {
|
||||
|
||||
private _token: platform.IntervalToken;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._token = -1;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if (this._token !== -1) {
|
||||
platform.clearInterval(this._token);
|
||||
this._token = -1;
|
||||
}
|
||||
}
|
||||
|
||||
cancelAndSet(runner: () => void, interval: number): void {
|
||||
this.cancel();
|
||||
this._token = platform.setInterval(() => {
|
||||
runner();
|
||||
}, interval);
|
||||
}
|
||||
}
|
||||
|
||||
export class RunOnceScheduler {
|
||||
|
||||
private timeoutToken: platform.TimeoutToken;
|
||||
private runner: () => void;
|
||||
private timeout: number;
|
||||
private timeoutHandler: () => void;
|
||||
|
||||
constructor(runner: () => void, timeout: number) {
|
||||
this.timeoutToken = -1;
|
||||
this.runner = runner;
|
||||
this.timeout = timeout;
|
||||
this.timeoutHandler = this.onTimeout.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose RunOnceScheduler
|
||||
*/
|
||||
dispose(): void {
|
||||
this.cancel();
|
||||
this.runner = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel current scheduled runner (if any).
|
||||
*/
|
||||
cancel(): void {
|
||||
if (this.isScheduled()) {
|
||||
platform.clearTimeout(this.timeoutToken);
|
||||
this.timeoutToken = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace runner. If there is a runner already scheduled, the new runner will be called.
|
||||
*/
|
||||
setRunner(runner: () => void): void {
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel previous runner (if any) & schedule a new runner.
|
||||
*/
|
||||
schedule(delay = this.timeout): void {
|
||||
this.cancel();
|
||||
this.timeoutToken = platform.setTimeout(this.timeoutHandler, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if scheduled.
|
||||
*/
|
||||
isScheduled(): boolean {
|
||||
return this.timeoutToken !== -1;
|
||||
}
|
||||
|
||||
private onTimeout() {
|
||||
this.timeoutToken = -1;
|
||||
if (this.runner) {
|
||||
this.runner();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function nfcall(fn: Function, ...args: any[]): Promise;
|
||||
export function nfcall<T>(fn: Function, ...args: any[]): TPromise<T>;
|
||||
export function nfcall(fn: Function, ...args: any[]): any {
|
||||
return new TPromise((c, e) => fn(...args, (err, result) => err ? e(err) : c(result)), () => null);
|
||||
}
|
||||
|
||||
export function ninvoke(thisArg: any, fn: Function, ...args: any[]): Promise;
|
||||
export function ninvoke<T>(thisArg: any, fn: Function, ...args: any[]): TPromise<T>;
|
||||
export function ninvoke(thisArg: any, fn: Function, ...args: any[]): any {
|
||||
return new TPromise((c, e) => fn.call(thisArg, ...args, (err, result) => err ? e(err) : c(result)), () => null);
|
||||
}
|
||||
15
src/vs/base/common/buildunit.json
Normal file
15
src/vs/base/common/buildunit.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "vs/base",
|
||||
"dependencies": [
|
||||
{ "name": "vs", "internal": false }
|
||||
],
|
||||
"libs": [
|
||||
"lib.core.d.ts"
|
||||
],
|
||||
"sources": [
|
||||
"**/*.ts"
|
||||
],
|
||||
"declares": [
|
||||
"vs/base/winjs.base.d.ts"
|
||||
]
|
||||
}
|
||||
29
src/vs/base/common/cache.ts
Normal file
29
src/vs/base/common/cache.ts
Normal 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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
export default class Cache<T> {
|
||||
|
||||
private promise: TPromise<T> = null;
|
||||
constructor(private task: () => TPromise<T>) { }
|
||||
|
||||
get(): TPromise<T> {
|
||||
if (this.promise) {
|
||||
return this.promise;
|
||||
}
|
||||
|
||||
const promise = this.task();
|
||||
|
||||
this.promise = new TPromise<T>((c, e) => promise.done(c, e), () => {
|
||||
this.promise = null;
|
||||
promise.cancel();
|
||||
});
|
||||
|
||||
return this.promise;
|
||||
}
|
||||
}
|
||||
86
src/vs/base/common/callbackList.ts
Normal file
86
src/vs/base/common/callbackList.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
export default class CallbackList {
|
||||
|
||||
private _callbacks: Function[];
|
||||
private _contexts: any[];
|
||||
|
||||
public add(callback: Function, context: any = null, bucket?: IDisposable[]): void {
|
||||
if (!this._callbacks) {
|
||||
this._callbacks = [];
|
||||
this._contexts = [];
|
||||
}
|
||||
this._callbacks.push(callback);
|
||||
this._contexts.push(context);
|
||||
|
||||
if (Array.isArray(bucket)) {
|
||||
bucket.push({ dispose: () => this.remove(callback, context) });
|
||||
}
|
||||
}
|
||||
|
||||
public remove(callback: Function, context: any = null): void {
|
||||
if (!this._callbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
let foundCallbackWithDifferentContext = false;
|
||||
for (let i = 0, len = this._callbacks.length; i < len; i++) {
|
||||
if (this._callbacks[i] === callback) {
|
||||
if (this._contexts[i] === context) {
|
||||
// callback & context match => remove it
|
||||
this._callbacks.splice(i, 1);
|
||||
this._contexts.splice(i, 1);
|
||||
return;
|
||||
} else {
|
||||
foundCallbackWithDifferentContext = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundCallbackWithDifferentContext) {
|
||||
throw new Error('When adding a listener with a context, you should remove it with the same context');
|
||||
}
|
||||
}
|
||||
|
||||
public invoke(...args: any[]): any[] {
|
||||
if (!this._callbacks) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const ret: any[] = [],
|
||||
callbacks = this._callbacks.slice(0),
|
||||
contexts = this._contexts.slice(0);
|
||||
|
||||
for (let i = 0, len = callbacks.length; i < len; i++) {
|
||||
try {
|
||||
ret.push(callbacks[i].apply(contexts[i], args));
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return !this._callbacks || this._callbacks.length === 0;
|
||||
}
|
||||
|
||||
public entries(): [Function, any][] {
|
||||
if (!this._callbacks) {
|
||||
return [];
|
||||
}
|
||||
return this._callbacks.map((fn, index) => <[Function, any]>[fn, this._contexts[index]]);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._callbacks = undefined;
|
||||
this._contexts = undefined;
|
||||
}
|
||||
}
|
||||
94
src/vs/base/common/cancellation.ts
Normal file
94
src/vs/base/common/cancellation.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface CancellationToken {
|
||||
readonly isCancellationRequested: boolean;
|
||||
/**
|
||||
* An event emitted when cancellation is requested
|
||||
* @event
|
||||
*/
|
||||
readonly onCancellationRequested: Event<any>;
|
||||
}
|
||||
|
||||
const shortcutEvent: Event<any> = Object.freeze(function (callback, context?) {
|
||||
let handle = setTimeout(callback.bind(context), 0);
|
||||
return { dispose() { clearTimeout(handle); } };
|
||||
});
|
||||
|
||||
export namespace CancellationToken {
|
||||
|
||||
export const None: CancellationToken = Object.freeze({
|
||||
isCancellationRequested: false,
|
||||
onCancellationRequested: Event.None
|
||||
});
|
||||
|
||||
export const Cancelled: CancellationToken = Object.freeze({
|
||||
isCancellationRequested: true,
|
||||
onCancellationRequested: shortcutEvent
|
||||
});
|
||||
}
|
||||
|
||||
class MutableToken implements CancellationToken {
|
||||
|
||||
private _isCancelled: boolean = false;
|
||||
private _emitter: Emitter<any>;
|
||||
|
||||
public cancel() {
|
||||
if (!this._isCancelled) {
|
||||
this._isCancelled = true;
|
||||
if (this._emitter) {
|
||||
this._emitter.fire(undefined);
|
||||
this._emitter = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get isCancellationRequested(): boolean {
|
||||
return this._isCancelled;
|
||||
}
|
||||
|
||||
get onCancellationRequested(): Event<any> {
|
||||
if (this._isCancelled) {
|
||||
return shortcutEvent;
|
||||
}
|
||||
if (!this._emitter) {
|
||||
this._emitter = new Emitter<any>();
|
||||
}
|
||||
return this._emitter.event;
|
||||
}
|
||||
}
|
||||
|
||||
export class CancellationTokenSource {
|
||||
|
||||
private _token: CancellationToken;
|
||||
|
||||
get token(): CancellationToken {
|
||||
if (!this._token) {
|
||||
// be lazy and create the token only when
|
||||
// actually needed
|
||||
this._token = new MutableToken();
|
||||
}
|
||||
return this._token;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if (!this._token) {
|
||||
// save an object by returning the default
|
||||
// cancelled token when cancellation happens
|
||||
// before someone asks for the token
|
||||
this._token = CancellationToken.Cancelled;
|
||||
} else {
|
||||
(<MutableToken>this._token).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
422
src/vs/base/common/charCode.ts
Normal file
422
src/vs/base/common/charCode.ts
Normal file
@@ -0,0 +1,422 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/
|
||||
|
||||
/**
|
||||
* An inlined enum containing useful character codes (to be used with String.charCodeAt).
|
||||
* Please leave the const keyword such that it gets inlined when compiled to JavaScript!
|
||||
*/
|
||||
export const enum CharCode {
|
||||
Null = 0,
|
||||
/**
|
||||
* The `\t` character.
|
||||
*/
|
||||
Tab = 9,
|
||||
/**
|
||||
* The `\n` character.
|
||||
*/
|
||||
LineFeed = 10,
|
||||
/**
|
||||
* The `\r` character.
|
||||
*/
|
||||
CarriageReturn = 13,
|
||||
Space = 32,
|
||||
/**
|
||||
* The `!` character.
|
||||
*/
|
||||
ExclamationMark = 33,
|
||||
/**
|
||||
* The `"` character.
|
||||
*/
|
||||
DoubleQuote = 34,
|
||||
/**
|
||||
* The `#` character.
|
||||
*/
|
||||
Hash = 35,
|
||||
/**
|
||||
* The `$` character.
|
||||
*/
|
||||
DollarSign = 36,
|
||||
/**
|
||||
* The `%` character.
|
||||
*/
|
||||
PercentSign = 37,
|
||||
/**
|
||||
* The `&` character.
|
||||
*/
|
||||
Ampersand = 38,
|
||||
/**
|
||||
* The `'` character.
|
||||
*/
|
||||
SingleQuote = 39,
|
||||
/**
|
||||
* The `(` character.
|
||||
*/
|
||||
OpenParen = 40,
|
||||
/**
|
||||
* The `)` character.
|
||||
*/
|
||||
CloseParen = 41,
|
||||
/**
|
||||
* The `*` character.
|
||||
*/
|
||||
Asterisk = 42,
|
||||
/**
|
||||
* The `+` character.
|
||||
*/
|
||||
Plus = 43,
|
||||
/**
|
||||
* The `,` character.
|
||||
*/
|
||||
Comma = 44,
|
||||
/**
|
||||
* The `-` character.
|
||||
*/
|
||||
Dash = 45,
|
||||
/**
|
||||
* The `.` character.
|
||||
*/
|
||||
Period = 46,
|
||||
/**
|
||||
* The `/` character.
|
||||
*/
|
||||
Slash = 47,
|
||||
|
||||
Digit0 = 48,
|
||||
Digit1 = 49,
|
||||
Digit2 = 50,
|
||||
Digit3 = 51,
|
||||
Digit4 = 52,
|
||||
Digit5 = 53,
|
||||
Digit6 = 54,
|
||||
Digit7 = 55,
|
||||
Digit8 = 56,
|
||||
Digit9 = 57,
|
||||
|
||||
/**
|
||||
* The `:` character.
|
||||
*/
|
||||
Colon = 58,
|
||||
/**
|
||||
* The `;` character.
|
||||
*/
|
||||
Semicolon = 59,
|
||||
/**
|
||||
* The `<` character.
|
||||
*/
|
||||
LessThan = 60,
|
||||
/**
|
||||
* The `=` character.
|
||||
*/
|
||||
Equals = 61,
|
||||
/**
|
||||
* The `>` character.
|
||||
*/
|
||||
GreaterThan = 62,
|
||||
/**
|
||||
* The `?` character.
|
||||
*/
|
||||
QuestionMark = 63,
|
||||
/**
|
||||
* The `@` character.
|
||||
*/
|
||||
AtSign = 64,
|
||||
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
T = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
|
||||
/**
|
||||
* The `[` character.
|
||||
*/
|
||||
OpenSquareBracket = 91,
|
||||
/**
|
||||
* The `\` character.
|
||||
*/
|
||||
Backslash = 92,
|
||||
/**
|
||||
* The `]` character.
|
||||
*/
|
||||
CloseSquareBracket = 93,
|
||||
/**
|
||||
* The `^` character.
|
||||
*/
|
||||
Caret = 94,
|
||||
/**
|
||||
* The `_` character.
|
||||
*/
|
||||
Underline = 95,
|
||||
/**
|
||||
* The ``(`)`` character.
|
||||
*/
|
||||
BackTick = 96,
|
||||
|
||||
a = 97,
|
||||
b = 98,
|
||||
c = 99,
|
||||
d = 100,
|
||||
e = 101,
|
||||
f = 102,
|
||||
g = 103,
|
||||
h = 104,
|
||||
i = 105,
|
||||
j = 106,
|
||||
k = 107,
|
||||
l = 108,
|
||||
m = 109,
|
||||
n = 110,
|
||||
o = 111,
|
||||
p = 112,
|
||||
q = 113,
|
||||
r = 114,
|
||||
s = 115,
|
||||
t = 116,
|
||||
u = 117,
|
||||
v = 118,
|
||||
w = 119,
|
||||
x = 120,
|
||||
y = 121,
|
||||
z = 122,
|
||||
|
||||
/**
|
||||
* The `{` character.
|
||||
*/
|
||||
OpenCurlyBrace = 123,
|
||||
/**
|
||||
* The `|` character.
|
||||
*/
|
||||
Pipe = 124,
|
||||
/**
|
||||
* The `}` character.
|
||||
*/
|
||||
CloseCurlyBrace = 125,
|
||||
/**
|
||||
* The `~` character.
|
||||
*/
|
||||
Tilde = 126,
|
||||
|
||||
U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent
|
||||
U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent
|
||||
U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent
|
||||
U_Combining_Tilde = 0x0303, // U+0303 Combining Tilde
|
||||
U_Combining_Macron = 0x0304, // U+0304 Combining Macron
|
||||
U_Combining_Overline = 0x0305, // U+0305 Combining Overline
|
||||
U_Combining_Breve = 0x0306, // U+0306 Combining Breve
|
||||
U_Combining_Dot_Above = 0x0307, // U+0307 Combining Dot Above
|
||||
U_Combining_Diaeresis = 0x0308, // U+0308 Combining Diaeresis
|
||||
U_Combining_Hook_Above = 0x0309, // U+0309 Combining Hook Above
|
||||
U_Combining_Ring_Above = 0x030A, // U+030A Combining Ring Above
|
||||
U_Combining_Double_Acute_Accent = 0x030B, // U+030B Combining Double Acute Accent
|
||||
U_Combining_Caron = 0x030C, // U+030C Combining Caron
|
||||
U_Combining_Vertical_Line_Above = 0x030D, // U+030D Combining Vertical Line Above
|
||||
U_Combining_Double_Vertical_Line_Above = 0x030E, // U+030E Combining Double Vertical Line Above
|
||||
U_Combining_Double_Grave_Accent = 0x030F, // U+030F Combining Double Grave Accent
|
||||
U_Combining_Candrabindu = 0x0310, // U+0310 Combining Candrabindu
|
||||
U_Combining_Inverted_Breve = 0x0311, // U+0311 Combining Inverted Breve
|
||||
U_Combining_Turned_Comma_Above = 0x0312, // U+0312 Combining Turned Comma Above
|
||||
U_Combining_Comma_Above = 0x0313, // U+0313 Combining Comma Above
|
||||
U_Combining_Reversed_Comma_Above = 0x0314, // U+0314 Combining Reversed Comma Above
|
||||
U_Combining_Comma_Above_Right = 0x0315, // U+0315 Combining Comma Above Right
|
||||
U_Combining_Grave_Accent_Below = 0x0316, // U+0316 Combining Grave Accent Below
|
||||
U_Combining_Acute_Accent_Below = 0x0317, // U+0317 Combining Acute Accent Below
|
||||
U_Combining_Left_Tack_Below = 0x0318, // U+0318 Combining Left Tack Below
|
||||
U_Combining_Right_Tack_Below = 0x0319, // U+0319 Combining Right Tack Below
|
||||
U_Combining_Left_Angle_Above = 0x031A, // U+031A Combining Left Angle Above
|
||||
U_Combining_Horn = 0x031B, // U+031B Combining Horn
|
||||
U_Combining_Left_Half_Ring_Below = 0x031C, // U+031C Combining Left Half Ring Below
|
||||
U_Combining_Up_Tack_Below = 0x031D, // U+031D Combining Up Tack Below
|
||||
U_Combining_Down_Tack_Below = 0x031E, // U+031E Combining Down Tack Below
|
||||
U_Combining_Plus_Sign_Below = 0x031F, // U+031F Combining Plus Sign Below
|
||||
U_Combining_Minus_Sign_Below = 0x0320, // U+0320 Combining Minus Sign Below
|
||||
U_Combining_Palatalized_Hook_Below = 0x0321, // U+0321 Combining Palatalized Hook Below
|
||||
U_Combining_Retroflex_Hook_Below = 0x0322, // U+0322 Combining Retroflex Hook Below
|
||||
U_Combining_Dot_Below = 0x0323, // U+0323 Combining Dot Below
|
||||
U_Combining_Diaeresis_Below = 0x0324, // U+0324 Combining Diaeresis Below
|
||||
U_Combining_Ring_Below = 0x0325, // U+0325 Combining Ring Below
|
||||
U_Combining_Comma_Below = 0x0326, // U+0326 Combining Comma Below
|
||||
U_Combining_Cedilla = 0x0327, // U+0327 Combining Cedilla
|
||||
U_Combining_Ogonek = 0x0328, // U+0328 Combining Ogonek
|
||||
U_Combining_Vertical_Line_Below = 0x0329, // U+0329 Combining Vertical Line Below
|
||||
U_Combining_Bridge_Below = 0x032A, // U+032A Combining Bridge Below
|
||||
U_Combining_Inverted_Double_Arch_Below = 0x032B, // U+032B Combining Inverted Double Arch Below
|
||||
U_Combining_Caron_Below = 0x032C, // U+032C Combining Caron Below
|
||||
U_Combining_Circumflex_Accent_Below = 0x032D, // U+032D Combining Circumflex Accent Below
|
||||
U_Combining_Breve_Below = 0x032E, // U+032E Combining Breve Below
|
||||
U_Combining_Inverted_Breve_Below = 0x032F, // U+032F Combining Inverted Breve Below
|
||||
U_Combining_Tilde_Below = 0x0330, // U+0330 Combining Tilde Below
|
||||
U_Combining_Macron_Below = 0x0331, // U+0331 Combining Macron Below
|
||||
U_Combining_Low_Line = 0x0332, // U+0332 Combining Low Line
|
||||
U_Combining_Double_Low_Line = 0x0333, // U+0333 Combining Double Low Line
|
||||
U_Combining_Tilde_Overlay = 0x0334, // U+0334 Combining Tilde Overlay
|
||||
U_Combining_Short_Stroke_Overlay = 0x0335, // U+0335 Combining Short Stroke Overlay
|
||||
U_Combining_Long_Stroke_Overlay = 0x0336, // U+0336 Combining Long Stroke Overlay
|
||||
U_Combining_Short_Solidus_Overlay = 0x0337, // U+0337 Combining Short Solidus Overlay
|
||||
U_Combining_Long_Solidus_Overlay = 0x0338, // U+0338 Combining Long Solidus Overlay
|
||||
U_Combining_Right_Half_Ring_Below = 0x0339, // U+0339 Combining Right Half Ring Below
|
||||
U_Combining_Inverted_Bridge_Below = 0x033A, // U+033A Combining Inverted Bridge Below
|
||||
U_Combining_Square_Below = 0x033B, // U+033B Combining Square Below
|
||||
U_Combining_Seagull_Below = 0x033C, // U+033C Combining Seagull Below
|
||||
U_Combining_X_Above = 0x033D, // U+033D Combining X Above
|
||||
U_Combining_Vertical_Tilde = 0x033E, // U+033E Combining Vertical Tilde
|
||||
U_Combining_Double_Overline = 0x033F, // U+033F Combining Double Overline
|
||||
U_Combining_Grave_Tone_Mark = 0x0340, // U+0340 Combining Grave Tone Mark
|
||||
U_Combining_Acute_Tone_Mark = 0x0341, // U+0341 Combining Acute Tone Mark
|
||||
U_Combining_Greek_Perispomeni = 0x0342, // U+0342 Combining Greek Perispomeni
|
||||
U_Combining_Greek_Koronis = 0x0343, // U+0343 Combining Greek Koronis
|
||||
U_Combining_Greek_Dialytika_Tonos = 0x0344, // U+0344 Combining Greek Dialytika Tonos
|
||||
U_Combining_Greek_Ypogegrammeni = 0x0345, // U+0345 Combining Greek Ypogegrammeni
|
||||
U_Combining_Bridge_Above = 0x0346, // U+0346 Combining Bridge Above
|
||||
U_Combining_Equals_Sign_Below = 0x0347, // U+0347 Combining Equals Sign Below
|
||||
U_Combining_Double_Vertical_Line_Below = 0x0348, // U+0348 Combining Double Vertical Line Below
|
||||
U_Combining_Left_Angle_Below = 0x0349, // U+0349 Combining Left Angle Below
|
||||
U_Combining_Not_Tilde_Above = 0x034A, // U+034A Combining Not Tilde Above
|
||||
U_Combining_Homothetic_Above = 0x034B, // U+034B Combining Homothetic Above
|
||||
U_Combining_Almost_Equal_To_Above = 0x034C, // U+034C Combining Almost Equal To Above
|
||||
U_Combining_Left_Right_Arrow_Below = 0x034D, // U+034D Combining Left Right Arrow Below
|
||||
U_Combining_Upwards_Arrow_Below = 0x034E, // U+034E Combining Upwards Arrow Below
|
||||
U_Combining_Grapheme_Joiner = 0x034F, // U+034F Combining Grapheme Joiner
|
||||
U_Combining_Right_Arrowhead_Above = 0x0350, // U+0350 Combining Right Arrowhead Above
|
||||
U_Combining_Left_Half_Ring_Above = 0x0351, // U+0351 Combining Left Half Ring Above
|
||||
U_Combining_Fermata = 0x0352, // U+0352 Combining Fermata
|
||||
U_Combining_X_Below = 0x0353, // U+0353 Combining X Below
|
||||
U_Combining_Left_Arrowhead_Below = 0x0354, // U+0354 Combining Left Arrowhead Below
|
||||
U_Combining_Right_Arrowhead_Below = 0x0355, // U+0355 Combining Right Arrowhead Below
|
||||
U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, // U+0356 Combining Right Arrowhead And Up Arrowhead Below
|
||||
U_Combining_Right_Half_Ring_Above = 0x0357, // U+0357 Combining Right Half Ring Above
|
||||
U_Combining_Dot_Above_Right = 0x0358, // U+0358 Combining Dot Above Right
|
||||
U_Combining_Asterisk_Below = 0x0359, // U+0359 Combining Asterisk Below
|
||||
U_Combining_Double_Ring_Below = 0x035A, // U+035A Combining Double Ring Below
|
||||
U_Combining_Zigzag_Above = 0x035B, // U+035B Combining Zigzag Above
|
||||
U_Combining_Double_Breve_Below = 0x035C, // U+035C Combining Double Breve Below
|
||||
U_Combining_Double_Breve = 0x035D, // U+035D Combining Double Breve
|
||||
U_Combining_Double_Macron = 0x035E, // U+035E Combining Double Macron
|
||||
U_Combining_Double_Macron_Below = 0x035F, // U+035F Combining Double Macron Below
|
||||
U_Combining_Double_Tilde = 0x0360, // U+0360 Combining Double Tilde
|
||||
U_Combining_Double_Inverted_Breve = 0x0361, // U+0361 Combining Double Inverted Breve
|
||||
U_Combining_Double_Rightwards_Arrow_Below = 0x0362, // U+0362 Combining Double Rightwards Arrow Below
|
||||
U_Combining_Latin_Small_Letter_A = 0x0363, // U+0363 Combining Latin Small Letter A
|
||||
U_Combining_Latin_Small_Letter_E = 0x0364, // U+0364 Combining Latin Small Letter E
|
||||
U_Combining_Latin_Small_Letter_I = 0x0365, // U+0365 Combining Latin Small Letter I
|
||||
U_Combining_Latin_Small_Letter_O = 0x0366, // U+0366 Combining Latin Small Letter O
|
||||
U_Combining_Latin_Small_Letter_U = 0x0367, // U+0367 Combining Latin Small Letter U
|
||||
U_Combining_Latin_Small_Letter_C = 0x0368, // U+0368 Combining Latin Small Letter C
|
||||
U_Combining_Latin_Small_Letter_D = 0x0369, // U+0369 Combining Latin Small Letter D
|
||||
U_Combining_Latin_Small_Letter_H = 0x036A, // U+036A Combining Latin Small Letter H
|
||||
U_Combining_Latin_Small_Letter_M = 0x036B, // U+036B Combining Latin Small Letter M
|
||||
U_Combining_Latin_Small_Letter_R = 0x036C, // U+036C Combining Latin Small Letter R
|
||||
U_Combining_Latin_Small_Letter_T = 0x036D, // U+036D Combining Latin Small Letter T
|
||||
U_Combining_Latin_Small_Letter_V = 0x036E, // U+036E Combining Latin Small Letter V
|
||||
U_Combining_Latin_Small_Letter_X = 0x036F, // U+036F Combining Latin Small Letter X
|
||||
|
||||
/**
|
||||
* Unicode Character 'LINE SEPARATOR' (U+2028)
|
||||
* http://www.fileformat.info/info/unicode/char/2028/index.htm
|
||||
*/
|
||||
LINE_SEPARATOR_2028 = 8232,
|
||||
|
||||
// http://www.fileformat.info/info/unicode/category/Sk/list.htm
|
||||
U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX
|
||||
U_GRAVE_ACCENT = 0x0060, // U+0060 GRAVE ACCENT
|
||||
U_DIAERESIS = 0x00A8, // U+00A8 DIAERESIS
|
||||
U_MACRON = 0x00AF, // U+00AF MACRON
|
||||
U_ACUTE_ACCENT = 0x00B4, // U+00B4 ACUTE ACCENT
|
||||
U_CEDILLA = 0x00B8, // U+00B8 CEDILLA
|
||||
U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2, // U+02C2 MODIFIER LETTER LEFT ARROWHEAD
|
||||
U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3, // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD
|
||||
U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4, // U+02C4 MODIFIER LETTER UP ARROWHEAD
|
||||
U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5, // U+02C5 MODIFIER LETTER DOWN ARROWHEAD
|
||||
U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2, // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING
|
||||
U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3, // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING
|
||||
U_MODIFIER_LETTER_UP_TACK = 0x02D4, // U+02D4 MODIFIER LETTER UP TACK
|
||||
U_MODIFIER_LETTER_DOWN_TACK = 0x02D5, // U+02D5 MODIFIER LETTER DOWN TACK
|
||||
U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6, // U+02D6 MODIFIER LETTER PLUS SIGN
|
||||
U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7, // U+02D7 MODIFIER LETTER MINUS SIGN
|
||||
U_BREVE = 0x02D8, // U+02D8 BREVE
|
||||
U_DOT_ABOVE = 0x02D9, // U+02D9 DOT ABOVE
|
||||
U_RING_ABOVE = 0x02DA, // U+02DA RING ABOVE
|
||||
U_OGONEK = 0x02DB, // U+02DB OGONEK
|
||||
U_SMALL_TILDE = 0x02DC, // U+02DC SMALL TILDE
|
||||
U_DOUBLE_ACUTE_ACCENT = 0x02DD, // U+02DD DOUBLE ACUTE ACCENT
|
||||
U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE, // U+02DE MODIFIER LETTER RHOTIC HOOK
|
||||
U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF, // U+02DF MODIFIER LETTER CROSS ACCENT
|
||||
U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5, // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR
|
||||
U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6, // U+02E6 MODIFIER LETTER HIGH TONE BAR
|
||||
U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7, // U+02E7 MODIFIER LETTER MID TONE BAR
|
||||
U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8, // U+02E8 MODIFIER LETTER LOW TONE BAR
|
||||
U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9, // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR
|
||||
U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA, // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK
|
||||
U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK
|
||||
U_MODIFIER_LETTER_UNASPIRATED = 0x02ED, // U+02ED MODIFIER LETTER UNASPIRATED
|
||||
U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF, // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD
|
||||
U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0, // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD
|
||||
U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1, // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD
|
||||
U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2, // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD
|
||||
U_MODIFIER_LETTER_LOW_RING = 0x02F3, // U+02F3 MODIFIER LETTER LOW RING
|
||||
U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4, // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT
|
||||
U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT
|
||||
U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT
|
||||
U_MODIFIER_LETTER_LOW_TILDE = 0x02F7, // U+02F7 MODIFIER LETTER LOW TILDE
|
||||
U_MODIFIER_LETTER_RAISED_COLON = 0x02F8, // U+02F8 MODIFIER LETTER RAISED COLON
|
||||
U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9, // U+02F9 MODIFIER LETTER BEGIN HIGH TONE
|
||||
U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA, // U+02FA MODIFIER LETTER END HIGH TONE
|
||||
U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB, // U+02FB MODIFIER LETTER BEGIN LOW TONE
|
||||
U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC, // U+02FC MODIFIER LETTER END LOW TONE
|
||||
U_MODIFIER_LETTER_SHELF = 0x02FD, // U+02FD MODIFIER LETTER SHELF
|
||||
U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE, // U+02FE MODIFIER LETTER OPEN SHELF
|
||||
U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF, // U+02FF MODIFIER LETTER LOW LEFT ARROW
|
||||
U_GREEK_LOWER_NUMERAL_SIGN = 0x0375, // U+0375 GREEK LOWER NUMERAL SIGN
|
||||
U_GREEK_TONOS = 0x0384, // U+0384 GREEK TONOS
|
||||
U_GREEK_DIALYTIKA_TONOS = 0x0385, // U+0385 GREEK DIALYTIKA TONOS
|
||||
U_GREEK_KORONIS = 0x1FBD, // U+1FBD GREEK KORONIS
|
||||
U_GREEK_PSILI = 0x1FBF, // U+1FBF GREEK PSILI
|
||||
U_GREEK_PERISPOMENI = 0x1FC0, // U+1FC0 GREEK PERISPOMENI
|
||||
U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1, // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI
|
||||
U_GREEK_PSILI_AND_VARIA = 0x1FCD, // U+1FCD GREEK PSILI AND VARIA
|
||||
U_GREEK_PSILI_AND_OXIA = 0x1FCE, // U+1FCE GREEK PSILI AND OXIA
|
||||
U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF, // U+1FCF GREEK PSILI AND PERISPOMENI
|
||||
U_GREEK_DASIA_AND_VARIA = 0x1FDD, // U+1FDD GREEK DASIA AND VARIA
|
||||
U_GREEK_DASIA_AND_OXIA = 0x1FDE, // U+1FDE GREEK DASIA AND OXIA
|
||||
U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF, // U+1FDF GREEK DASIA AND PERISPOMENI
|
||||
U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED, // U+1FED GREEK DIALYTIKA AND VARIA
|
||||
U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE, // U+1FEE GREEK DIALYTIKA AND OXIA
|
||||
U_GREEK_VARIA = 0x1FEF, // U+1FEF GREEK VARIA
|
||||
U_GREEK_OXIA = 0x1FFD, // U+1FFD GREEK OXIA
|
||||
U_GREEK_DASIA = 0x1FFE, // U+1FFE GREEK DASIA
|
||||
|
||||
|
||||
U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE'
|
||||
|
||||
/**
|
||||
* UTF-8 BOM
|
||||
* Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF)
|
||||
* http://www.fileformat.info/info/unicode/char/feff/index.htm
|
||||
*/
|
||||
UTF8_BOM = 65279
|
||||
}
|
||||
94
src/vs/base/common/collections.ts
Normal file
94
src/vs/base/common/collections.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* An interface for a JavaScript object that
|
||||
* acts a dictionary. The keys are strings.
|
||||
*/
|
||||
export interface IStringDictionary<V> {
|
||||
[name: string]: V;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for a JavaScript object that
|
||||
* acts a dictionary. The keys are numbers.
|
||||
*/
|
||||
export interface INumberDictionary<V> {
|
||||
[idx: number]: V;
|
||||
}
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* Returns an array which contains all values that reside
|
||||
* in the given set.
|
||||
*/
|
||||
export function values<T>(from: IStringDictionary<T> | INumberDictionary<T>): T[] {
|
||||
const result: T[] = [];
|
||||
for (var key in from) {
|
||||
if (hasOwnProperty.call(from, key)) {
|
||||
result.push(from[key]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function size<T>(from: IStringDictionary<T> | INumberDictionary<T>): number {
|
||||
let count = 0;
|
||||
for (var key in from) {
|
||||
if (hasOwnProperty.call(from, key)) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over each entry in the provided set. The iterator allows
|
||||
* to remove elements and will stop when the callback returns {{false}}.
|
||||
*/
|
||||
export function forEach<T>(from: IStringDictionary<T> | INumberDictionary<T>, callback: (entry: { key: any; value: T; }, remove: Function) => any): void {
|
||||
for (let key in from) {
|
||||
if (hasOwnProperty.call(from, key)) {
|
||||
const result = callback({ key: key, value: from[key] }, function () {
|
||||
delete from[key];
|
||||
});
|
||||
if (result === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an element from the dictionary. Returns {{false}} if the property
|
||||
* does not exists.
|
||||
*/
|
||||
export function remove<T>(from: IStringDictionary<T> | INumberDictionary<T>, key: string): boolean {
|
||||
if (!hasOwnProperty.call(from, key)) {
|
||||
return false;
|
||||
}
|
||||
delete from[key];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups the collection into a dictionary based on the provided
|
||||
* group function.
|
||||
*/
|
||||
export function groupBy<T>(data: T[], groupFn: (element: T) => string): IStringDictionary<T[]> {
|
||||
const result: IStringDictionary<T[]> = Object.create(null);
|
||||
for (const element of data) {
|
||||
const key = groupFn(element);
|
||||
let target = result[key];
|
||||
if (!target) {
|
||||
target = result[key] = [];
|
||||
}
|
||||
target.push(element);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
595
src/vs/base/common/color.ts
Normal file
595
src/vs/base/common/color.ts
Normal file
@@ -0,0 +1,595 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
function roundFloat(number: number, decimalPoints: number): number {
|
||||
const decimal = Math.pow(10, decimalPoints);
|
||||
return Math.round(number * decimal) / decimal;
|
||||
}
|
||||
|
||||
export class RGBA {
|
||||
_rgbaBrand: void;
|
||||
|
||||
/**
|
||||
* Red: integer in [0-255]
|
||||
*/
|
||||
readonly r: number;
|
||||
|
||||
/**
|
||||
* Green: integer in [0-255]
|
||||
*/
|
||||
readonly g: number;
|
||||
|
||||
/**
|
||||
* Blue: integer in [0-255]
|
||||
*/
|
||||
readonly b: number;
|
||||
|
||||
/**
|
||||
* Alpha: float in [0-1]
|
||||
*/
|
||||
readonly a: number;
|
||||
|
||||
constructor(r: number, g: number, b: number, a: number = 1) {
|
||||
this.r = Math.min(255, Math.max(0, r)) | 0;
|
||||
this.g = Math.min(255, Math.max(0, g)) | 0;
|
||||
this.b = Math.min(255, Math.max(0, b)) | 0;
|
||||
this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
|
||||
}
|
||||
|
||||
static equals(a: RGBA, b: RGBA): boolean {
|
||||
return a.r === b.r && a.g === b.g && a.b === b.b && a.a === b.a;
|
||||
}
|
||||
}
|
||||
|
||||
export class HSLA {
|
||||
|
||||
_hslaBrand: void;
|
||||
|
||||
/**
|
||||
* Hue: integer in [0, 360]
|
||||
*/
|
||||
readonly h: number;
|
||||
|
||||
/**
|
||||
* Saturation: float in [0, 1]
|
||||
*/
|
||||
readonly s: number;
|
||||
|
||||
/**
|
||||
* Luminosity: float in [0, 1]
|
||||
*/
|
||||
readonly l: number;
|
||||
|
||||
/**
|
||||
* Alpha: float in [0, 1]
|
||||
*/
|
||||
readonly a: number;
|
||||
|
||||
constructor(h: number, s: number, l: number, a: number) {
|
||||
this.h = Math.max(Math.min(360, h), 0) | 0;
|
||||
this.s = roundFloat(Math.max(Math.min(1, s), 0), 3);
|
||||
this.l = roundFloat(Math.max(Math.min(1, l), 0), 3);
|
||||
this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
|
||||
}
|
||||
|
||||
static equals(a: HSLA, b: HSLA): boolean {
|
||||
return a.h === b.h && a.s === b.s && a.l === b.l && a.a === b.a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an RGB color value to HSL. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes r, g, and b are contained in the set [0, 255] and
|
||||
* returns h in the set [0, 360], s, and l in the set [0, 1].
|
||||
*/
|
||||
static fromRGBA(rgba: RGBA): HSLA {
|
||||
const r = rgba.r / 255;
|
||||
const g = rgba.g / 255;
|
||||
const b = rgba.b / 255;
|
||||
const a = rgba.a;
|
||||
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
let h = 0;
|
||||
let s = 0;
|
||||
const l = (min + max) / 2;
|
||||
const chroma = max - min;
|
||||
|
||||
if (chroma > 0) {
|
||||
s = Math.min((l <= 0.5 ? chroma / (2 * l) : chroma / (2 - (2 * l))), 1);
|
||||
|
||||
switch (max) {
|
||||
case r: h = (g - b) / chroma + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / chroma + 2; break;
|
||||
case b: h = (r - g) / chroma + 4; break;
|
||||
}
|
||||
|
||||
h *= 60;
|
||||
h = Math.round(h);
|
||||
}
|
||||
return new HSLA(h, s, l, a);
|
||||
}
|
||||
|
||||
private static _hue2rgb(p: number, q: number, t: number) {
|
||||
if (t < 0) {
|
||||
t += 1;
|
||||
}
|
||||
if (t > 1) {
|
||||
t -= 1;
|
||||
}
|
||||
if (t < 1 / 6) {
|
||||
return p + (q - p) * 6 * t;
|
||||
}
|
||||
if (t < 1 / 2) {
|
||||
return q;
|
||||
}
|
||||
if (t < 2 / 3) {
|
||||
return p + (q - p) * (2 / 3 - t) * 6;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HSL color value to RGB. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes h in the set [0, 360] s, and l are contained in the set [0, 1] and
|
||||
* returns r, g, and b in the set [0, 255].
|
||||
*/
|
||||
static toRGBA(hsla: HSLA): RGBA {
|
||||
const h = hsla.h / 360;
|
||||
const { s, l, a } = hsla;
|
||||
let r: number, g: number, b: number;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
const p = 2 * l - q;
|
||||
r = HSLA._hue2rgb(p, q, h + 1 / 3);
|
||||
g = HSLA._hue2rgb(p, q, h);
|
||||
b = HSLA._hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
|
||||
return new RGBA(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a);
|
||||
}
|
||||
}
|
||||
|
||||
export class HSVA {
|
||||
|
||||
_hsvaBrand: void;
|
||||
|
||||
/**
|
||||
* Hue: integer in [0, 360]
|
||||
*/
|
||||
readonly h: number;
|
||||
|
||||
/**
|
||||
* Saturation: float in [0, 1]
|
||||
*/
|
||||
readonly s: number;
|
||||
|
||||
/**
|
||||
* Value: float in [0, 1]
|
||||
*/
|
||||
readonly v: number;
|
||||
|
||||
/**
|
||||
* Alpha: float in [0, 1]
|
||||
*/
|
||||
readonly a: number;
|
||||
|
||||
constructor(h: number, s: number, v: number, a: number) {
|
||||
this.h = Math.max(Math.min(360, h), 0) | 0;
|
||||
this.s = roundFloat(Math.max(Math.min(1, s), 0), 3);
|
||||
this.v = roundFloat(Math.max(Math.min(1, v), 0), 3);
|
||||
this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
|
||||
}
|
||||
|
||||
static equals(a: HSVA, b: HSVA): boolean {
|
||||
return a.h === b.h && a.s === b.s && a.v === b.v && a.a === b.a;
|
||||
}
|
||||
|
||||
// from http://www.rapidtables.com/convert/color/rgb-to-hsv.htm
|
||||
static fromRGBA(rgba: RGBA): HSVA {
|
||||
const r = rgba.r / 255;
|
||||
const g = rgba.g / 255;
|
||||
const b = rgba.b / 255;
|
||||
const cmax = Math.max(r, g, b);
|
||||
const cmin = Math.min(r, g, b);
|
||||
const delta = cmax - cmin;
|
||||
const s = cmax === 0 ? 0 : (delta / cmax);
|
||||
let m: number;
|
||||
|
||||
if (delta === 0) {
|
||||
m = 0;
|
||||
} else if (cmax === r) {
|
||||
m = ((((g - b) / delta) % 6) + 6) % 6;
|
||||
} else if (cmax === g) {
|
||||
m = ((b - r) / delta) + 2;
|
||||
} else {
|
||||
m = ((r - g) / delta) + 4;
|
||||
}
|
||||
|
||||
return new HSVA(m * 60, s, cmax, rgba.a);
|
||||
}
|
||||
|
||||
// from http://www.rapidtables.com/convert/color/hsv-to-rgb.htm
|
||||
static toRGBA(hsva: HSVA): RGBA {
|
||||
const { h, s, v, a } = hsva;
|
||||
const c = v * s;
|
||||
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
|
||||
const m = v - c;
|
||||
let [r, g, b] = [0, 0, 0];
|
||||
|
||||
if (h < 60) {
|
||||
r = c;
|
||||
g = x;
|
||||
} else if (h < 120) {
|
||||
r = x;
|
||||
g = c;
|
||||
} else if (h < 180) {
|
||||
g = c;
|
||||
b = x;
|
||||
} else if (h < 240) {
|
||||
g = x;
|
||||
b = c;
|
||||
} else if (h < 300) {
|
||||
r = x;
|
||||
b = c;
|
||||
} else if (h < 360) {
|
||||
r = c;
|
||||
b = x;
|
||||
}
|
||||
|
||||
r = Math.round((r + m) * 255);
|
||||
g = Math.round((g + m) * 255);
|
||||
b = Math.round((b + m) * 255);
|
||||
|
||||
return new RGBA(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
export class Color {
|
||||
|
||||
static fromHex(hex: string): Color {
|
||||
return Color.Format.CSS.parseHex(hex) || Color.red;
|
||||
}
|
||||
|
||||
readonly rgba: RGBA;
|
||||
private _hsla: HSLA;
|
||||
get hsla(): HSLA {
|
||||
if (this._hsla) {
|
||||
return this._hsla;
|
||||
} else {
|
||||
return HSLA.fromRGBA(this.rgba);
|
||||
}
|
||||
}
|
||||
|
||||
private _hsva: HSVA;
|
||||
get hsva(): HSVA {
|
||||
if (this._hsva) {
|
||||
return this._hsva;
|
||||
}
|
||||
return HSVA.fromRGBA(this.rgba);
|
||||
}
|
||||
|
||||
constructor(arg: RGBA | HSLA | HSVA) {
|
||||
if (!arg) {
|
||||
throw new Error('Color needs a value');
|
||||
} else if (arg instanceof RGBA) {
|
||||
this.rgba = arg;
|
||||
} else if (arg instanceof HSLA) {
|
||||
this._hsla = arg;
|
||||
this.rgba = HSLA.toRGBA(arg);
|
||||
} else if (arg instanceof HSVA) {
|
||||
this._hsva = arg;
|
||||
this.rgba = HSVA.toRGBA(arg);
|
||||
} else {
|
||||
throw new Error('Invalid color ctor argument');
|
||||
}
|
||||
}
|
||||
|
||||
equals(other: Color): boolean {
|
||||
return !!other && RGBA.equals(this.rgba, other.rgba) && HSLA.equals(this.hsla, other.hsla) && HSVA.equals(this.hsva, other.hsva);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
* Returns the number in the set [0, 1]. O => Darkest Black. 1 => Lightest white.
|
||||
*/
|
||||
getRelativeLuminance(): number {
|
||||
const R = Color._relativeLuminanceForComponent(this.rgba.r);
|
||||
const G = Color._relativeLuminanceForComponent(this.rgba.g);
|
||||
const B = Color._relativeLuminanceForComponent(this.rgba.b);
|
||||
const luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
||||
|
||||
return roundFloat(luminance, 4);
|
||||
}
|
||||
|
||||
private static _relativeLuminanceForComponent(color: number): number {
|
||||
const c = color / 255;
|
||||
return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
||||
* Returns the contrast ration number in the set [1, 21].
|
||||
*/
|
||||
getContrastRatio(another: Color): number {
|
||||
const lum1 = this.getRelativeLuminance();
|
||||
const lum2 = another.getRelativeLuminance();
|
||||
return lum1 > lum2 ? (lum1 + 0.05) / (lum2 + 0.05) : (lum2 + 0.05) / (lum1 + 0.05);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://24ways.org/2010/calculating-color-contrast
|
||||
* Return 'true' if darker color otherwise 'false'
|
||||
*/
|
||||
isDarker(): boolean {
|
||||
const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
|
||||
return yiq < 128;
|
||||
}
|
||||
|
||||
/**
|
||||
* http://24ways.org/2010/calculating-color-contrast
|
||||
* Return 'true' if lighter color otherwise 'false'
|
||||
*/
|
||||
isLighter(): boolean {
|
||||
const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
|
||||
return yiq >= 128;
|
||||
}
|
||||
|
||||
isLighterThan(another: Color): boolean {
|
||||
const lum1 = this.getRelativeLuminance();
|
||||
const lum2 = another.getRelativeLuminance();
|
||||
return lum1 > lum2;
|
||||
}
|
||||
|
||||
isDarkerThan(another: Color): boolean {
|
||||
const lum1 = this.getRelativeLuminance();
|
||||
const lum2 = another.getRelativeLuminance();
|
||||
return lum1 < lum2;
|
||||
}
|
||||
|
||||
lighten(factor: number): Color {
|
||||
return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l + this.hsla.l * factor, this.hsla.a));
|
||||
}
|
||||
|
||||
darken(factor: number): Color {
|
||||
return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l - this.hsla.l * factor, this.hsla.a));
|
||||
}
|
||||
|
||||
transparent(factor: number): Color {
|
||||
const { r, g, b, a } = this.rgba;
|
||||
return new Color(new RGBA(r, g, b, a * factor));
|
||||
}
|
||||
|
||||
isTransparent(): boolean {
|
||||
return this.rgba.a === 0;
|
||||
}
|
||||
|
||||
isOpaque(): boolean {
|
||||
return this.rgba.a === 1;
|
||||
}
|
||||
|
||||
opposite(): Color {
|
||||
return new Color(new RGBA(255 - this.rgba.r, 255 - this.rgba.g, 255 - this.rgba.b, this.rgba.a));
|
||||
}
|
||||
|
||||
blend(c: Color): Color {
|
||||
const rgba = c.rgba;
|
||||
|
||||
// Convert to 0..1 opacity
|
||||
const thisA = this.rgba.a;
|
||||
const colorA = rgba.a;
|
||||
|
||||
let a = thisA + colorA * (1 - thisA);
|
||||
if (a < 1.0e-6) {
|
||||
return Color.transparent;
|
||||
}
|
||||
|
||||
const r = this.rgba.r * thisA / a + rgba.r * colorA * (1 - thisA) / a;
|
||||
const g = this.rgba.g * thisA / a + rgba.g * colorA * (1 - thisA) / a;
|
||||
const b = this.rgba.b * thisA / a + rgba.b * colorA * (1 - thisA) / a;
|
||||
|
||||
return new Color(new RGBA(r, g, b, a));
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return Color.Format.CSS.format(this);
|
||||
}
|
||||
|
||||
static getLighterColor(of: Color, relative: Color, factor?: number): Color {
|
||||
if (of.isLighterThan(relative)) {
|
||||
return of;
|
||||
}
|
||||
factor = factor ? factor : 0.5;
|
||||
const lum1 = of.getRelativeLuminance();
|
||||
const lum2 = relative.getRelativeLuminance();
|
||||
factor = factor * (lum2 - lum1) / lum2;
|
||||
return of.lighten(factor);
|
||||
}
|
||||
|
||||
static getDarkerColor(of: Color, relative: Color, factor?: number): Color {
|
||||
if (of.isDarkerThan(relative)) {
|
||||
return of;
|
||||
}
|
||||
factor = factor ? factor : 0.5;
|
||||
const lum1 = of.getRelativeLuminance();
|
||||
const lum2 = relative.getRelativeLuminance();
|
||||
factor = factor * (lum1 - lum2) / lum1;
|
||||
return of.darken(factor);
|
||||
}
|
||||
|
||||
static readonly white = new Color(new RGBA(255, 255, 255, 1));
|
||||
static readonly black = new Color(new RGBA(0, 0, 0, 1));
|
||||
static readonly red = new Color(new RGBA(255, 0, 0, 1));
|
||||
static readonly blue = new Color(new RGBA(0, 0, 255, 1));
|
||||
static readonly green = new Color(new RGBA(0, 255, 0, 1));
|
||||
static readonly cyan = new Color(new RGBA(0, 255, 255, 1));
|
||||
static readonly lightgrey = new Color(new RGBA(211, 211, 211, 1));
|
||||
static readonly transparent = new Color(new RGBA(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
export namespace Color {
|
||||
export namespace Format {
|
||||
export namespace CSS {
|
||||
|
||||
export function formatRGB(color: Color): string {
|
||||
if (color.rgba.a === 1) {
|
||||
return `rgb(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b})`;
|
||||
}
|
||||
|
||||
return Color.Format.CSS.formatRGBA(color);
|
||||
}
|
||||
|
||||
export function formatRGBA(color: Color): string {
|
||||
return `rgba(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b}, ${+(color.rgba.a).toFixed(2)})`;
|
||||
}
|
||||
|
||||
export function formatHSL(color: Color): string {
|
||||
if (color.hsla.a === 1) {
|
||||
return `hsl(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%)`;
|
||||
}
|
||||
|
||||
return Color.Format.CSS.formatHSLA(color);
|
||||
}
|
||||
|
||||
export function formatHSLA(color: Color): string {
|
||||
return `hsla(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%, ${color.hsla.a.toFixed(2)})`;
|
||||
}
|
||||
|
||||
function _toTwoDigitHex(n: number): string {
|
||||
const r = n.toString(16);
|
||||
return r.length !== 2 ? '0' + r : r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the color as #RRGGBB
|
||||
*/
|
||||
export function formatHex(color: Color): string {
|
||||
return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the color as #RRGGBBAA
|
||||
* If 'compact' is set, colors without transparancy will be printed as #RRGGBB
|
||||
*/
|
||||
export function formatHexA(color: Color, compact = false): string {
|
||||
if (compact && color.rgba.a === 1) {
|
||||
return Color.Format.CSS.formatHex(color);
|
||||
}
|
||||
|
||||
return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}${_toTwoDigitHex(Math.round(color.rgba.a * 255))}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default format will use HEX if opaque and RGBA otherwise.
|
||||
*/
|
||||
export function format(color: Color): string | null {
|
||||
if (!color) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (color.isOpaque()) {
|
||||
return Color.Format.CSS.formatHex(color);
|
||||
}
|
||||
|
||||
return Color.Format.CSS.formatRGBA(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Hex color value to a Color.
|
||||
* returns r, g, and b are contained in the set [0, 255]
|
||||
* @param hex string (#RGB, #RGBA, #RRGGBB or #RRGGBBAA).
|
||||
*/
|
||||
export function parseHex(hex: string): Color | null {
|
||||
if (!hex) {
|
||||
// Invalid color
|
||||
return null;
|
||||
}
|
||||
|
||||
const length = hex.length;
|
||||
|
||||
if (length === 0) {
|
||||
// Invalid color
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hex.charCodeAt(0) !== CharCode.Hash) {
|
||||
// Does not begin with a #
|
||||
return null;
|
||||
}
|
||||
|
||||
if (length === 7) {
|
||||
// #RRGGBB format
|
||||
const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2));
|
||||
const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4));
|
||||
const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6));
|
||||
return new Color(new RGBA(r, g, b, 1));
|
||||
}
|
||||
|
||||
if (length === 9) {
|
||||
// #RRGGBBAA format
|
||||
const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2));
|
||||
const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4));
|
||||
const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6));
|
||||
const a = 16 * _parseHexDigit(hex.charCodeAt(7)) + _parseHexDigit(hex.charCodeAt(8));
|
||||
return new Color(new RGBA(r, g, b, a / 255));
|
||||
}
|
||||
|
||||
if (length === 4) {
|
||||
// #RGB format
|
||||
const r = _parseHexDigit(hex.charCodeAt(1));
|
||||
const g = _parseHexDigit(hex.charCodeAt(2));
|
||||
const b = _parseHexDigit(hex.charCodeAt(3));
|
||||
return new Color(new RGBA(16 * r + r, 16 * g + g, 16 * b + b));
|
||||
}
|
||||
|
||||
if (length === 5) {
|
||||
// #RGBA format
|
||||
const r = _parseHexDigit(hex.charCodeAt(1));
|
||||
const g = _parseHexDigit(hex.charCodeAt(2));
|
||||
const b = _parseHexDigit(hex.charCodeAt(3));
|
||||
const a = _parseHexDigit(hex.charCodeAt(4));
|
||||
return new Color(new RGBA(16 * r + r, 16 * g + g, 16 * b + b, (16 * a + a) / 255));
|
||||
}
|
||||
|
||||
// Invalid color
|
||||
return null;
|
||||
}
|
||||
|
||||
function _parseHexDigit(charCode: CharCode): number {
|
||||
switch (charCode) {
|
||||
case CharCode.Digit0: return 0;
|
||||
case CharCode.Digit1: return 1;
|
||||
case CharCode.Digit2: return 2;
|
||||
case CharCode.Digit3: return 3;
|
||||
case CharCode.Digit4: return 4;
|
||||
case CharCode.Digit5: return 5;
|
||||
case CharCode.Digit6: return 6;
|
||||
case CharCode.Digit7: return 7;
|
||||
case CharCode.Digit8: return 8;
|
||||
case CharCode.Digit9: return 9;
|
||||
case CharCode.a: return 10;
|
||||
case CharCode.A: return 10;
|
||||
case CharCode.b: return 11;
|
||||
case CharCode.B: return 11;
|
||||
case CharCode.c: return 12;
|
||||
case CharCode.C: return 12;
|
||||
case CharCode.d: return 13;
|
||||
case CharCode.D: return 13;
|
||||
case CharCode.e: return 14;
|
||||
case CharCode.E: return 14;
|
||||
case CharCode.f: return 15;
|
||||
case CharCode.F: return 15;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
241
src/vs/base/common/comparers.ts
Normal file
241
src/vs/base/common/comparers.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import scorer = require('vs/base/common/scorer');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
|
||||
let intlFileNameCollator: Intl.Collator;
|
||||
let intlFileNameCollatorIsNumeric: boolean;
|
||||
|
||||
export function setFileNameComparer(collator: Intl.Collator): void {
|
||||
intlFileNameCollator = collator;
|
||||
intlFileNameCollatorIsNumeric = collator.resolvedOptions().numeric;
|
||||
}
|
||||
|
||||
export function compareFileNames(one: string, other: string): number {
|
||||
if (intlFileNameCollator) {
|
||||
const a = one || '';
|
||||
const b = other || '';
|
||||
const result = intlFileNameCollator.compare(a, b);
|
||||
|
||||
// Using the numeric option in the collator will
|
||||
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
|
||||
if (intlFileNameCollatorIsNumeric && result === 0 && a !== b) {
|
||||
return a < b ? -1 : 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return noIntlCompareFileNames(one, other);
|
||||
}
|
||||
|
||||
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
|
||||
|
||||
export function noIntlCompareFileNames(one: string, other: string): number {
|
||||
let oneMatch = FileNameMatch.exec(one.toLowerCase());
|
||||
let otherMatch = FileNameMatch.exec(other.toLowerCase());
|
||||
|
||||
let oneName = oneMatch[1] || '';
|
||||
let oneExtension = oneMatch[3] || '';
|
||||
|
||||
let otherName = otherMatch[1] || '';
|
||||
let otherExtension = otherMatch[3] || '';
|
||||
|
||||
if (oneName !== otherName) {
|
||||
return oneName < otherName ? -1 : 1;
|
||||
}
|
||||
|
||||
if (oneExtension === otherExtension) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return oneExtension < otherExtension ? -1 : 1;
|
||||
}
|
||||
|
||||
export function compareFileExtensions(one: string, other: string): number {
|
||||
if (intlFileNameCollator) {
|
||||
const oneMatch = one ? FileNameMatch.exec(one) : [];
|
||||
const otherMatch = other ? FileNameMatch.exec(other) : [];
|
||||
|
||||
const oneName = oneMatch[1] || '';
|
||||
const oneExtension = oneMatch[3] || '';
|
||||
|
||||
const otherName = otherMatch[1] || '';
|
||||
const otherExtension = otherMatch[3] || '';
|
||||
|
||||
let result = intlFileNameCollator.compare(oneExtension, otherExtension);
|
||||
|
||||
if (result === 0) {
|
||||
// Using the numeric option in the collator will
|
||||
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
|
||||
if (intlFileNameCollatorIsNumeric && oneExtension !== otherExtension) {
|
||||
return oneExtension < otherExtension ? -1 : 1;
|
||||
}
|
||||
|
||||
// Extensions are equal, compare filenames
|
||||
result = intlFileNameCollator.compare(oneName, otherName);
|
||||
|
||||
if (intlFileNameCollatorIsNumeric && result === 0 && oneName !== otherName) {
|
||||
return oneName < otherName ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return noIntlCompareFileExtensions(one, other);
|
||||
}
|
||||
|
||||
function noIntlCompareFileExtensions(one: string, other: string): number {
|
||||
const oneMatch = one ? FileNameMatch.exec(one.toLowerCase()) : [];
|
||||
const otherMatch = other ? FileNameMatch.exec(other.toLowerCase()) : [];
|
||||
|
||||
const oneName = oneMatch[1] || '';
|
||||
const oneExtension = oneMatch[3] || '';
|
||||
|
||||
const otherName = otherMatch[1] || '';
|
||||
const otherExtension = otherMatch[3] || '';
|
||||
|
||||
if (oneExtension !== otherExtension) {
|
||||
return oneExtension < otherExtension ? -1 : 1;
|
||||
}
|
||||
|
||||
if (oneName === otherName) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return oneName < otherName ? -1 : 1;
|
||||
}
|
||||
|
||||
export function comparePaths(one: string, other: string): number {
|
||||
const oneParts = one.split(paths.nativeSep);
|
||||
const otherParts = other.split(paths.nativeSep);
|
||||
|
||||
const lastOne = oneParts.length - 1;
|
||||
const lastOther = otherParts.length - 1;
|
||||
let endOne: boolean, endOther: boolean, onePart: string, otherPart: string;
|
||||
|
||||
for (let i = 0; ; i++) {
|
||||
endOne = lastOne === i;
|
||||
endOther = lastOther === i;
|
||||
|
||||
if (endOne && endOther) {
|
||||
return compareFileNames(oneParts[i], otherParts[i]);
|
||||
} else if (endOne) {
|
||||
return -1;
|
||||
} else if (endOther) {
|
||||
return 1;
|
||||
} else if ((onePart = oneParts[i].toLowerCase()) !== (otherPart = otherParts[i].toLowerCase())) {
|
||||
return onePart < otherPart ? -1 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function compareAnything(one: string, other: string, lookFor: string): number {
|
||||
let elementAName = one.toLowerCase();
|
||||
let elementBName = other.toLowerCase();
|
||||
|
||||
// Sort prefix matches over non prefix matches
|
||||
const prefixCompare = compareByPrefix(one, other, lookFor);
|
||||
if (prefixCompare) {
|
||||
return prefixCompare;
|
||||
}
|
||||
|
||||
// Sort suffix matches over non suffix matches
|
||||
let elementASuffixMatch = strings.endsWith(elementAName, lookFor);
|
||||
let elementBSuffixMatch = strings.endsWith(elementBName, lookFor);
|
||||
if (elementASuffixMatch !== elementBSuffixMatch) {
|
||||
return elementASuffixMatch ? -1 : 1;
|
||||
}
|
||||
|
||||
// Understand file names
|
||||
let r = compareFileNames(elementAName, elementBName);
|
||||
if (r !== 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// Compare by name
|
||||
return elementAName.localeCompare(elementBName);
|
||||
}
|
||||
|
||||
export function compareByPrefix(one: string, other: string, lookFor: string): number {
|
||||
let elementAName = one.toLowerCase();
|
||||
let elementBName = other.toLowerCase();
|
||||
|
||||
// Sort prefix matches over non prefix matches
|
||||
let elementAPrefixMatch = strings.startsWith(elementAName, lookFor);
|
||||
let elementBPrefixMatch = strings.startsWith(elementBName, lookFor);
|
||||
if (elementAPrefixMatch !== elementBPrefixMatch) {
|
||||
return elementAPrefixMatch ? -1 : 1;
|
||||
}
|
||||
|
||||
// Same prefix: Sort shorter matches to the top to have those on top that match more precisely
|
||||
else if (elementAPrefixMatch && elementBPrefixMatch) {
|
||||
if (elementAName.length < elementBName.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (elementAName.length > elementBName.length) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export interface IScorableResourceAccessor<T> {
|
||||
getLabel(t: T): string;
|
||||
getResourcePath(t: T): string;
|
||||
}
|
||||
|
||||
export function compareByScore<T>(elementA: T, elementB: T, accessor: IScorableResourceAccessor<T>, lookFor: string, lookForNormalizedLower: string, scorerCache?: { [key: string]: number }): number {
|
||||
const labelA = accessor.getLabel(elementA);
|
||||
const labelB = accessor.getLabel(elementB);
|
||||
|
||||
// treat prefix matches highest in any case
|
||||
const prefixCompare = compareByPrefix(labelA, labelB, lookFor);
|
||||
if (prefixCompare) {
|
||||
return prefixCompare;
|
||||
}
|
||||
|
||||
// Give higher importance to label score
|
||||
const labelAScore = scorer.score(labelA, lookFor, scorerCache);
|
||||
const labelBScore = scorer.score(labelB, lookFor, scorerCache);
|
||||
|
||||
if (labelAScore !== labelBScore) {
|
||||
return labelAScore > labelBScore ? -1 : 1;
|
||||
}
|
||||
|
||||
// Score on full resource path comes next (if available)
|
||||
let resourcePathA = accessor.getResourcePath(elementA);
|
||||
let resourcePathB = accessor.getResourcePath(elementB);
|
||||
if (resourcePathA && resourcePathB) {
|
||||
const resourceAScore = scorer.score(resourcePathA, lookFor, scorerCache);
|
||||
const resourceBScore = scorer.score(resourcePathB, lookFor, scorerCache);
|
||||
|
||||
if (resourceAScore !== resourceBScore) {
|
||||
return resourceAScore > resourceBScore ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// At this place, the scores are identical so we check for string lengths and favor shorter ones
|
||||
if (labelA.length !== labelB.length) {
|
||||
return labelA.length < labelB.length ? -1 : 1;
|
||||
}
|
||||
|
||||
if (resourcePathA && resourcePathB && resourcePathA.length !== resourcePathB.length) {
|
||||
return resourcePathA.length < resourcePathB.length ? -1 : 1;
|
||||
}
|
||||
|
||||
// Finally compare by label or resource path
|
||||
if (labelA === labelB && resourcePathA && resourcePathB) {
|
||||
return compareAnything(resourcePathA, resourcePathB, lookForNormalizedLower);
|
||||
}
|
||||
|
||||
return compareAnything(labelA, labelB, lookForNormalizedLower);
|
||||
}
|
||||
59
src/vs/base/common/decorators.ts
Normal file
59
src/vs/base/common/decorators.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export function createDecorator(mapFn: (fn: Function) => Function): Function {
|
||||
return (target: any, key: string, descriptor: any) => {
|
||||
let fnKey: string = null;
|
||||
let fn: Function = null;
|
||||
|
||||
if (typeof descriptor.value === 'function') {
|
||||
fnKey = 'value';
|
||||
fn = descriptor.value;
|
||||
} else if (typeof descriptor.get === 'function') {
|
||||
fnKey = 'get';
|
||||
fn = descriptor.get;
|
||||
}
|
||||
|
||||
if (!fn) {
|
||||
throw new Error('not supported');
|
||||
}
|
||||
|
||||
descriptor[fnKey] = mapFn(fn);
|
||||
};
|
||||
}
|
||||
|
||||
export function memoize(target: any, key: string, descriptor: any) {
|
||||
let fnKey: string = null;
|
||||
let fn: Function = null;
|
||||
|
||||
if (typeof descriptor.value === 'function') {
|
||||
fnKey = 'value';
|
||||
fn = descriptor.value;
|
||||
} else if (typeof descriptor.get === 'function') {
|
||||
fnKey = 'get';
|
||||
fn = descriptor.get;
|
||||
}
|
||||
|
||||
if (!fn) {
|
||||
throw new Error('not supported');
|
||||
}
|
||||
|
||||
const memoizeKey = `$memoize$${key}`;
|
||||
|
||||
descriptor[fnKey] = function (...args: any[]) {
|
||||
if (!this.hasOwnProperty(memoizeKey)) {
|
||||
Object.defineProperty(this, memoizeKey, {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: fn.apply(this, args)
|
||||
});
|
||||
}
|
||||
|
||||
return this[memoizeKey];
|
||||
};
|
||||
}
|
||||
88
src/vs/base/common/diagnostics.ts
Normal file
88
src/vs/base/common/diagnostics.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
|
||||
/**
|
||||
* To enable diagnostics, open a browser console and type: window.Monaco.Diagnostics.<diagnostics name> = true.
|
||||
* Then trigger an action that will write to diagnostics to see all cached output from the past.
|
||||
*/
|
||||
|
||||
var globals = Platform.globals;
|
||||
if (!globals.Monaco) {
|
||||
globals.Monaco = {};
|
||||
}
|
||||
globals.Monaco.Diagnostics = {};
|
||||
|
||||
var switches = globals.Monaco.Diagnostics;
|
||||
var map = new Map<string, Function[]>();
|
||||
var data: any[] = [];
|
||||
|
||||
function fifo(array: any[], size: number) {
|
||||
while (array.length > size) {
|
||||
array.shift();
|
||||
}
|
||||
}
|
||||
|
||||
export function register(what: string, fn: Function): (...args: any[]) => void {
|
||||
|
||||
let disable = true; // Otherwise we have unreachable code.
|
||||
if (disable) {
|
||||
return () => {
|
||||
// Intentional empty, disable for now because it is leaking memory
|
||||
};
|
||||
}
|
||||
|
||||
// register switch
|
||||
var flag = switches[what] || false;
|
||||
switches[what] = flag;
|
||||
|
||||
// register function
|
||||
var tracers = map.get(what) || [];
|
||||
tracers.push(fn);
|
||||
map.set(what, tracers);
|
||||
|
||||
var result = function (...args: any[]) {
|
||||
|
||||
var idx: number;
|
||||
|
||||
if (switches[what] === true) {
|
||||
// replay back-in-time functions
|
||||
var allArgs = [arguments];
|
||||
idx = data.indexOf(fn);
|
||||
if (idx !== -1) {
|
||||
allArgs.unshift.apply(allArgs, data[idx + 1] || []);
|
||||
data[idx + 1] = [];
|
||||
}
|
||||
|
||||
var doIt: () => void = function () {
|
||||
var thisArguments = allArgs.shift();
|
||||
fn.apply(fn, thisArguments);
|
||||
if (allArgs.length > 0) {
|
||||
Platform.setTimeout(doIt, 500);
|
||||
}
|
||||
};
|
||||
doIt();
|
||||
|
||||
} else {
|
||||
// know where to store
|
||||
idx = data.indexOf(fn);
|
||||
idx = idx !== -1 ? idx : data.length;
|
||||
var dataIdx = idx + 1;
|
||||
|
||||
// store arguments
|
||||
var allargs = data[dataIdx] || [];
|
||||
allargs.push(arguments);
|
||||
fifo(allargs, 50);
|
||||
|
||||
// store data
|
||||
data[idx] = fn;
|
||||
data[dataIdx] = allargs;
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
1034
src/vs/base/common/diff/diff.ts
Normal file
1034
src/vs/base/common/diff/diff.ts
Normal file
File diff suppressed because it is too large
Load Diff
334
src/vs/base/common/diff/diff2.ts
Normal file
334
src/vs/base/common/diff/diff2.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { DiffChange } from 'vs/base/common/diff/diffChange';
|
||||
|
||||
export interface ISequence {
|
||||
getLength(): number;
|
||||
getElementHash(index: number): string;
|
||||
}
|
||||
|
||||
export interface IDiffChange {
|
||||
/**
|
||||
* The position of the first element in the original sequence which
|
||||
* this change affects.
|
||||
*/
|
||||
originalStart: number;
|
||||
|
||||
/**
|
||||
* The number of elements from the original sequence which were
|
||||
* affected.
|
||||
*/
|
||||
originalLength: number;
|
||||
|
||||
/**
|
||||
* The position of the first element in the modified sequence which
|
||||
* this change affects.
|
||||
*/
|
||||
modifiedStart: number;
|
||||
|
||||
/**
|
||||
* The number of elements from the modified sequence which were
|
||||
* affected (added).
|
||||
*/
|
||||
modifiedLength: number;
|
||||
}
|
||||
|
||||
export interface IContinueProcessingPredicate {
|
||||
(furthestOriginalIndex: number, originalSequence: ISequence, matchLengthOfLongest: number): boolean;
|
||||
}
|
||||
|
||||
export interface IHashFunction {
|
||||
(sequence: ISequence, index: number): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of the difference algorithm described by Hirschberg
|
||||
*/
|
||||
export class LcsDiff2 {
|
||||
|
||||
private x: ISequence;
|
||||
private y: ISequence;
|
||||
|
||||
private ids_for_x: number[];
|
||||
private ids_for_y: number[];
|
||||
|
||||
private hashFunc: IHashFunction;
|
||||
|
||||
private resultX: boolean[];
|
||||
private resultY: boolean[];
|
||||
private forwardPrev: number[];
|
||||
private forwardCurr: number[];
|
||||
private backwardPrev: number[];
|
||||
private backwardCurr: number[];
|
||||
|
||||
constructor(originalSequence: ISequence, newSequence: ISequence, continueProcessingPredicate: IContinueProcessingPredicate, hashFunc: IHashFunction) {
|
||||
this.x = originalSequence;
|
||||
this.y = newSequence;
|
||||
this.ids_for_x = [];
|
||||
this.ids_for_y = [];
|
||||
|
||||
if (hashFunc) {
|
||||
this.hashFunc = hashFunc;
|
||||
} else {
|
||||
this.hashFunc = function (sequence, index) {
|
||||
return sequence[index];
|
||||
};
|
||||
}
|
||||
|
||||
this.resultX = [];
|
||||
this.resultY = [];
|
||||
this.forwardPrev = [];
|
||||
this.forwardCurr = [];
|
||||
this.backwardPrev = [];
|
||||
this.backwardCurr = [];
|
||||
|
||||
for (let i = 0, length = this.x.getLength(); i < length; i++) {
|
||||
this.resultX[i] = false;
|
||||
}
|
||||
|
||||
for (let i = 0, length = this.y.getLength(); i <= length; i++) {
|
||||
this.resultY[i] = false;
|
||||
}
|
||||
|
||||
this.ComputeUniqueIdentifiers();
|
||||
}
|
||||
|
||||
private ComputeUniqueIdentifiers() {
|
||||
let xLength = this.x.getLength();
|
||||
let yLength = this.y.getLength();
|
||||
this.ids_for_x = new Array<number>(xLength);
|
||||
this.ids_for_y = new Array<number>(yLength);
|
||||
|
||||
// Create a new hash table for unique elements from the original
|
||||
// sequence.
|
||||
let hashTable: { [key: string]: number; } = {};
|
||||
let currentUniqueId = 1;
|
||||
let i: number;
|
||||
|
||||
// Fill up the hash table for unique elements
|
||||
for (i = 0; i < xLength; i++) {
|
||||
let xElementHash = this.x.getElementHash(i);
|
||||
if (!hashTable.hasOwnProperty(xElementHash)) {
|
||||
// No entry in the hashtable so this is a new unique element.
|
||||
// Assign the element a new unique identifier and add it to the
|
||||
// hash table
|
||||
this.ids_for_x[i] = currentUniqueId++;
|
||||
hashTable[xElementHash] = this.ids_for_x[i];
|
||||
} else {
|
||||
this.ids_for_x[i] = hashTable[xElementHash];
|
||||
}
|
||||
}
|
||||
|
||||
// Now match up modified elements
|
||||
for (i = 0; i < yLength; i++) {
|
||||
let yElementHash = this.y.getElementHash(i);
|
||||
if (!hashTable.hasOwnProperty(yElementHash)) {
|
||||
this.ids_for_y[i] = currentUniqueId++;
|
||||
hashTable[yElementHash] = this.ids_for_y[i];
|
||||
} else {
|
||||
this.ids_for_y[i] = hashTable[yElementHash];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ElementsAreEqual(xIndex: number, yIndex: number): boolean {
|
||||
return this.ids_for_x[xIndex] === this.ids_for_y[yIndex];
|
||||
}
|
||||
|
||||
public ComputeDiff(): IDiffChange[] {
|
||||
let xLength = this.x.getLength();
|
||||
let yLength = this.y.getLength();
|
||||
|
||||
this.execute(0, xLength - 1, 0, yLength - 1);
|
||||
|
||||
// Construct the changes
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
let xChangeStart: number, yChangeStart: number;
|
||||
let changes: DiffChange[] = [];
|
||||
while (i < xLength && j < yLength) {
|
||||
if (this.resultX[i] && this.resultY[j]) {
|
||||
// No change
|
||||
i++;
|
||||
j++;
|
||||
} else {
|
||||
xChangeStart = i;
|
||||
yChangeStart = j;
|
||||
while (i < xLength && !this.resultX[i]) {
|
||||
i++;
|
||||
}
|
||||
while (j < yLength && !this.resultY[j]) {
|
||||
j++;
|
||||
}
|
||||
changes.push(new DiffChange(xChangeStart, i - xChangeStart, yChangeStart, j - yChangeStart));
|
||||
}
|
||||
}
|
||||
if (i < xLength) {
|
||||
changes.push(new DiffChange(i, xLength - i, yLength, 0));
|
||||
}
|
||||
if (j < yLength) {
|
||||
changes.push(new DiffChange(xLength, 0, j, yLength - j));
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
private forward(xStart: number, xStop: number, yStart: number, yStop: number): number[] {
|
||||
let prev = this.forwardPrev,
|
||||
curr = this.forwardCurr,
|
||||
tmp: number[],
|
||||
i: number,
|
||||
j: number;
|
||||
|
||||
// First line
|
||||
prev[yStart] = this.ElementsAreEqual(xStart, yStart) ? 1 : 0;
|
||||
for (j = yStart + 1; j <= yStop; j++) {
|
||||
prev[j] = this.ElementsAreEqual(xStart, j) ? 1 : prev[j - 1];
|
||||
}
|
||||
|
||||
for (i = xStart + 1; i <= xStop; i++) {
|
||||
// First column
|
||||
curr[yStart] = this.ElementsAreEqual(i, yStart) ? 1 : prev[yStart];
|
||||
|
||||
for (j = yStart + 1; j <= yStop; j++) {
|
||||
if (this.ElementsAreEqual(i, j)) {
|
||||
curr[j] = prev[j - 1] + 1;
|
||||
} else {
|
||||
curr[j] = prev[j] > curr[j - 1] ? prev[j] : curr[j - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Swap prev & curr
|
||||
tmp = curr;
|
||||
curr = prev;
|
||||
prev = tmp;
|
||||
}
|
||||
|
||||
// Result is always in prev
|
||||
return prev;
|
||||
}
|
||||
|
||||
private backward(xStart: number, xStop: number, yStart: number, yStop: number): number[] {
|
||||
let prev = this.backwardPrev,
|
||||
curr = this.backwardCurr,
|
||||
tmp: number[],
|
||||
i: number,
|
||||
j: number;
|
||||
|
||||
// Last line
|
||||
prev[yStop] = this.ElementsAreEqual(xStop, yStop) ? 1 : 0;
|
||||
for (j = yStop - 1; j >= yStart; j--) {
|
||||
prev[j] = this.ElementsAreEqual(xStop, j) ? 1 : prev[j + 1];
|
||||
}
|
||||
|
||||
for (i = xStop - 1; i >= xStart; i--) {
|
||||
// Last column
|
||||
curr[yStop] = this.ElementsAreEqual(i, yStop) ? 1 : prev[yStop];
|
||||
|
||||
for (j = yStop - 1; j >= yStart; j--) {
|
||||
if (this.ElementsAreEqual(i, j)) {
|
||||
curr[j] = prev[j + 1] + 1;
|
||||
} else {
|
||||
curr[j] = prev[j] > curr[j + 1] ? prev[j] : curr[j + 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Swap prev & curr
|
||||
tmp = curr;
|
||||
curr = prev;
|
||||
prev = tmp;
|
||||
}
|
||||
|
||||
// Result is always in prev
|
||||
return prev;
|
||||
}
|
||||
|
||||
private findCut(xStart: number, xStop: number, yStart: number, yStop: number, middle: number): number {
|
||||
let L1 = this.forward(xStart, middle, yStart, yStop);
|
||||
let L2 = this.backward(middle + 1, xStop, yStart, yStop);
|
||||
|
||||
// First cut
|
||||
let max = L2[yStart], cut = yStart - 1;
|
||||
|
||||
// Middle cut
|
||||
for (let j = yStart; j < yStop; j++) {
|
||||
if (L1[j] + L2[j + 1] > max) {
|
||||
max = L1[j] + L2[j + 1];
|
||||
cut = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Last cut
|
||||
if (L1[yStop] > max) {
|
||||
max = L1[yStop];
|
||||
cut = yStop;
|
||||
}
|
||||
|
||||
return cut;
|
||||
}
|
||||
|
||||
private execute(xStart: number, xStop: number, yStart: number, yStop: number) {
|
||||
// Do some prefix trimming
|
||||
while (xStart <= xStop && yStart <= yStop && this.ElementsAreEqual(xStart, yStart)) {
|
||||
this.resultX[xStart] = true;
|
||||
xStart++;
|
||||
this.resultY[yStart] = true;
|
||||
yStart++;
|
||||
}
|
||||
|
||||
// Do some suffix trimming
|
||||
while (xStart <= xStop && yStart <= yStop && this.ElementsAreEqual(xStop, yStop)) {
|
||||
this.resultX[xStop] = true;
|
||||
xStop--;
|
||||
this.resultY[yStop] = true;
|
||||
yStop--;
|
||||
}
|
||||
|
||||
if (xStart > xStop || yStart > yStop) {
|
||||
return;
|
||||
}
|
||||
|
||||
let found: number, i: number;
|
||||
if (xStart === xStop) {
|
||||
found = -1;
|
||||
for (i = yStart; i <= yStop; i++) {
|
||||
if (this.ElementsAreEqual(xStart, i)) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found >= 0) {
|
||||
this.resultX[xStart] = true;
|
||||
this.resultY[found] = true;
|
||||
}
|
||||
} else if (yStart === yStop) {
|
||||
found = -1;
|
||||
for (i = xStart; i <= xStop; i++) {
|
||||
if (this.ElementsAreEqual(i, yStart)) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found >= 0) {
|
||||
this.resultX[found] = true;
|
||||
this.resultY[yStart] = true;
|
||||
}
|
||||
} else {
|
||||
let middle = Math.floor((xStart + xStop) / 2);
|
||||
let cut = this.findCut(xStart, xStop, yStart, yStop, middle);
|
||||
|
||||
if (yStart <= cut) {
|
||||
this.execute(xStart, middle, yStart, cut);
|
||||
}
|
||||
|
||||
if (cut + 1 <= yStop) {
|
||||
this.execute(middle + 1, xStop, cut + 1, yStop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
80
src/vs/base/common/diff/diffChange.ts
Normal file
80
src/vs/base/common/diff/diffChange.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export const DifferenceType = {
|
||||
Add: 0,
|
||||
Remove: 1,
|
||||
Change: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents information about a specific difference between two sequences.
|
||||
*/
|
||||
export class DiffChange {
|
||||
|
||||
/**
|
||||
* The position of the first element in the original sequence which
|
||||
* this change affects.
|
||||
*/
|
||||
public originalStart: number;
|
||||
|
||||
/**
|
||||
* The number of elements from the original sequence which were
|
||||
* affected.
|
||||
*/
|
||||
public originalLength: number;
|
||||
|
||||
/**
|
||||
* The position of the first element in the modified sequence which
|
||||
* this change affects.
|
||||
*/
|
||||
public modifiedStart: number;
|
||||
|
||||
/**
|
||||
* The number of elements from the modified sequence which were
|
||||
* affected (added).
|
||||
*/
|
||||
public modifiedLength: number;
|
||||
|
||||
/**
|
||||
* Constructs a new DiffChange with the given sequence information
|
||||
* and content.
|
||||
*/
|
||||
constructor(originalStart: number, originalLength: number, modifiedStart: number, modifiedLength: number) {
|
||||
//Debug.Assert(originalLength > 0 || modifiedLength > 0, "originalLength and modifiedLength cannot both be <= 0");
|
||||
this.originalStart = originalStart;
|
||||
this.originalLength = originalLength;
|
||||
this.modifiedStart = modifiedStart;
|
||||
this.modifiedLength = modifiedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of difference.
|
||||
*/
|
||||
public getChangeType() {
|
||||
if (this.originalLength === 0) {
|
||||
return DifferenceType.Add;
|
||||
} else if (this.modifiedLength === 0) {
|
||||
return DifferenceType.Remove;
|
||||
} else {
|
||||
return DifferenceType.Change;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The end point (exclusive) of the change in the original sequence.
|
||||
*/
|
||||
public getOriginalEnd() {
|
||||
return this.originalStart + this.originalLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* The end point (exclusive) of the change in the modified sequence.
|
||||
*/
|
||||
public getModifiedEnd() {
|
||||
return this.modifiedStart + this.modifiedLength;
|
||||
}
|
||||
}
|
||||
249
src/vs/base/common/errorMessage.ts
Normal file
249
src/vs/base/common/errorMessage.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import objects = require('vs/base/common/objects');
|
||||
import types = require('vs/base/common/types');
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import strings = require('vs/base/common/strings');
|
||||
|
||||
export interface IXHRResponse {
|
||||
responseText: string;
|
||||
status: number;
|
||||
|
||||
readyState: number;
|
||||
getResponseHeader: (header: string) => string;
|
||||
}
|
||||
|
||||
export interface IConnectionErrorData {
|
||||
status: number;
|
||||
statusText?: string;
|
||||
responseText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for all connection errors originating from XHR requests.
|
||||
*/
|
||||
export class ConnectionError implements Error {
|
||||
public status: number;
|
||||
public statusText: string;
|
||||
public responseText: string;
|
||||
public errorMessage: string;
|
||||
public errorCode: string;
|
||||
public errorObject: any;
|
||||
public name: string;
|
||||
|
||||
constructor(mixin: IConnectionErrorData);
|
||||
constructor(request: IXHRResponse);
|
||||
constructor(arg: any) {
|
||||
this.status = arg.status;
|
||||
this.statusText = arg.statusText;
|
||||
this.name = 'ConnectionError';
|
||||
|
||||
try {
|
||||
this.responseText = arg.responseText;
|
||||
} catch (e) {
|
||||
this.responseText = '';
|
||||
}
|
||||
|
||||
this.errorMessage = null;
|
||||
this.errorCode = null;
|
||||
this.errorObject = null;
|
||||
|
||||
if (this.responseText) {
|
||||
try {
|
||||
let errorObj = JSON.parse(this.responseText);
|
||||
this.errorMessage = errorObj.message;
|
||||
this.errorCode = errorObj.code;
|
||||
this.errorObject = errorObj;
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get message(): string {
|
||||
return this.connectionErrorToMessage(this, false);
|
||||
}
|
||||
|
||||
public get verboseMessage(): string {
|
||||
return this.connectionErrorToMessage(this, true);
|
||||
}
|
||||
|
||||
private connectionErrorDetailsToMessage(error: ConnectionError, verbose: boolean): string {
|
||||
let errorCode = error.errorCode;
|
||||
let errorMessage = error.errorMessage;
|
||||
|
||||
if (errorCode !== null && errorMessage !== null) {
|
||||
return nls.localize(
|
||||
{
|
||||
key: 'message',
|
||||
comment: [
|
||||
'{0} represents the error message',
|
||||
'{1} represents the error code'
|
||||
]
|
||||
},
|
||||
"{0}. Error code: {1}",
|
||||
strings.rtrim(errorMessage, '.'), errorCode);
|
||||
}
|
||||
|
||||
if (errorMessage !== null) {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
if (verbose && error.responseText !== null) {
|
||||
return error.responseText;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private connectionErrorToMessage(error: ConnectionError, verbose: boolean): string {
|
||||
let details = this.connectionErrorDetailsToMessage(error, verbose);
|
||||
|
||||
// Status Code based Error
|
||||
if (error.status === 401) {
|
||||
if (details !== null) {
|
||||
return nls.localize(
|
||||
{
|
||||
key: 'error.permission.verbose',
|
||||
comment: [
|
||||
'{0} represents detailed information why the permission got denied'
|
||||
]
|
||||
},
|
||||
"Permission Denied (HTTP {0})",
|
||||
details);
|
||||
}
|
||||
|
||||
return nls.localize('error.permission', "Permission Denied");
|
||||
}
|
||||
|
||||
// Return error details if present
|
||||
if (details) {
|
||||
return details;
|
||||
}
|
||||
|
||||
// Fallback to HTTP Status and Code
|
||||
if (error.status > 0 && error.statusText !== null) {
|
||||
if (verbose && error.responseText !== null && error.responseText.length > 0) {
|
||||
return nls.localize('error.http.verbose', "{0} (HTTP {1}: {2})", error.statusText, error.status, error.responseText);
|
||||
}
|
||||
|
||||
return nls.localize('error.http', "{0} (HTTP {1})", error.statusText, error.status);
|
||||
}
|
||||
|
||||
// Finally its an Unknown Connection Error
|
||||
if (verbose && error.responseText !== null && error.responseText.length > 0) {
|
||||
return nls.localize('error.connection.unknown.verbose', "Unknown Connection Error ({0})", error.responseText);
|
||||
}
|
||||
|
||||
return nls.localize('error.connection.unknown', "An unknown connection error occurred. Either you are no longer connected to the internet or the server you are connected to is offline.");
|
||||
}
|
||||
}
|
||||
|
||||
// Bug: Can not subclass a JS Type. Do it manually (as done in WinJS.Class.derive)
|
||||
objects.derive(Error, ConnectionError);
|
||||
|
||||
function xhrToErrorMessage(xhr: IConnectionErrorData, verbose: boolean): string {
|
||||
let ce = new ConnectionError(xhr);
|
||||
if (verbose) {
|
||||
return ce.verboseMessage;
|
||||
} else {
|
||||
return ce.message;
|
||||
}
|
||||
}
|
||||
|
||||
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), exception.stack || exception.stacktrace);
|
||||
}
|
||||
|
||||
return detectSystemErrorMessage(exception);
|
||||
}
|
||||
|
||||
return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
|
||||
}
|
||||
|
||||
function detectSystemErrorMessage(exception: any): string {
|
||||
|
||||
// See https://nodejs.org/api/errors.html#errors_class_system_error
|
||||
if (typeof exception.code === 'string' && typeof exception.errno === 'number' && typeof exception.syscall === 'string') {
|
||||
return nls.localize('nodeExceptionMessage', "A system error occurred ({0})", exception.message);
|
||||
}
|
||||
|
||||
return exception.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to generate a human readable error message out of the error. If the verbose parameter
|
||||
* is set to true, the error message will include stacktrace details if provided.
|
||||
* @returns A string containing the error message.
|
||||
*/
|
||||
export function toErrorMessage(error: any = null, verbose: boolean = false): string {
|
||||
if (!error) {
|
||||
return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
|
||||
}
|
||||
|
||||
if (Array.isArray(error)) {
|
||||
let errors: any[] = arrays.coalesce(error);
|
||||
let msg = toErrorMessage(errors[0], verbose);
|
||||
|
||||
if (errors.length > 1) {
|
||||
return nls.localize('error.moreErrors', "{0} ({1} errors in total)", msg, errors.length);
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (types.isString(error)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!types.isUndefinedOrNull(error.status)) {
|
||||
return xhrToErrorMessage(error, verbose);
|
||||
}
|
||||
|
||||
if (error.detail) {
|
||||
let detail = error.detail;
|
||||
|
||||
if (detail.error) {
|
||||
if (detail.error && !types.isUndefinedOrNull(detail.error.status)) {
|
||||
return xhrToErrorMessage(detail.error, verbose);
|
||||
}
|
||||
|
||||
if (types.isArray(detail.error)) {
|
||||
for (let i = 0; i < detail.error.length; i++) {
|
||||
if (detail.error[i] && !types.isUndefinedOrNull(detail.error[i].status)) {
|
||||
return xhrToErrorMessage(detail.error[i], verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
return exceptionToErrorMessage(detail.error, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
if (detail.exception) {
|
||||
if (!types.isUndefinedOrNull(detail.exception.status)) {
|
||||
return xhrToErrorMessage(detail.exception, verbose);
|
||||
}
|
||||
|
||||
return exceptionToErrorMessage(detail.exception, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
if (error.stack) {
|
||||
return exceptionToErrorMessage(error, verbose);
|
||||
}
|
||||
|
||||
if (error.message) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
|
||||
}
|
||||
284
src/vs/base/common/errors.ts
Normal file
284
src/vs/base/common/errors.ts
Normal file
@@ -0,0 +1,284 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import platform = require('vs/base/common/platform');
|
||||
import types = require('vs/base/common/types');
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { TPromise, IPromiseError, IPromiseErrorDetail } from 'vs/base/common/winjs.base';
|
||||
|
||||
// ------ BEGIN Hook up error listeners to winjs promises
|
||||
|
||||
let outstandingPromiseErrors: { [id: string]: IPromiseErrorDetail; } = {};
|
||||
function promiseErrorHandler(e: IPromiseError): void {
|
||||
|
||||
//
|
||||
// e.detail looks like: { exception, error, promise, handler, id, parent }
|
||||
//
|
||||
const details = e.detail;
|
||||
const id = details.id;
|
||||
|
||||
// If the error has a parent promise then this is not the origination of the
|
||||
// error so we check if it has a handler, and if so we mark that the error
|
||||
// was handled by removing it from outstandingPromiseErrors
|
||||
//
|
||||
if (details.parent) {
|
||||
if (details.handler && outstandingPromiseErrors) {
|
||||
delete outstandingPromiseErrors[id];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Indicate that this error was originated and needs to be handled
|
||||
outstandingPromiseErrors[id] = details;
|
||||
|
||||
// The first time the queue fills up this iteration, schedule a timeout to
|
||||
// check if any errors are still unhandled.
|
||||
if (Object.keys(outstandingPromiseErrors).length === 1) {
|
||||
setTimeout(function () {
|
||||
const errors = outstandingPromiseErrors;
|
||||
outstandingPromiseErrors = {};
|
||||
Object.keys(errors).forEach(function (errorId) {
|
||||
const error = errors[errorId];
|
||||
if (error.exception) {
|
||||
onUnexpectedError(error.exception);
|
||||
} else if (error.error) {
|
||||
onUnexpectedError(error.error);
|
||||
}
|
||||
console.log('WARNING: Promise with no error callback:' + error.id);
|
||||
console.log(error);
|
||||
if (error.exception) {
|
||||
console.log(error.exception.stack);
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
TPromise.addEventListener('error', promiseErrorHandler);
|
||||
|
||||
// ------ END Hook up error listeners to winjs promises
|
||||
|
||||
export interface ErrorListenerCallback {
|
||||
(error: any): void;
|
||||
}
|
||||
|
||||
export interface ErrorListenerUnbind {
|
||||
(): void;
|
||||
}
|
||||
|
||||
// Avoid circular dependency on EventEmitter by implementing a subset of the interface.
|
||||
export class ErrorHandler {
|
||||
private unexpectedErrorHandler: (e: any) => void;
|
||||
private listeners: ErrorListenerCallback[];
|
||||
|
||||
constructor() {
|
||||
|
||||
this.listeners = [];
|
||||
|
||||
this.unexpectedErrorHandler = function (e: any) {
|
||||
platform.setTimeout(() => {
|
||||
if (e.stack) {
|
||||
throw new Error(e.message + '\n\n' + e.stack);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}, 0);
|
||||
};
|
||||
}
|
||||
|
||||
public addListener(listener: ErrorListenerCallback): ErrorListenerUnbind {
|
||||
this.listeners.push(listener);
|
||||
|
||||
return () => {
|
||||
this._removeListener(listener);
|
||||
};
|
||||
}
|
||||
|
||||
private emit(e: any): void {
|
||||
this.listeners.forEach((listener) => {
|
||||
listener(e);
|
||||
});
|
||||
}
|
||||
|
||||
private _removeListener(listener: ErrorListenerCallback): void {
|
||||
this.listeners.splice(this.listeners.indexOf(listener), 1);
|
||||
}
|
||||
|
||||
public setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
|
||||
this.unexpectedErrorHandler = newUnexpectedErrorHandler;
|
||||
}
|
||||
|
||||
public getUnexpectedErrorHandler(): (e: any) => void {
|
||||
return this.unexpectedErrorHandler;
|
||||
}
|
||||
|
||||
public 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 {
|
||||
this.unexpectedErrorHandler(e);
|
||||
}
|
||||
}
|
||||
|
||||
export const errorHandler = new ErrorHandler();
|
||||
|
||||
export function setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
|
||||
errorHandler.setUnexpectedErrorHandler(newUnexpectedErrorHandler);
|
||||
}
|
||||
|
||||
export function onUnexpectedError(e: any): undefined {
|
||||
// ignore errors from cancelled promises
|
||||
if (!isPromiseCanceledError(e)) {
|
||||
errorHandler.onUnexpectedError(e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function onUnexpectedExternalError(e: any): undefined {
|
||||
// ignore errors from cancelled promises
|
||||
if (!isPromiseCanceledError(e)) {
|
||||
errorHandler.onUnexpectedExternalError(e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function onUnexpectedPromiseError<T>(promise: TPromise<T>): TPromise<T | void> {
|
||||
return promise.then(null, onUnexpectedError);
|
||||
}
|
||||
|
||||
export interface SerializedError {
|
||||
readonly $isError: true;
|
||||
readonly name: string;
|
||||
readonly message: string;
|
||||
readonly stack: string;
|
||||
}
|
||||
|
||||
export function transformErrorForSerialization(error: Error): SerializedError;
|
||||
export function transformErrorForSerialization(error: any): any;
|
||||
export function transformErrorForSerialization(error: any): any {
|
||||
if (error instanceof Error) {
|
||||
let { name, message } = error;
|
||||
let stack: string = (<any>error).stacktrace || (<any>error).stack;
|
||||
return {
|
||||
$isError: true,
|
||||
name,
|
||||
message,
|
||||
stack
|
||||
};
|
||||
}
|
||||
|
||||
// return as is
|
||||
return error;
|
||||
}
|
||||
|
||||
// see https://github.com/v8/v8/wiki/Stack%20Trace%20API#basic-stack-traces
|
||||
export interface V8CallSite {
|
||||
getThis(): any;
|
||||
getTypeName(): string;
|
||||
getFunction(): string;
|
||||
getFunctionName(): string;
|
||||
getMethodName(): string;
|
||||
getFileName(): string;
|
||||
getLineNumber(): number;
|
||||
getColumnNumber(): number;
|
||||
getEvalOrigin(): string;
|
||||
isToplevel(): boolean;
|
||||
isEval(): boolean;
|
||||
isNative(): boolean;
|
||||
isConstructor(): boolean;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
const canceledName = 'Canceled';
|
||||
|
||||
/**
|
||||
* Checks if the given error is a promise in canceled state
|
||||
*/
|
||||
export function isPromiseCanceledError(error: any): boolean {
|
||||
return error instanceof Error && error.name === canceledName && error.message === canceledName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error that signals cancellation.
|
||||
*/
|
||||
export function canceled(): Error {
|
||||
let error = new Error(canceledName);
|
||||
error.name = error.message;
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error that signals something is not implemented.
|
||||
*/
|
||||
export function notImplemented(): Error {
|
||||
return new Error('Not Implemented');
|
||||
}
|
||||
|
||||
export function illegalArgument(name?: string): Error {
|
||||
if (name) {
|
||||
return new Error(`Illegal argument: ${name}`);
|
||||
} else {
|
||||
return new Error('Illegal argument');
|
||||
}
|
||||
}
|
||||
|
||||
export function illegalState(name?: string): Error {
|
||||
if (name) {
|
||||
return new Error(`Illegal state: ${name}`);
|
||||
} else {
|
||||
return new Error('Illegal state');
|
||||
}
|
||||
}
|
||||
|
||||
export function readonly(name?: string): Error {
|
||||
return name
|
||||
? new Error(`readonly property '${name} cannot be changed'`)
|
||||
: new Error('readonly property cannot be changed');
|
||||
}
|
||||
|
||||
export function disposed(what: string): Error {
|
||||
const result = new Error(`${what} has been disposed`);
|
||||
result.name = 'DISPOSED';
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface IErrorOptions {
|
||||
severity?: Severity;
|
||||
actions?: IAction[];
|
||||
}
|
||||
|
||||
export function create(message: string, options: IErrorOptions = {}): Error {
|
||||
let result = new Error(message);
|
||||
|
||||
if (types.isNumber(options.severity)) {
|
||||
(<any>result).severity = options.severity;
|
||||
}
|
||||
|
||||
if (options.actions) {
|
||||
(<any>result).actions = options.actions;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getErrorMessage(err: any): string {
|
||||
if (!err) {
|
||||
return 'Error';
|
||||
}
|
||||
|
||||
if (err.message) {
|
||||
return err.message;
|
||||
}
|
||||
|
||||
if (err.stack) {
|
||||
return err.stack.split('\n')[0];
|
||||
}
|
||||
|
||||
return String(err);
|
||||
}
|
||||
530
src/vs/base/common/event.ts
Normal file
530
src/vs/base/common/event.ts
Normal file
@@ -0,0 +1,530 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import CallbackList from 'vs/base/common/callbackList';
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { once as onceFn } from 'vs/base/common/functional';
|
||||
|
||||
/**
|
||||
* To an event a function with one or zero parameters
|
||||
* can be subscribed. The event is the subscriber function itself.
|
||||
*/
|
||||
interface Event<T> {
|
||||
(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
|
||||
}
|
||||
|
||||
namespace Event {
|
||||
const _disposable = { dispose() { } };
|
||||
export const None: Event<any> = function () { return _disposable; };
|
||||
}
|
||||
|
||||
export default Event;
|
||||
|
||||
export interface EmitterOptions {
|
||||
onFirstListenerAdd?: Function;
|
||||
onFirstListenerDidAdd?: Function;
|
||||
onListenerDidAdd?: Function;
|
||||
onLastListenerRemove?: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Emitter can be used to expose an Event to the public
|
||||
* to fire it from the insides.
|
||||
* Sample:
|
||||
class Document {
|
||||
|
||||
private _onDidChange = new Emitter<(value:string)=>any>();
|
||||
|
||||
public onDidChange = this._onDidChange.event;
|
||||
|
||||
// getter-style
|
||||
// get onDidChange(): Event<(value:string)=>any> {
|
||||
// return this._onDidChange.event;
|
||||
// }
|
||||
|
||||
private _doIt() {
|
||||
//...
|
||||
this._onDidChange.fire(value);
|
||||
}
|
||||
}
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
|
||||
private static _noop = function () { };
|
||||
|
||||
private _event: Event<T>;
|
||||
private _callbacks: CallbackList;
|
||||
private _disposed: boolean;
|
||||
|
||||
constructor(private _options?: EmitterOptions) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* For the public to allow to subscribe
|
||||
* to events from this Emitter
|
||||
*/
|
||||
get event(): Event<T> {
|
||||
if (!this._event) {
|
||||
this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]) => {
|
||||
if (!this._callbacks) {
|
||||
this._callbacks = new CallbackList();
|
||||
}
|
||||
|
||||
const firstListener = this._callbacks.isEmpty();
|
||||
|
||||
if (firstListener && this._options && this._options.onFirstListenerAdd) {
|
||||
this._options.onFirstListenerAdd(this);
|
||||
}
|
||||
|
||||
this._callbacks.add(listener, thisArgs);
|
||||
|
||||
if (firstListener && this._options && this._options.onFirstListenerDidAdd) {
|
||||
this._options.onFirstListenerDidAdd(this);
|
||||
}
|
||||
|
||||
if (this._options && this._options.onListenerDidAdd) {
|
||||
this._options.onListenerDidAdd(this, listener, thisArgs);
|
||||
}
|
||||
|
||||
let result: IDisposable;
|
||||
result = {
|
||||
dispose: () => {
|
||||
result.dispose = Emitter._noop;
|
||||
if (!this._disposed) {
|
||||
this._callbacks.remove(listener, thisArgs);
|
||||
if (this._options && this._options.onLastListenerRemove && this._callbacks.isEmpty()) {
|
||||
this._options.onLastListenerRemove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (Array.isArray(disposables)) {
|
||||
disposables.push(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
return this._event;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be kept private to fire an event to
|
||||
* subscribers
|
||||
*/
|
||||
fire(event?: T): any {
|
||||
if (this._callbacks) {
|
||||
this._callbacks.invoke.call(this._callbacks, event);
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._callbacks) {
|
||||
this._callbacks.dispose();
|
||||
this._callbacks = undefined;
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class EventMultiplexer<T> implements IDisposable {
|
||||
|
||||
private emitter: Emitter<T>;
|
||||
private hasListeners = false;
|
||||
private events: { event: Event<T>; listener: IDisposable; }[] = [];
|
||||
|
||||
constructor() {
|
||||
this.emitter = new Emitter<T>({
|
||||
onFirstListenerAdd: () => this.onFirstListenerAdd(),
|
||||
onLastListenerRemove: () => this.onLastListenerRemove()
|
||||
});
|
||||
}
|
||||
|
||||
get event(): Event<T> {
|
||||
return this.emitter.event;
|
||||
}
|
||||
|
||||
add(event: Event<T>): IDisposable {
|
||||
const e = { event: event, listener: null };
|
||||
this.events.push(e);
|
||||
|
||||
if (this.hasListeners) {
|
||||
this.hook(e);
|
||||
}
|
||||
|
||||
const dispose = () => {
|
||||
if (this.hasListeners) {
|
||||
this.unhook(e);
|
||||
}
|
||||
|
||||
const idx = this.events.indexOf(e);
|
||||
this.events.splice(idx, 1);
|
||||
};
|
||||
|
||||
return toDisposable(onceFn(dispose));
|
||||
}
|
||||
|
||||
private onFirstListenerAdd(): void {
|
||||
this.hasListeners = true;
|
||||
this.events.forEach(e => this.hook(e));
|
||||
}
|
||||
|
||||
private onLastListenerRemove(): void {
|
||||
this.hasListeners = false;
|
||||
this.events.forEach(e => this.unhook(e));
|
||||
}
|
||||
|
||||
private hook(e: { event: Event<T>; listener: IDisposable; }): void {
|
||||
e.listener = e.event(r => this.emitter.fire(r));
|
||||
}
|
||||
|
||||
private unhook(e: { event: Event<T>; listener: IDisposable; }): void {
|
||||
e.listener.dispose();
|
||||
e.listener = null;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.emitter.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Event which is backed-up by the event emitter. This allows
|
||||
* to use the existing eventing pattern and is likely using less memory.
|
||||
* Sample:
|
||||
*
|
||||
* class Document {
|
||||
*
|
||||
* private _eventbus = new EventEmitter();
|
||||
*
|
||||
* public onDidChange = fromEventEmitter(this._eventbus, 'changed');
|
||||
*
|
||||
* // getter-style
|
||||
* // get onDidChange(): Event<(value:string)=>any> {
|
||||
* // cache fromEventEmitter result and return
|
||||
* // }
|
||||
*
|
||||
* private _doIt() {
|
||||
* // ...
|
||||
* this._eventbus.emit('changed', value)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export function fromEventEmitter<T>(emitter: EventEmitter, eventType: string): Event<T> {
|
||||
return function (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable {
|
||||
const result = emitter.addListener(eventType, function () {
|
||||
listener.apply(thisArgs, arguments);
|
||||
});
|
||||
if (Array.isArray(disposables)) {
|
||||
disposables.push(result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export function fromCallback<T>(fn: (handler: (e: T) => void) => IDisposable): Event<T> {
|
||||
let listener: IDisposable;
|
||||
|
||||
const emitter = new Emitter<T>({
|
||||
onFirstListenerAdd: () => listener = fn(e => emitter.fire(e)),
|
||||
onLastListenerRemove: () => listener.dispose()
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
export function fromPromise(promise: TPromise<any>): Event<void> {
|
||||
const emitter = new Emitter<void>();
|
||||
let shouldEmit = false;
|
||||
|
||||
promise
|
||||
.then(null, () => null)
|
||||
.then(() => {
|
||||
if (!shouldEmit) {
|
||||
setTimeout(() => emitter.fire(), 0);
|
||||
} else {
|
||||
emitter.fire();
|
||||
}
|
||||
});
|
||||
|
||||
shouldEmit = true;
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
export function toPromise<T>(event: Event<T>): TPromise<T> {
|
||||
return new TPromise(complete => {
|
||||
const sub = event(e => {
|
||||
sub.dispose();
|
||||
complete(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function delayed<T>(promise: TPromise<Event<T>>): Event<T> {
|
||||
let toCancel: TPromise<any> = null;
|
||||
let listener: IDisposable = null;
|
||||
|
||||
const emitter = new Emitter<T>({
|
||||
onFirstListenerAdd() {
|
||||
toCancel = promise.then(
|
||||
event => listener = event(e => emitter.fire(e)),
|
||||
() => null
|
||||
);
|
||||
},
|
||||
onLastListenerRemove() {
|
||||
if (toCancel) {
|
||||
toCancel.cancel();
|
||||
toCancel = null;
|
||||
}
|
||||
|
||||
if (listener) {
|
||||
listener.dispose();
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
export function once<T>(event: Event<T>): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => {
|
||||
const result = event(e => {
|
||||
result.dispose();
|
||||
return listener.call(thisArgs, e);
|
||||
}, null, disposables);
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export function any<T>(...events: Event<T>[]): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => combinedDisposable(events.map(event => event(e => listener.call(thisArgs, e), null, disposables)));
|
||||
}
|
||||
|
||||
export function debounceEvent<T>(event: Event<T>, merger: (last: T, event: T) => T, delay?: number, leading?: boolean): Event<T>;
|
||||
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O, event: I) => O, delay?: number, leading?: boolean): Event<O>;
|
||||
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O, event: I) => O, delay: number = 100, leading = false): Event<O> {
|
||||
|
||||
let subscription: IDisposable;
|
||||
let output: O;
|
||||
let handle: number;
|
||||
let numDebouncedCalls = 0;
|
||||
|
||||
const emitter = new Emitter<O>({
|
||||
onFirstListenerAdd() {
|
||||
subscription = event(cur => {
|
||||
numDebouncedCalls++;
|
||||
|
||||
output = merger(output, cur);
|
||||
if (!handle && leading) {
|
||||
emitter.fire(output);
|
||||
}
|
||||
|
||||
clearTimeout(handle);
|
||||
handle = setTimeout(() => {
|
||||
let _output = output;
|
||||
output = undefined;
|
||||
if (!leading || numDebouncedCalls > 1) {
|
||||
emitter.fire(_output);
|
||||
}
|
||||
|
||||
handle = null;
|
||||
numDebouncedCalls = 0;
|
||||
}, delay);
|
||||
});
|
||||
},
|
||||
onLastListenerRemove() {
|
||||
subscription.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* The EventDelayer is useful in situations in which you want
|
||||
* to delay firing your events during some code.
|
||||
* You can wrap that code and be sure that the event will not
|
||||
* be fired during that wrap.
|
||||
*
|
||||
* ```
|
||||
* const emitter: Emitter;
|
||||
* const delayer = new EventDelayer();
|
||||
* const delayedEvent = delayer.wrapEvent(emitter.event);
|
||||
*
|
||||
* delayedEvent(console.log);
|
||||
*
|
||||
* delayer.bufferEvents(() => {
|
||||
* emitter.fire(); // event will not be fired yet
|
||||
* });
|
||||
*
|
||||
* // event will only be fired at this point
|
||||
* ```
|
||||
*/
|
||||
export class EventBufferer {
|
||||
|
||||
private buffers: Function[][] = [];
|
||||
|
||||
wrapEvent<T>(event: Event<T>): Event<T> {
|
||||
return (listener, thisArgs?, disposables?) => {
|
||||
return event(i => {
|
||||
const buffer = this.buffers[this.buffers.length - 1];
|
||||
|
||||
if (buffer) {
|
||||
buffer.push(() => listener.call(thisArgs, i));
|
||||
} else {
|
||||
listener.call(thisArgs, i);
|
||||
}
|
||||
}, void 0, disposables);
|
||||
};
|
||||
}
|
||||
|
||||
bufferEvents(fn: () => void): void {
|
||||
const buffer: Function[] = [];
|
||||
this.buffers.push(buffer);
|
||||
fn();
|
||||
this.buffers.pop();
|
||||
buffer.forEach(flush => flush());
|
||||
}
|
||||
}
|
||||
|
||||
export interface IChainableEvent<T> {
|
||||
event: Event<T>;
|
||||
map<O>(fn: (i: T) => O): IChainableEvent<O>;
|
||||
filter(fn: (e: T) => boolean): IChainableEvent<T>;
|
||||
on(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
|
||||
}
|
||||
|
||||
export function mapEvent<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
|
||||
return (listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables);
|
||||
}
|
||||
|
||||
export function filterEvent<T>(event: Event<T>, filter: (e: T) => boolean): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
|
||||
}
|
||||
|
||||
class ChainableEvent<T> implements IChainableEvent<T> {
|
||||
|
||||
get event(): Event<T> { return this._event; }
|
||||
|
||||
constructor(private _event: Event<T>) { }
|
||||
|
||||
map<O>(fn: (i: T) => O): IChainableEvent<O> {
|
||||
return new ChainableEvent(mapEvent(this._event, fn));
|
||||
}
|
||||
|
||||
filter(fn: (e: T) => boolean): IChainableEvent<T> {
|
||||
return new ChainableEvent(filterEvent(this._event, fn));
|
||||
}
|
||||
|
||||
on(listener, thisArgs, disposables: IDisposable[]) {
|
||||
return this._event(listener, thisArgs, disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export function chain<T>(event: Event<T>): IChainableEvent<T> {
|
||||
return new ChainableEvent(event);
|
||||
}
|
||||
|
||||
export function stopwatch<T>(event: Event<T>): Event<number> {
|
||||
const start = new Date().getTime();
|
||||
return mapEvent(once(event), _ => new Date().getTime() - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffers the provided event until a first listener comes
|
||||
* along, at which point fire all the events at once and
|
||||
* pipe the event from then on.
|
||||
*
|
||||
* ```typescript
|
||||
* const emitter = new Emitter<number>();
|
||||
* const event = emitter.event;
|
||||
* const bufferedEvent = buffer(event);
|
||||
*
|
||||
* emitter.fire(1);
|
||||
* emitter.fire(2);
|
||||
* emitter.fire(3);
|
||||
* // nothing...
|
||||
*
|
||||
* const listener = bufferedEvent(num => console.log(num));
|
||||
* // 1, 2, 3
|
||||
*
|
||||
* emitter.fire(4);
|
||||
* // 4
|
||||
* ```
|
||||
*/
|
||||
export function buffer<T>(event: Event<T>, nextTick = false, buffer: T[] = []): Event<T> {
|
||||
buffer = buffer.slice();
|
||||
|
||||
let listener = event(e => {
|
||||
if (buffer) {
|
||||
buffer.push(e);
|
||||
} else {
|
||||
emitter.fire(e);
|
||||
}
|
||||
});
|
||||
|
||||
const flush = () => {
|
||||
buffer.forEach(e => emitter.fire(e));
|
||||
buffer = null;
|
||||
};
|
||||
|
||||
const emitter = new Emitter<T>({
|
||||
onFirstListenerAdd() {
|
||||
if (!listener) {
|
||||
listener = event(e => emitter.fire(e));
|
||||
}
|
||||
},
|
||||
|
||||
onFirstListenerDidAdd() {
|
||||
if (buffer) {
|
||||
if (nextTick) {
|
||||
setTimeout(flush);
|
||||
} else {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLastListenerRemove() {
|
||||
listener.dispose();
|
||||
listener = null;
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to `buffer` but it buffers indefinitely and repeats
|
||||
* the buffered events to every new listener.
|
||||
*/
|
||||
export function echo<T>(event: Event<T>, nextTick = false, buffer: T[] = []): Event<T> {
|
||||
buffer = buffer.slice();
|
||||
|
||||
event(e => {
|
||||
buffer.push(e);
|
||||
emitter.fire(e);
|
||||
});
|
||||
|
||||
const flush = (listener, thisArgs?) => buffer.forEach(e => listener.call(thisArgs, e));
|
||||
|
||||
const emitter = new Emitter<T>({
|
||||
onListenerDidAdd(emitter, listener, thisArgs?) {
|
||||
if (nextTick) {
|
||||
setTimeout(() => flush(listener, thisArgs));
|
||||
} else {
|
||||
flush(listener, thisArgs);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
299
src/vs/base/common/eventEmitter.ts
Normal file
299
src/vs/base/common/eventEmitter.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Errors = require('vs/base/common/errors');
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class EmitterEvent {
|
||||
|
||||
public readonly type: string;
|
||||
public readonly data: any;
|
||||
|
||||
constructor(eventType: string = null, data: any = null) {
|
||||
this.type = eventType;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ListenerCallback {
|
||||
(value: any): void;
|
||||
}
|
||||
|
||||
export interface BulkListenerCallback {
|
||||
(value: EmitterEvent[]): void;
|
||||
}
|
||||
|
||||
export interface IBaseEventEmitter {
|
||||
addBulkListener(listener: BulkListenerCallback): IDisposable;
|
||||
}
|
||||
|
||||
export interface IEventEmitter extends IBaseEventEmitter, IDisposable {
|
||||
addListener(eventType: string, listener: ListenerCallback): IDisposable;
|
||||
addOneTimeListener(eventType: string, listener: ListenerCallback): IDisposable;
|
||||
addEmitter(eventEmitter: IEventEmitter): IDisposable;
|
||||
}
|
||||
|
||||
export interface IListenersMap {
|
||||
[key: string]: ListenerCallback[];
|
||||
}
|
||||
|
||||
export class EventEmitter implements IEventEmitter {
|
||||
|
||||
protected _listeners: IListenersMap;
|
||||
protected _bulkListeners: ListenerCallback[];
|
||||
private _collectedEvents: EmitterEvent[];
|
||||
private _deferredCnt: number;
|
||||
private _allowedEventTypes: { [eventType: string]: boolean; };
|
||||
|
||||
constructor(allowedEventTypes: string[] = null) {
|
||||
this._listeners = {};
|
||||
this._bulkListeners = [];
|
||||
this._collectedEvents = [];
|
||||
this._deferredCnt = 0;
|
||||
if (allowedEventTypes) {
|
||||
this._allowedEventTypes = {};
|
||||
for (let i = 0; i < allowedEventTypes.length; i++) {
|
||||
this._allowedEventTypes[allowedEventTypes[i]] = true;
|
||||
}
|
||||
} else {
|
||||
this._allowedEventTypes = null;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._listeners = {};
|
||||
this._bulkListeners = [];
|
||||
this._collectedEvents = [];
|
||||
this._deferredCnt = 0;
|
||||
this._allowedEventTypes = null;
|
||||
}
|
||||
|
||||
public addListener(eventType: string, listener: ListenerCallback): IDisposable {
|
||||
if (eventType === '*') {
|
||||
throw new Error('Use addBulkListener(listener) to register your listener!');
|
||||
}
|
||||
|
||||
if (this._allowedEventTypes && !this._allowedEventTypes.hasOwnProperty(eventType)) {
|
||||
throw new Error('This object will never emit this event type!');
|
||||
}
|
||||
|
||||
if (this._listeners.hasOwnProperty(eventType)) {
|
||||
this._listeners[eventType].push(listener);
|
||||
} else {
|
||||
this._listeners[eventType] = [listener];
|
||||
}
|
||||
|
||||
let bound = this;
|
||||
return {
|
||||
dispose: () => {
|
||||
if (!bound) {
|
||||
// Already called
|
||||
return;
|
||||
}
|
||||
|
||||
bound._removeListener(eventType, listener);
|
||||
|
||||
// Prevent leakers from holding on to the event emitter
|
||||
bound = null;
|
||||
listener = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public addOneTimeListener(eventType: string, listener: ListenerCallback): IDisposable {
|
||||
const disposable = this.addListener(eventType, value => {
|
||||
disposable.dispose();
|
||||
listener(value);
|
||||
});
|
||||
|
||||
return disposable;
|
||||
}
|
||||
|
||||
public addBulkListener(listener: BulkListenerCallback): IDisposable {
|
||||
|
||||
this._bulkListeners.push(listener);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this._removeBulkListener(listener);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public addEmitter(eventEmitter: IBaseEventEmitter): IDisposable {
|
||||
return eventEmitter.addBulkListener((events: EmitterEvent[]): void => {
|
||||
if (this._deferredCnt === 0) {
|
||||
this._emitEvents(events);
|
||||
} else {
|
||||
// Collect for later
|
||||
this._collectedEvents.push.apply(this._collectedEvents, events);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _removeListener(eventType: string, listener: ListenerCallback): void {
|
||||
if (this._listeners.hasOwnProperty(eventType)) {
|
||||
let listeners = this._listeners[eventType];
|
||||
for (let i = 0, len = listeners.length; i < len; i++) {
|
||||
if (listeners[i] === listener) {
|
||||
listeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _removeBulkListener(listener: BulkListenerCallback): void {
|
||||
for (let i = 0, len = this._bulkListeners.length; i < len; i++) {
|
||||
if (this._bulkListeners[i] === listener) {
|
||||
this._bulkListeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _emitToSpecificTypeListeners(eventType: string, data: any): void {
|
||||
if (this._listeners.hasOwnProperty(eventType)) {
|
||||
const listeners = this._listeners[eventType].slice(0);
|
||||
for (let i = 0, len = listeners.length; i < len; i++) {
|
||||
safeInvoke1Arg(listeners[i], data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _emitToBulkListeners(events: EmitterEvent[]): void {
|
||||
const bulkListeners = this._bulkListeners.slice(0);
|
||||
for (let i = 0, len = bulkListeners.length; i < len; i++) {
|
||||
safeInvoke1Arg(bulkListeners[i], events);
|
||||
}
|
||||
}
|
||||
|
||||
protected _emitEvents(events: EmitterEvent[]): void {
|
||||
if (this._bulkListeners.length > 0) {
|
||||
this._emitToBulkListeners(events);
|
||||
}
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
const e = events[i];
|
||||
|
||||
this._emitToSpecificTypeListeners(e.type, e.data);
|
||||
}
|
||||
}
|
||||
|
||||
public emit(eventType: string, data: any = {}): void {
|
||||
if (this._allowedEventTypes && !this._allowedEventTypes.hasOwnProperty(eventType)) {
|
||||
throw new Error('Cannot emit this event type because it wasn\'t listed!');
|
||||
}
|
||||
// Early return if no listeners would get this
|
||||
if (!this._listeners.hasOwnProperty(eventType) && this._bulkListeners.length === 0) {
|
||||
return;
|
||||
}
|
||||
const emitterEvent = new EmitterEvent(eventType, data);
|
||||
|
||||
if (this._deferredCnt === 0) {
|
||||
this._emitEvents([emitterEvent]);
|
||||
} else {
|
||||
// Collect for later
|
||||
this._collectedEvents.push(emitterEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public beginDeferredEmit(): void {
|
||||
this._deferredCnt = this._deferredCnt + 1;
|
||||
}
|
||||
|
||||
public endDeferredEmit(): void {
|
||||
this._deferredCnt = this._deferredCnt - 1;
|
||||
|
||||
if (this._deferredCnt === 0) {
|
||||
this._emitCollected();
|
||||
}
|
||||
}
|
||||
|
||||
public deferredEmit<T>(callback: () => T): T {
|
||||
this.beginDeferredEmit();
|
||||
|
||||
let result: T = safeInvokeNoArg<T>(callback);
|
||||
|
||||
this.endDeferredEmit();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _emitCollected(): void {
|
||||
if (this._collectedEvents.length === 0) {
|
||||
return;
|
||||
}
|
||||
// Flush collected events
|
||||
const events = this._collectedEvents;
|
||||
this._collectedEvents = [];
|
||||
this._emitEvents(events);
|
||||
}
|
||||
}
|
||||
|
||||
class EmitQueueElement {
|
||||
public target: Function;
|
||||
public arg: any;
|
||||
|
||||
constructor(target: Function, arg: any) {
|
||||
this.target = target;
|
||||
this.arg = arg;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as EventEmitter, but guarantees events are delivered in order to each listener
|
||||
*/
|
||||
export class OrderGuaranteeEventEmitter extends EventEmitter {
|
||||
|
||||
private _emitQueue: EmitQueueElement[];
|
||||
|
||||
constructor() {
|
||||
super(null);
|
||||
this._emitQueue = [];
|
||||
}
|
||||
|
||||
protected _emitToSpecificTypeListeners(eventType: string, data: any): void {
|
||||
if (this._listeners.hasOwnProperty(eventType)) {
|
||||
let listeners = this._listeners[eventType];
|
||||
for (let i = 0, len = listeners.length; i < len; i++) {
|
||||
this._emitQueue.push(new EmitQueueElement(listeners[i], data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _emitToBulkListeners(events: EmitterEvent[]): void {
|
||||
let bulkListeners = this._bulkListeners;
|
||||
for (let i = 0, len = bulkListeners.length; i < len; i++) {
|
||||
this._emitQueue.push(new EmitQueueElement(bulkListeners[i], events));
|
||||
}
|
||||
}
|
||||
|
||||
protected _emitEvents(events: EmitterEvent[]): void {
|
||||
super._emitEvents(events);
|
||||
|
||||
while (this._emitQueue.length > 0) {
|
||||
let queueElement = this._emitQueue.shift();
|
||||
safeInvoke1Arg(queueElement.target, queueElement.arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function safeInvokeNoArg<T>(func: Function): T {
|
||||
try {
|
||||
return func();
|
||||
} catch (e) {
|
||||
Errors.onUnexpectedError(e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function safeInvoke1Arg(func: Function, arg1: any): any {
|
||||
try {
|
||||
return func(arg1);
|
||||
} catch (e) {
|
||||
Errors.onUnexpectedError(e);
|
||||
}
|
||||
}
|
||||
78
src/vs/base/common/events.ts
Normal file
78
src/vs/base/common/events.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export class Event {
|
||||
public time: number;
|
||||
public originalEvent: Event;
|
||||
public source: any;
|
||||
|
||||
constructor(originalEvent?: Event) {
|
||||
this.time = (new Date()).getTime();
|
||||
this.originalEvent = originalEvent;
|
||||
this.source = null;
|
||||
}
|
||||
}
|
||||
|
||||
export class PropertyChangeEvent extends Event {
|
||||
public key: string;
|
||||
public oldValue: any;
|
||||
public newValue: any;
|
||||
|
||||
constructor(key?: string, oldValue?: any, newValue?: any, originalEvent?: Event) {
|
||||
super(originalEvent);
|
||||
|
||||
this.key = key;
|
||||
this.oldValue = oldValue;
|
||||
this.newValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewerEvent extends Event {
|
||||
public element: any;
|
||||
|
||||
constructor(element: any, originalEvent?: Event) {
|
||||
super(originalEvent);
|
||||
|
||||
this.element = element;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ISelectionEvent {
|
||||
selection: any[];
|
||||
payload?: any;
|
||||
source: any;
|
||||
}
|
||||
|
||||
export interface IFocusEvent {
|
||||
focus: any;
|
||||
payload?: any;
|
||||
source: any;
|
||||
}
|
||||
|
||||
export interface IHighlightEvent {
|
||||
highlight: any;
|
||||
payload?: any;
|
||||
source: any;
|
||||
}
|
||||
|
||||
export const EventType = {
|
||||
PROPERTY_CHANGED: 'propertyChanged',
|
||||
SELECTION: 'selection',
|
||||
FOCUS: 'focus',
|
||||
BLUR: 'blur',
|
||||
HIGHLIGHT: 'highlight',
|
||||
EXPAND: 'expand',
|
||||
COLLAPSE: 'collapse',
|
||||
TOGGLE: 'toggle',
|
||||
BEFORE_RUN: 'beforeRun',
|
||||
RUN: 'run',
|
||||
EDIT: 'edit',
|
||||
SAVE: 'save',
|
||||
CANCEL: 'cancel',
|
||||
CHANGE: 'change',
|
||||
DISPOSE: 'dispose',
|
||||
};
|
||||
|
||||
716
src/vs/base/common/filters.ts
Normal file
716
src/vs/base/common/filters.ts
Normal file
@@ -0,0 +1,716 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import strings = require('vs/base/common/strings');
|
||||
import { BoundedMap } from 'vs/base/common/map';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export interface IFilter {
|
||||
// Returns null if word doesn't match.
|
||||
(word: string, wordToMatchAgainst: string): IMatch[];
|
||||
}
|
||||
|
||||
export interface IMatch {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
// Combined filters
|
||||
|
||||
/**
|
||||
* @returns A filter which combines the provided set
|
||||
* of filters with an or. The *first* filters that
|
||||
* matches defined the return value of the returned
|
||||
* filter.
|
||||
*/
|
||||
export function or(...filter: IFilter[]): IFilter {
|
||||
return function (word: string, wordToMatchAgainst: string): IMatch[] {
|
||||
for (let i = 0, len = filter.length; i < len; i++) {
|
||||
let match = filter[i](word, wordToMatchAgainst);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns A filter which combines the provided set
|
||||
* of filters with an and. The combines matches are
|
||||
* returned if *all* filters match.
|
||||
*/
|
||||
export function and(...filter: IFilter[]): IFilter {
|
||||
return function (word: string, wordToMatchAgainst: string): IMatch[] {
|
||||
let result: IMatch[] = [];
|
||||
for (let i = 0, len = filter.length; i < len; i++) {
|
||||
let match = filter[i](word, wordToMatchAgainst);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
result = result.concat(match);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
// Prefix
|
||||
|
||||
export const matchesStrictPrefix: IFilter = _matchesPrefix.bind(undefined, false);
|
||||
export const matchesPrefix: IFilter = _matchesPrefix.bind(undefined, true);
|
||||
|
||||
function _matchesPrefix(ignoreCase: boolean, word: string, wordToMatchAgainst: string): IMatch[] {
|
||||
if (!wordToMatchAgainst || wordToMatchAgainst.length < word.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let matches: boolean;
|
||||
if (ignoreCase) {
|
||||
matches = strings.beginsWithIgnoreCase(wordToMatchAgainst, word);
|
||||
} else {
|
||||
matches = wordToMatchAgainst.indexOf(word) === 0;
|
||||
}
|
||||
|
||||
if (!matches) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return word.length > 0 ? [{ start: 0, end: word.length }] : [];
|
||||
}
|
||||
|
||||
// Contiguous Substring
|
||||
|
||||
export function matchesContiguousSubString(word: string, wordToMatchAgainst: string): IMatch[] {
|
||||
let index = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase());
|
||||
if (index === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [{ start: index, end: index + word.length }];
|
||||
}
|
||||
|
||||
// Substring
|
||||
|
||||
export function matchesSubString(word: string, wordToMatchAgainst: string): IMatch[] {
|
||||
return _matchesSubString(word.toLowerCase(), wordToMatchAgainst.toLowerCase(), 0, 0);
|
||||
}
|
||||
|
||||
function _matchesSubString(word: string, wordToMatchAgainst: string, i: number, j: number): IMatch[] {
|
||||
if (i === word.length) {
|
||||
return [];
|
||||
} else if (j === wordToMatchAgainst.length) {
|
||||
return null;
|
||||
} else {
|
||||
if (word[i] === wordToMatchAgainst[j]) {
|
||||
let result: IMatch[] = null;
|
||||
if (result = _matchesSubString(word, wordToMatchAgainst, i + 1, j + 1)) {
|
||||
return join({ start: j, end: j + 1 }, result);
|
||||
}
|
||||
}
|
||||
|
||||
return _matchesSubString(word, wordToMatchAgainst, i, j + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// CamelCase
|
||||
|
||||
function isLower(code: number): boolean {
|
||||
return CharCode.a <= code && code <= CharCode.z;
|
||||
}
|
||||
|
||||
function isUpper(code: number): boolean {
|
||||
return CharCode.A <= code && code <= CharCode.Z;
|
||||
}
|
||||
|
||||
function isNumber(code: number): boolean {
|
||||
return CharCode.Digit0 <= code && code <= CharCode.Digit9;
|
||||
}
|
||||
|
||||
function isWhitespace(code: number): boolean {
|
||||
return (
|
||||
code === CharCode.Space
|
||||
|| code === CharCode.Tab
|
||||
|| code === CharCode.LineFeed
|
||||
|| code === CharCode.CarriageReturn
|
||||
);
|
||||
}
|
||||
|
||||
function isAlphanumeric(code: number): boolean {
|
||||
return isLower(code) || isUpper(code) || isNumber(code);
|
||||
}
|
||||
|
||||
function join(head: IMatch, tail: IMatch[]): IMatch[] {
|
||||
if (tail.length === 0) {
|
||||
tail = [head];
|
||||
} else if (head.end === tail[0].start) {
|
||||
tail[0].start = head.start;
|
||||
} else {
|
||||
tail.unshift(head);
|
||||
}
|
||||
return tail;
|
||||
}
|
||||
|
||||
function nextAnchor(camelCaseWord: string, start: number): number {
|
||||
for (let i = start; i < camelCaseWord.length; i++) {
|
||||
let c = camelCaseWord.charCodeAt(i);
|
||||
if (isUpper(c) || isNumber(c) || (i > 0 && !isAlphanumeric(camelCaseWord.charCodeAt(i - 1)))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return camelCaseWord.length;
|
||||
}
|
||||
|
||||
function _matchesCamelCase(word: string, camelCaseWord: string, i: number, j: number): IMatch[] {
|
||||
if (i === word.length) {
|
||||
return [];
|
||||
} else if (j === camelCaseWord.length) {
|
||||
return null;
|
||||
} else if (word[i] !== camelCaseWord[j].toLowerCase()) {
|
||||
return null;
|
||||
} else {
|
||||
let result: IMatch[] = null;
|
||||
let nextUpperIndex = j + 1;
|
||||
result = _matchesCamelCase(word, camelCaseWord, i + 1, j + 1);
|
||||
while (!result && (nextUpperIndex = nextAnchor(camelCaseWord, nextUpperIndex)) < camelCaseWord.length) {
|
||||
result = _matchesCamelCase(word, camelCaseWord, i + 1, nextUpperIndex);
|
||||
nextUpperIndex++;
|
||||
}
|
||||
return result === null ? null : join({ start: j, end: j + 1 }, result);
|
||||
}
|
||||
}
|
||||
|
||||
interface ICamelCaseAnalysis {
|
||||
upperPercent: number;
|
||||
lowerPercent: number;
|
||||
alphaPercent: number;
|
||||
numericPercent: number;
|
||||
}
|
||||
|
||||
// Heuristic to avoid computing camel case matcher for words that don't
|
||||
// look like camelCaseWords.
|
||||
function analyzeCamelCaseWord(word: string): ICamelCaseAnalysis {
|
||||
let upper = 0, lower = 0, alpha = 0, numeric = 0, code = 0;
|
||||
|
||||
for (let i = 0; i < word.length; i++) {
|
||||
code = word.charCodeAt(i);
|
||||
|
||||
if (isUpper(code)) { upper++; }
|
||||
if (isLower(code)) { lower++; }
|
||||
if (isAlphanumeric(code)) { alpha++; }
|
||||
if (isNumber(code)) { numeric++; }
|
||||
}
|
||||
|
||||
let upperPercent = upper / word.length;
|
||||
let lowerPercent = lower / word.length;
|
||||
let alphaPercent = alpha / word.length;
|
||||
let numericPercent = numeric / word.length;
|
||||
|
||||
return { upperPercent, lowerPercent, alphaPercent, numericPercent };
|
||||
}
|
||||
|
||||
function isUpperCaseWord(analysis: ICamelCaseAnalysis): boolean {
|
||||
const { upperPercent, lowerPercent } = analysis;
|
||||
return lowerPercent === 0 && upperPercent > 0.6;
|
||||
}
|
||||
|
||||
function isCamelCaseWord(analysis: ICamelCaseAnalysis): boolean {
|
||||
const { upperPercent, lowerPercent, alphaPercent, numericPercent } = analysis;
|
||||
return lowerPercent > 0.2 && upperPercent < 0.8 && alphaPercent > 0.6 && numericPercent < 0.2;
|
||||
}
|
||||
|
||||
// Heuristic to avoid computing camel case matcher for words that don't
|
||||
// look like camel case patterns.
|
||||
function isCamelCasePattern(word: string): boolean {
|
||||
let upper = 0, lower = 0, code = 0, whitespace = 0;
|
||||
|
||||
for (let i = 0; i < word.length; i++) {
|
||||
code = word.charCodeAt(i);
|
||||
|
||||
if (isUpper(code)) { upper++; }
|
||||
if (isLower(code)) { lower++; }
|
||||
if (isWhitespace(code)) { whitespace++; }
|
||||
}
|
||||
|
||||
if ((upper === 0 || lower === 0) && whitespace === 0) {
|
||||
return word.length <= 30;
|
||||
} else {
|
||||
return upper <= 5;
|
||||
}
|
||||
}
|
||||
|
||||
export function matchesCamelCase(word: string, camelCaseWord: string): IMatch[] {
|
||||
if (!camelCaseWord || camelCaseWord.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isCamelCasePattern(word)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (camelCaseWord.length > 60) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const analysis = analyzeCamelCaseWord(camelCaseWord);
|
||||
|
||||
if (!isCamelCaseWord(analysis)) {
|
||||
if (!isUpperCaseWord(analysis)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
camelCaseWord = camelCaseWord.toLowerCase();
|
||||
}
|
||||
|
||||
let result: IMatch[] = null;
|
||||
let i = 0;
|
||||
|
||||
while (i < camelCaseWord.length && (result = _matchesCamelCase(word.toLowerCase(), camelCaseWord, 0, i)) === null) {
|
||||
i = nextAnchor(camelCaseWord, i + 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Matches beginning of words supporting non-ASCII languages
|
||||
// If `contiguous` is true then matches word with beginnings of the words in the target. E.g. "pul" will match "Git: Pull"
|
||||
// Otherwise also matches sub string of the word with beginnings of the words in the target. E.g. "gp" or "g p" will match "Git: Pull"
|
||||
// Useful in cases where the target is words (e.g. command labels)
|
||||
|
||||
export function matchesWords(word: string, target: string, contiguous: boolean = false): IMatch[] {
|
||||
if (!target || target.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result: IMatch[] = null;
|
||||
let i = 0;
|
||||
|
||||
while (i < target.length && (result = _matchesWords(word.toLowerCase(), target, 0, i, contiguous)) === null) {
|
||||
i = nextWord(target, i + 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function _matchesWords(word: string, target: string, i: number, j: number, contiguous: boolean): IMatch[] {
|
||||
if (i === word.length) {
|
||||
return [];
|
||||
} else if (j === target.length) {
|
||||
return null;
|
||||
} else if (word[i] !== target[j].toLowerCase()) {
|
||||
return null;
|
||||
} else {
|
||||
let result: IMatch[] = null;
|
||||
let nextWordIndex = j + 1;
|
||||
result = _matchesWords(word, target, i + 1, j + 1, contiguous);
|
||||
if (!contiguous) {
|
||||
while (!result && (nextWordIndex = nextWord(target, nextWordIndex)) < target.length) {
|
||||
result = _matchesWords(word, target, i + 1, nextWordIndex, contiguous);
|
||||
nextWordIndex++;
|
||||
}
|
||||
}
|
||||
return result === null ? null : join({ start: j, end: j + 1 }, result);
|
||||
}
|
||||
}
|
||||
|
||||
function nextWord(word: string, start: number): number {
|
||||
for (let i = start; i < word.length; i++) {
|
||||
let c = word.charCodeAt(i);
|
||||
if (isWhitespace(c) || (i > 0 && isWhitespace(word.charCodeAt(i - 1)))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return word.length;
|
||||
}
|
||||
|
||||
// Fuzzy
|
||||
|
||||
export enum SubstringMatching {
|
||||
Contiguous,
|
||||
Separate
|
||||
}
|
||||
|
||||
export const fuzzyContiguousFilter = or(matchesPrefix, matchesCamelCase, matchesContiguousSubString);
|
||||
const fuzzySeparateFilter = or(matchesPrefix, matchesCamelCase, matchesSubString);
|
||||
const fuzzyRegExpCache = new BoundedMap<RegExp>(10000); // bounded to 10000 elements
|
||||
|
||||
export function matchesFuzzy(word: string, wordToMatchAgainst: string, enableSeparateSubstringMatching = false): IMatch[] {
|
||||
if (typeof word !== 'string' || typeof wordToMatchAgainst !== 'string') {
|
||||
return null; // return early for invalid input
|
||||
}
|
||||
|
||||
// Form RegExp for wildcard matches
|
||||
let regexp = fuzzyRegExpCache.get(word);
|
||||
if (!regexp) {
|
||||
regexp = new RegExp(strings.convertSimple2RegExpPattern(word), 'i');
|
||||
fuzzyRegExpCache.set(word, regexp);
|
||||
}
|
||||
|
||||
// RegExp Filter
|
||||
let match: RegExpExecArray = regexp.exec(wordToMatchAgainst);
|
||||
if (match) {
|
||||
return [{ start: match.index, end: match.index + match[0].length }];
|
||||
}
|
||||
|
||||
// Default Filter
|
||||
return enableSeparateSubstringMatching ? fuzzySeparateFilter(word, wordToMatchAgainst) : fuzzyContiguousFilter(word, wordToMatchAgainst);
|
||||
}
|
||||
|
||||
export function createMatches(position: number[]): IMatch[] {
|
||||
let ret: IMatch[] = [];
|
||||
if (!position) {
|
||||
return ret;
|
||||
}
|
||||
let last: IMatch;
|
||||
for (const pos of position) {
|
||||
if (last && last.end === pos) {
|
||||
last.end += 1;
|
||||
} else {
|
||||
last = { start: pos, end: pos + 1 };
|
||||
ret.push(last);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function initTable() {
|
||||
const table: number[][] = [];
|
||||
const row: number[] = [0];
|
||||
for (let i = 1; i <= 100; i++) {
|
||||
row.push(-i);
|
||||
}
|
||||
for (let i = 0; i <= 100; i++) {
|
||||
let thisRow = row.slice(0);
|
||||
thisRow[0] = -i;
|
||||
table.push(thisRow);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
const _table = initTable();
|
||||
const _scores = initTable();
|
||||
const _arrows = <Arrow[][]>initTable();
|
||||
const _debug = false;
|
||||
|
||||
function printTable(table: number[][], pattern: string, patternLen: number, word: string, wordLen: number): string {
|
||||
function pad(s: string, n: number, pad = ' ') {
|
||||
while (s.length < n) {
|
||||
s = pad + s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
let ret = ` | |${word.split('').map(c => pad(c, 3)).join('|')}\n`;
|
||||
|
||||
for (let i = 0; i <= patternLen; i++) {
|
||||
if (i === 0) {
|
||||
ret += ' |';
|
||||
} else {
|
||||
ret += `${pattern[i - 1]}|`;
|
||||
}
|
||||
ret += table[i].slice(0, wordLen + 1).map(n => pad(n.toString(), 3)).join('|') + '\n';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const _seps: { [ch: string]: boolean } = Object.create(null);
|
||||
_seps['_'] = true;
|
||||
_seps['-'] = true;
|
||||
_seps['.'] = true;
|
||||
_seps[' '] = true;
|
||||
_seps['/'] = true;
|
||||
_seps['\\'] = true;
|
||||
_seps['\''] = true;
|
||||
_seps['"'] = true;
|
||||
_seps[':'] = true;
|
||||
|
||||
const _ws: { [ch: string]: boolean } = Object.create(null);
|
||||
_ws[' '] = true;
|
||||
_ws['\t'] = true;
|
||||
|
||||
const enum Arrow { Top = 0b1, Diag = 0b10, Left = 0b100 }
|
||||
|
||||
export function fuzzyScore(pattern: string, word: string, patternMaxWhitespaceIgnore?: number): [number, number[]] {
|
||||
|
||||
const patternLen = pattern.length > 100 ? 100 : pattern.length;
|
||||
const wordLen = word.length > 100 ? 100 : word.length;
|
||||
|
||||
// Check for leading whitespace in the pattern and
|
||||
// start matching just after that position. This is
|
||||
// like `pattern = pattern.rtrim()` but doesn't create
|
||||
// a new string
|
||||
let patternStartPos = 0;
|
||||
if (patternMaxWhitespaceIgnore === undefined) {
|
||||
patternMaxWhitespaceIgnore = patternLen;
|
||||
}
|
||||
while (patternStartPos < patternMaxWhitespaceIgnore) {
|
||||
if (_ws[pattern[patternStartPos]]) {
|
||||
patternStartPos += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (patternStartPos === patternLen) {
|
||||
return [-100, []];
|
||||
}
|
||||
|
||||
if (patternLen > wordLen) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const lowPattern = pattern.toLowerCase();
|
||||
const lowWord = word.toLowerCase();
|
||||
|
||||
let patternPos = patternStartPos;
|
||||
let wordPos = 0;
|
||||
|
||||
// Run a simple check if the characters of pattern occur
|
||||
// (in order) at all in word. If that isn't the case we
|
||||
// stop because no match will be possible
|
||||
while (patternPos < patternLen && wordPos < wordLen) {
|
||||
if (lowPattern[patternPos] === lowWord[wordPos]) {
|
||||
patternPos += 1;
|
||||
}
|
||||
wordPos += 1;
|
||||
}
|
||||
if (patternPos !== patternLen) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// There will be a mach, fill in tables
|
||||
for (patternPos = patternStartPos + 1; patternPos <= patternLen; patternPos++) {
|
||||
|
||||
let lastLowWordChar = '';
|
||||
|
||||
for (wordPos = 1; wordPos <= wordLen; wordPos++) {
|
||||
|
||||
let score = -1;
|
||||
let lowWordChar = lowWord[wordPos - 1];
|
||||
if (lowPattern[patternPos - 1] === lowWordChar) {
|
||||
if (wordPos === (patternPos - patternStartPos)) {
|
||||
// common prefix: `foobar <-> foobaz`
|
||||
if (pattern[patternPos - 1] === word[wordPos - 1]) {
|
||||
score = 7;
|
||||
} else {
|
||||
score = 5;
|
||||
}
|
||||
} else if (lowWordChar !== word[wordPos - 1]) {
|
||||
// hitting upper-case: `foo <-> forOthers`
|
||||
if (pattern[patternPos - 1] === word[wordPos - 1]) {
|
||||
score = 7;
|
||||
} else {
|
||||
score = 5;
|
||||
}
|
||||
} else if (_seps[lastLowWordChar]) {
|
||||
// post separator: `foo <-> bar_foo`
|
||||
score = 5;
|
||||
|
||||
} else {
|
||||
score = 1;
|
||||
}
|
||||
}
|
||||
|
||||
_scores[patternPos][wordPos] = score;
|
||||
|
||||
let diag = _table[patternPos - 1][wordPos - 1] + (score > 1 ? 1 : score);
|
||||
let top = _table[patternPos - 1][wordPos] + -1;
|
||||
let left = _table[patternPos][wordPos - 1] + -1;
|
||||
|
||||
if (left >= top) {
|
||||
// left or diag
|
||||
if (left > diag) {
|
||||
_table[patternPos][wordPos] = left;
|
||||
_arrows[patternPos][wordPos] = Arrow.Left;
|
||||
} else if (left === diag) {
|
||||
_table[patternPos][wordPos] = left;
|
||||
_arrows[patternPos][wordPos] = Arrow.Left | Arrow.Diag;
|
||||
} else {
|
||||
_table[patternPos][wordPos] = diag;
|
||||
_arrows[patternPos][wordPos] = Arrow.Diag;
|
||||
}
|
||||
} else {
|
||||
// top or diag
|
||||
if (top > diag) {
|
||||
_table[patternPos][wordPos] = top;
|
||||
_arrows[patternPos][wordPos] = Arrow.Top;
|
||||
} else if (top === diag) {
|
||||
_table[patternPos][wordPos] = top;
|
||||
_arrows[patternPos][wordPos] = Arrow.Top | Arrow.Diag;
|
||||
} else {
|
||||
_table[patternPos][wordPos] = diag;
|
||||
_arrows[patternPos][wordPos] = Arrow.Diag;
|
||||
}
|
||||
}
|
||||
|
||||
lastLowWordChar = lowWordChar;
|
||||
}
|
||||
}
|
||||
|
||||
if (_debug) {
|
||||
console.log(printTable(_table, pattern, patternLen, word, wordLen));
|
||||
console.log(printTable(_arrows, pattern, patternLen, word, wordLen));
|
||||
console.log(printTable(_scores, pattern, patternLen, word, wordLen));
|
||||
}
|
||||
|
||||
// _bucket is an array of [PrefixArray] we use to keep
|
||||
// track of scores and matches. After calling `_findAllMatches`
|
||||
// the best match (if available) is the first item in the array
|
||||
_bucket.length = 0;
|
||||
_topScore = -100;
|
||||
_patternStartPos = patternStartPos;
|
||||
_findAllMatches(patternLen, wordLen, 0, new LazyArray(), false);
|
||||
|
||||
if (_bucket.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [_topScore, _bucket[0].toArray()];
|
||||
}
|
||||
|
||||
let _bucket: LazyArray[] = [];
|
||||
let _topScore: number = 0;
|
||||
let _patternStartPos: number = 0;
|
||||
|
||||
function _findAllMatches(patternPos: number, wordPos: number, total: number, matches: LazyArray, lastMatched: boolean): void {
|
||||
|
||||
if (_bucket.length >= 10 || total < -25) {
|
||||
// stop when having already 10 results, or
|
||||
// when a potential alignment as already 5 gaps
|
||||
return;
|
||||
}
|
||||
|
||||
let simpleMatchCount = 0;
|
||||
|
||||
while (patternPos > _patternStartPos && wordPos > 0) {
|
||||
|
||||
let score = _scores[patternPos][wordPos];
|
||||
let arrow = _arrows[patternPos][wordPos];
|
||||
|
||||
if (arrow === Arrow.Left) {
|
||||
// left
|
||||
wordPos -= 1;
|
||||
if (lastMatched) {
|
||||
total -= 5; // new gap penalty
|
||||
} else if (!matches.isEmpty()) {
|
||||
total -= 1; // gap penalty after first match
|
||||
}
|
||||
lastMatched = false;
|
||||
simpleMatchCount = 0;
|
||||
|
||||
} else if (arrow & Arrow.Diag) {
|
||||
|
||||
if (arrow & Arrow.Left) {
|
||||
// left
|
||||
_findAllMatches(
|
||||
patternPos,
|
||||
wordPos - 1,
|
||||
!matches.isEmpty() ? total - 1 : total, // gap penalty after first match
|
||||
matches.slice(),
|
||||
lastMatched
|
||||
);
|
||||
}
|
||||
|
||||
// diag
|
||||
total += score;
|
||||
patternPos -= 1;
|
||||
wordPos -= 1;
|
||||
matches.unshift(wordPos);
|
||||
lastMatched = true;
|
||||
|
||||
// count simple matches and boost a row of
|
||||
// simple matches when they yield in a
|
||||
// strong match.
|
||||
if (score === 1) {
|
||||
simpleMatchCount += 1;
|
||||
|
||||
if (patternPos === _patternStartPos) {
|
||||
// when the first match is a weak
|
||||
// match we discard it
|
||||
return undefined;
|
||||
}
|
||||
|
||||
} else {
|
||||
// boost
|
||||
total += 1 + (simpleMatchCount * (score - 1));
|
||||
simpleMatchCount = 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
total -= wordPos >= 3 ? 9 : wordPos * 3; // late start penalty
|
||||
|
||||
// dynamically keep track of the current top score
|
||||
// and insert the current best score at head, the rest at tail
|
||||
if (total > _topScore) {
|
||||
_topScore = total;
|
||||
_bucket.unshift(matches);
|
||||
} else {
|
||||
_bucket.push(matches);
|
||||
}
|
||||
}
|
||||
|
||||
class LazyArray {
|
||||
|
||||
private _parent: LazyArray;
|
||||
private _parentLen: number;
|
||||
private _data: number[];
|
||||
|
||||
isEmpty(): boolean {
|
||||
return !this._data && (!this._parent || this._parent.isEmpty());
|
||||
}
|
||||
|
||||
unshift(n: number) {
|
||||
if (!this._data) {
|
||||
this._data = [n];
|
||||
} else {
|
||||
this._data.unshift(n);
|
||||
}
|
||||
}
|
||||
|
||||
slice(): LazyArray {
|
||||
const ret = new LazyArray();
|
||||
ret._parent = this;
|
||||
ret._parentLen = this._data ? this._data.length : 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
toArray(): number[] {
|
||||
if (!this._data) {
|
||||
return this._parent.toArray();
|
||||
}
|
||||
const bucket: number[][] = [];
|
||||
let element = <LazyArray>this;
|
||||
while (element) {
|
||||
if (element._parent && element._parent._data) {
|
||||
bucket.push(element._parent._data.slice(element._parent._data.length - element._parentLen));
|
||||
}
|
||||
element = element._parent;
|
||||
}
|
||||
return Array.prototype.concat.apply(this._data, bucket);
|
||||
}
|
||||
}
|
||||
|
||||
export function nextTypoPermutation(pattern: string, patternPos: number) {
|
||||
|
||||
if (patternPos + 1 >= pattern.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return pattern.slice(0, patternPos)
|
||||
+ pattern[patternPos + 1]
|
||||
+ pattern[patternPos]
|
||||
+ pattern.slice(patternPos + 2);
|
||||
}
|
||||
|
||||
export function fuzzyScoreGraceful(pattern: string, word: string): [number, number[]] {
|
||||
let ret = fuzzyScore(pattern, word);
|
||||
for (let patternPos = 1; patternPos < pattern.length - 1 && !ret; patternPos++) {
|
||||
let pattern2 = nextTypoPermutation(pattern, patternPos);
|
||||
ret = fuzzyScore(pattern2, word);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
28
src/vs/base/common/functional.ts
Normal file
28
src/vs/base/common/functional.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export function not<A>(fn: (a: A) => boolean): (a: A) => boolean;
|
||||
export function not(fn: Function): Function {
|
||||
return (...args) => !fn(...args);
|
||||
}
|
||||
|
||||
export function once<T extends Function>(fn: T): T {
|
||||
const _this = this;
|
||||
let didCall = false;
|
||||
let result: any;
|
||||
|
||||
return function () {
|
||||
if (didCall) {
|
||||
return result;
|
||||
}
|
||||
|
||||
didCall = true;
|
||||
result = fn.apply(_this, arguments);
|
||||
|
||||
return result;
|
||||
} as any as T;
|
||||
}
|
||||
648
src/vs/base/common/glob.ts
Normal file
648
src/vs/base/common/glob.ts
Normal file
@@ -0,0 +1,648 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import objects = require('vs/base/common/objects');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import paths = require('vs/base/common/paths');
|
||||
import { BoundedMap } from 'vs/base/common/map';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
export interface IExpression {
|
||||
[pattern: string]: boolean | SiblingClause | any;
|
||||
}
|
||||
|
||||
export function getEmptyExpression(): IExpression {
|
||||
return Object.create(null);
|
||||
}
|
||||
|
||||
export function mergeExpressions(...expressions: IExpression[]): IExpression {
|
||||
return objects.assign(getEmptyExpression(), ...expressions.filter(expr => !!expr));
|
||||
}
|
||||
|
||||
export interface SiblingClause {
|
||||
when: string;
|
||||
}
|
||||
|
||||
const PATH_REGEX = '[/\\\\]'; // any slash or backslash
|
||||
const NO_PATH_REGEX = '[^/\\\\]'; // any non-slash and non-backslash
|
||||
const ALL_FORWARD_SLASHES = /\//g;
|
||||
|
||||
function starsToRegExp(starCount: number): string {
|
||||
switch (starCount) {
|
||||
case 0:
|
||||
return '';
|
||||
case 1:
|
||||
return `${NO_PATH_REGEX}*?`; // 1 star matches any number of characters except path separator (/ and \) - non greedy (?)
|
||||
default:
|
||||
// Matches: (Path Sep OR Path Val followed by Path Sep OR Path Sep followed by Path Val) 0-many times
|
||||
// Group is non capturing because we don't need to capture at all (?:...)
|
||||
// Overall we use non-greedy matching because it could be that we match too much
|
||||
return `(?:${PATH_REGEX}|${NO_PATH_REGEX}+${PATH_REGEX}|${PATH_REGEX}${NO_PATH_REGEX}+)*?`;
|
||||
}
|
||||
}
|
||||
|
||||
export function splitGlobAware(pattern: string, splitChar: string): string[] {
|
||||
if (!pattern) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let segments: string[] = [];
|
||||
|
||||
let inBraces = false;
|
||||
let inBrackets = false;
|
||||
|
||||
let char: string;
|
||||
let curVal = '';
|
||||
for (let i = 0; i < pattern.length; i++) {
|
||||
char = pattern[i];
|
||||
|
||||
switch (char) {
|
||||
case splitChar:
|
||||
if (!inBraces && !inBrackets) {
|
||||
segments.push(curVal);
|
||||
curVal = '';
|
||||
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case '{':
|
||||
inBraces = true;
|
||||
break;
|
||||
case '}':
|
||||
inBraces = false;
|
||||
break;
|
||||
case '[':
|
||||
inBrackets = true;
|
||||
break;
|
||||
case ']':
|
||||
inBrackets = false;
|
||||
break;
|
||||
}
|
||||
|
||||
curVal += char;
|
||||
}
|
||||
|
||||
// Tail
|
||||
if (curVal) {
|
||||
segments.push(curVal);
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
function parseRegExp(pattern: string): string {
|
||||
if (!pattern) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let regEx = '';
|
||||
|
||||
// Split up into segments for each slash found
|
||||
let segments = splitGlobAware(pattern, '/');
|
||||
|
||||
// Special case where we only have globstars
|
||||
if (segments.every(s => s === '**')) {
|
||||
regEx = '.*';
|
||||
}
|
||||
|
||||
// Build regex over segments
|
||||
else {
|
||||
let previousSegmentWasGlobStar = false;
|
||||
segments.forEach((segment, index) => {
|
||||
|
||||
// Globstar is special
|
||||
if (segment === '**') {
|
||||
|
||||
// if we have more than one globstar after another, just ignore it
|
||||
if (!previousSegmentWasGlobStar) {
|
||||
regEx += starsToRegExp(2);
|
||||
previousSegmentWasGlobStar = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// States
|
||||
let inBraces = false;
|
||||
let braceVal = '';
|
||||
|
||||
let inBrackets = false;
|
||||
let bracketVal = '';
|
||||
|
||||
let char: string;
|
||||
for (let i = 0; i < segment.length; i++) {
|
||||
char = segment[i];
|
||||
|
||||
// Support brace expansion
|
||||
if (char !== '}' && inBraces) {
|
||||
braceVal += char;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Support brackets
|
||||
if (char !== ']' && inBrackets) {
|
||||
let res: string;
|
||||
switch (char) {
|
||||
case '-': // allow the range operator
|
||||
res = char;
|
||||
break;
|
||||
case '^': // allow the negate operator
|
||||
res = char;
|
||||
break;
|
||||
default:
|
||||
res = strings.escapeRegExpCharacters(char);
|
||||
}
|
||||
|
||||
bracketVal += res;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (char) {
|
||||
case '{':
|
||||
inBraces = true;
|
||||
continue;
|
||||
|
||||
case '[':
|
||||
inBrackets = true;
|
||||
continue;
|
||||
|
||||
case '}':
|
||||
let choices = splitGlobAware(braceVal, ',');
|
||||
|
||||
// Converts {foo,bar} => [foo|bar]
|
||||
let braceRegExp = `(?:${choices.map(c => parseRegExp(c)).join('|')})`;
|
||||
|
||||
regEx += braceRegExp;
|
||||
|
||||
inBraces = false;
|
||||
braceVal = '';
|
||||
|
||||
break;
|
||||
|
||||
case ']':
|
||||
regEx += ('[' + bracketVal + ']');
|
||||
|
||||
inBrackets = false;
|
||||
bracketVal = '';
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case '?':
|
||||
regEx += NO_PATH_REGEX; // 1 ? matches any single character except path separator (/ and \)
|
||||
continue;
|
||||
|
||||
case '*':
|
||||
regEx += starsToRegExp(1);
|
||||
continue;
|
||||
|
||||
default:
|
||||
regEx += strings.escapeRegExpCharacters(char);
|
||||
}
|
||||
}
|
||||
|
||||
// Tail: Add the slash we had split on if there is more to come and the next one is not a globstar
|
||||
if (index < segments.length - 1 && segments[index + 1] !== '**') {
|
||||
regEx += PATH_REGEX;
|
||||
}
|
||||
|
||||
// reset state
|
||||
previousSegmentWasGlobStar = false;
|
||||
});
|
||||
}
|
||||
|
||||
return regEx;
|
||||
}
|
||||
|
||||
// regexes to check for trival glob patterns that just check for String#endsWith
|
||||
const T1 = /^\*\*\/\*\.[\w\.-]+$/; // **/*.something
|
||||
const T2 = /^\*\*\/([\w\.-]+)\/?$/; // **/something
|
||||
const T3 = /^{\*\*\/[\*\.]?[\w\.-]+\/?(,\*\*\/[\*\.]?[\w\.-]+\/?)*}$/; // {**/*.something,**/*.else} or {**/package.json,**/project.json}
|
||||
const T3_2 = /^{\*\*\/[\*\.]?[\w\.-]+(\/(\*\*)?)?(,\*\*\/[\*\.]?[\w\.-]+(\/(\*\*)?)?)*}$/; // Like T3, with optional trailing /**
|
||||
const T4 = /^\*\*((\/[\w\.-]+)+)\/?$/; // **/something/else
|
||||
const T5 = /^([\w\.-]+(\/[\w\.-]+)*)\/?$/; // something/else
|
||||
|
||||
export type ParsedPattern = (path: string, basename?: string) => boolean;
|
||||
|
||||
// The ParsedExpression returns a Promise iff siblingsFn returns a Promise.
|
||||
export type ParsedExpression = (path: string, basename?: string, siblingsFn?: () => string[] | TPromise<string[]>) => string | TPromise<string> /* the matching pattern */;
|
||||
|
||||
export interface IGlobOptions {
|
||||
/**
|
||||
* Simplify patterns for use as exclusion filters during tree traversal to skip entire subtrees. Cannot be used outside of a tree traversal.
|
||||
*/
|
||||
trimForExclusions?: boolean;
|
||||
}
|
||||
|
||||
interface ParsedStringPattern {
|
||||
(path: string, basename: string): string | TPromise<string> /* the matching pattern */;
|
||||
basenames?: string[];
|
||||
patterns?: string[];
|
||||
allBasenames?: string[];
|
||||
allPaths?: string[];
|
||||
}
|
||||
type SiblingsPattern = { siblings: string[], name: string };
|
||||
interface ParsedExpressionPattern {
|
||||
(path: string, basename: string, siblingsPatternFn: () => SiblingsPattern | TPromise<SiblingsPattern>): string | TPromise<string> /* the matching pattern */;
|
||||
requiresSiblings?: boolean;
|
||||
allBasenames?: string[];
|
||||
allPaths?: string[];
|
||||
}
|
||||
|
||||
const CACHE = new BoundedMap<ParsedStringPattern>(10000); // bounded to 10000 elements
|
||||
|
||||
const FALSE = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
const NULL = function (): string {
|
||||
return null;
|
||||
};
|
||||
|
||||
function parsePattern(pattern: string, options: IGlobOptions): ParsedStringPattern {
|
||||
if (!pattern) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Whitespace trimming
|
||||
pattern = pattern.trim();
|
||||
|
||||
// Check cache
|
||||
const patternKey = `${pattern}_${!!options.trimForExclusions}`;
|
||||
let parsedPattern = CACHE.get(patternKey);
|
||||
if (parsedPattern) {
|
||||
return parsedPattern;
|
||||
}
|
||||
|
||||
// Check for Trivias
|
||||
let match: RegExpExecArray;
|
||||
if (T1.test(pattern)) { // common pattern: **/*.txt just need endsWith check
|
||||
const base = pattern.substr(4); // '**/*'.length === 4
|
||||
parsedPattern = function (path, basename) {
|
||||
return path && strings.endsWith(path, base) ? pattern : null;
|
||||
};
|
||||
} else if (match = T2.exec(trimForExclusions(pattern, options))) { // common pattern: **/some.txt just need basename check
|
||||
parsedPattern = trivia2(match[1], pattern);
|
||||
} else if ((options.trimForExclusions ? T3_2 : T3).test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png}
|
||||
parsedPattern = trivia3(pattern, options);
|
||||
} else if (match = T4.exec(trimForExclusions(pattern, options))) { // common pattern: **/something/else just need endsWith check
|
||||
parsedPattern = trivia4and5(match[1].substr(1), pattern, true);
|
||||
} else if (match = T5.exec(trimForExclusions(pattern, options))) { // common pattern: something/else just need equals check
|
||||
parsedPattern = trivia4and5(match[1], pattern, false);
|
||||
}
|
||||
|
||||
// Otherwise convert to pattern
|
||||
else {
|
||||
parsedPattern = toRegExp(pattern);
|
||||
}
|
||||
|
||||
// Cache
|
||||
CACHE.set(patternKey, parsedPattern);
|
||||
|
||||
return parsedPattern;
|
||||
}
|
||||
|
||||
function trimForExclusions(pattern: string, options: IGlobOptions): string {
|
||||
return options.trimForExclusions && strings.endsWith(pattern, '/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later
|
||||
}
|
||||
|
||||
// common pattern: **/some.txt just need basename check
|
||||
function trivia2(base: string, originalPattern: string): ParsedStringPattern {
|
||||
const slashBase = `/${base}`;
|
||||
const backslashBase = `\\${base}`;
|
||||
const parsedPattern: ParsedStringPattern = function (path, basename) {
|
||||
if (!path) {
|
||||
return null;
|
||||
}
|
||||
if (basename) {
|
||||
return basename === base ? originalPattern : null;
|
||||
}
|
||||
return path === base || strings.endsWith(path, slashBase) || strings.endsWith(path, backslashBase) ? originalPattern : null;
|
||||
};
|
||||
const basenames = [base];
|
||||
parsedPattern.basenames = basenames;
|
||||
parsedPattern.patterns = [originalPattern];
|
||||
parsedPattern.allBasenames = basenames;
|
||||
return parsedPattern;
|
||||
}
|
||||
|
||||
// repetition of common patterns (see above) {**/*.txt,**/*.png}
|
||||
function trivia3(pattern: string, options: IGlobOptions): ParsedStringPattern {
|
||||
const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1).split(',')
|
||||
.map(pattern => parsePattern(pattern, options))
|
||||
.filter(pattern => pattern !== NULL), pattern);
|
||||
const n = parsedPatterns.length;
|
||||
if (!n) {
|
||||
return NULL;
|
||||
}
|
||||
if (n === 1) {
|
||||
return <ParsedStringPattern>parsedPatterns[0];
|
||||
}
|
||||
const parsedPattern: ParsedStringPattern = function (path: string, basename: string) {
|
||||
for (let i = 0, n = parsedPatterns.length; i < n; i++) {
|
||||
if ((<ParsedStringPattern>parsedPatterns[i])(path, basename)) {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const withBasenames = arrays.first(parsedPatterns, pattern => !!(<ParsedStringPattern>pattern).allBasenames);
|
||||
if (withBasenames) {
|
||||
parsedPattern.allBasenames = (<ParsedStringPattern>withBasenames).allBasenames;
|
||||
}
|
||||
const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, <string[]>[]);
|
||||
if (allPaths.length) {
|
||||
parsedPattern.allPaths = allPaths;
|
||||
}
|
||||
return parsedPattern;
|
||||
}
|
||||
|
||||
// common patterns: **/something/else just need endsWith check, something/else just needs and equals check
|
||||
function trivia4and5(path: string, pattern: string, matchPathEnds: boolean): ParsedStringPattern {
|
||||
const nativePath = paths.nativeSep !== paths.sep ? path.replace(ALL_FORWARD_SLASHES, paths.nativeSep) : path;
|
||||
const nativePathEnd = paths.nativeSep + nativePath;
|
||||
const parsedPattern: ParsedStringPattern = matchPathEnds ? function (path, basename) {
|
||||
return path && (path === nativePath || strings.endsWith(path, nativePathEnd)) ? pattern : null;
|
||||
} : function (path, basename) {
|
||||
return path && path === nativePath ? pattern : null;
|
||||
};
|
||||
parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + path];
|
||||
return parsedPattern;
|
||||
}
|
||||
|
||||
function toRegExp(pattern: string): ParsedStringPattern {
|
||||
try {
|
||||
const regExp = new RegExp(`^${parseRegExp(pattern)}$`);
|
||||
return function (path: string, basename: string) {
|
||||
regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it!
|
||||
return path && regExp.test(path) ? pattern : null;
|
||||
};
|
||||
} catch (error) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified glob matching. Supports a subset of glob patterns:
|
||||
* - * matches anything inside a path segment
|
||||
* - ? matches 1 character inside a path segment
|
||||
* - ** matches anything including an empty path segment
|
||||
* - simple brace expansion ({js,ts} => js or ts)
|
||||
* - character ranges (using [...])
|
||||
*/
|
||||
export function match(pattern: string, path: string): boolean;
|
||||
export function match(expression: IExpression, path: string, siblingsFn?: () => string[]): string /* the matching pattern */;
|
||||
export function match(arg1: string | IExpression, path: string, siblingsFn?: () => string[]): any {
|
||||
if (!arg1 || !path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parse(<IExpression>arg1)(path, undefined, siblingsFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified glob matching. Supports a subset of glob patterns:
|
||||
* - * matches anything inside a path segment
|
||||
* - ? matches 1 character inside a path segment
|
||||
* - ** matches anything including an empty path segment
|
||||
* - simple brace expansion ({js,ts} => js or ts)
|
||||
* - character ranges (using [...])
|
||||
*/
|
||||
export function parse(pattern: string, options?: IGlobOptions): ParsedPattern;
|
||||
export function parse(expression: IExpression, options?: IGlobOptions): ParsedExpression;
|
||||
export function parse(arg1: string | IExpression, options: IGlobOptions = {}): any {
|
||||
if (!arg1) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Glob with String
|
||||
if (typeof arg1 === 'string') {
|
||||
const parsedPattern = parsePattern(arg1, options);
|
||||
if (parsedPattern === NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
const resultPattern = function (path: string, basename: string) {
|
||||
return !!parsedPattern(path, basename);
|
||||
};
|
||||
if (parsedPattern.allBasenames) {
|
||||
(<ParsedStringPattern><any>resultPattern).allBasenames = parsedPattern.allBasenames;
|
||||
}
|
||||
if (parsedPattern.allPaths) {
|
||||
(<ParsedStringPattern><any>resultPattern).allPaths = parsedPattern.allPaths;
|
||||
}
|
||||
return resultPattern;
|
||||
}
|
||||
|
||||
// Glob with Expression
|
||||
return parsedExpression(<IExpression>arg1, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `parse`, but the ParsedExpression is guaranteed to return a Promise
|
||||
*/
|
||||
export function parseToAsync(expression: IExpression, options?: IGlobOptions): ParsedExpression {
|
||||
const parsedExpression = parse(expression, options);
|
||||
return (path: string, basename?: string, siblingsFn?: () => TPromise<string[]>): TPromise<string> => {
|
||||
const result = parsedExpression(path, basename, siblingsFn);
|
||||
return result instanceof TPromise ? result : TPromise.as(result);
|
||||
};
|
||||
}
|
||||
|
||||
export function getBasenameTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
|
||||
return (<ParsedStringPattern>patternOrExpression).allBasenames || [];
|
||||
}
|
||||
|
||||
export function getPathTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
|
||||
return (<ParsedStringPattern>patternOrExpression).allPaths || [];
|
||||
}
|
||||
|
||||
function parsedExpression(expression: IExpression, options: IGlobOptions): ParsedExpression {
|
||||
const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression)
|
||||
.map(pattern => parseExpressionPattern(pattern, expression[pattern], options))
|
||||
.filter(pattern => pattern !== NULL));
|
||||
|
||||
const n = parsedPatterns.length;
|
||||
if (!n) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!parsedPatterns.some(parsedPattern => (<ParsedExpressionPattern>parsedPattern).requiresSiblings)) {
|
||||
if (n === 1) {
|
||||
return <ParsedStringPattern>parsedPatterns[0];
|
||||
}
|
||||
|
||||
const resultExpression: ParsedStringPattern = function (path: string, basename: string, siblingsFn?: () => string[]) {
|
||||
for (let i = 0, n = parsedPatterns.length; i < n; i++) {
|
||||
// Pattern matches path
|
||||
const result = (<ParsedStringPattern>parsedPatterns[i])(path, basename);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const withBasenames = arrays.first(parsedPatterns, pattern => !!(<ParsedStringPattern>pattern).allBasenames);
|
||||
if (withBasenames) {
|
||||
resultExpression.allBasenames = (<ParsedStringPattern>withBasenames).allBasenames;
|
||||
}
|
||||
|
||||
const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, <string[]>[]);
|
||||
if (allPaths.length) {
|
||||
resultExpression.allPaths = allPaths;
|
||||
}
|
||||
|
||||
return resultExpression;
|
||||
}
|
||||
|
||||
const resultExpression: ParsedStringPattern = function (path: string, basename: string, siblingsFn?: () => string[] | TPromise<string[]>) {
|
||||
let siblingsPattern: SiblingsPattern | TPromise<SiblingsPattern>;
|
||||
let siblingsResolved = !siblingsFn;
|
||||
|
||||
function siblingsToSiblingsPattern(siblings: string[]) {
|
||||
if (siblings && siblings.length) {
|
||||
if (!basename) {
|
||||
basename = paths.basename(path);
|
||||
}
|
||||
const name = basename.substr(0, basename.length - paths.extname(path).length);
|
||||
return { siblings, name };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function siblingsPatternFn() {
|
||||
// Resolve siblings only once
|
||||
if (!siblingsResolved) {
|
||||
siblingsResolved = true;
|
||||
const siblings = siblingsFn();
|
||||
siblingsPattern = TPromise.is(siblings) ?
|
||||
siblings.then(siblingsToSiblingsPattern) :
|
||||
siblingsToSiblingsPattern(siblings);
|
||||
}
|
||||
|
||||
return siblingsPattern;
|
||||
}
|
||||
|
||||
for (let i = 0, n = parsedPatterns.length; i < n; i++) {
|
||||
// Pattern matches path
|
||||
const result = (<ParsedExpressionPattern>parsedPatterns[i])(path, basename, siblingsPatternFn);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const withBasenames = arrays.first(parsedPatterns, pattern => !!(<ParsedStringPattern>pattern).allBasenames);
|
||||
if (withBasenames) {
|
||||
resultExpression.allBasenames = (<ParsedStringPattern>withBasenames).allBasenames;
|
||||
}
|
||||
|
||||
const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, <string[]>[]);
|
||||
if (allPaths.length) {
|
||||
resultExpression.allPaths = allPaths;
|
||||
}
|
||||
|
||||
return resultExpression;
|
||||
}
|
||||
|
||||
function parseExpressionPattern(pattern: string, value: any, options: IGlobOptions): (ParsedStringPattern | ParsedExpressionPattern) {
|
||||
if (value === false) {
|
||||
return NULL; // pattern is disabled
|
||||
}
|
||||
|
||||
const parsedPattern = parsePattern(pattern, options);
|
||||
if (parsedPattern === NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Expression Pattern is <boolean>
|
||||
if (typeof value === 'boolean') {
|
||||
return parsedPattern;
|
||||
}
|
||||
|
||||
// Expression Pattern is <SiblingClause>
|
||||
if (value) {
|
||||
const when = (<SiblingClause>value).when;
|
||||
if (typeof when === 'string') {
|
||||
const siblingsPatternToMatchingPattern = (siblingsPattern: SiblingsPattern): string => {
|
||||
let clausePattern = when.replace('$(basename)', siblingsPattern.name);
|
||||
if (siblingsPattern.siblings.indexOf(clausePattern) !== -1) {
|
||||
return pattern;
|
||||
} else {
|
||||
return null; // pattern does not match in the end because the when clause is not satisfied
|
||||
}
|
||||
};
|
||||
|
||||
const result: ParsedExpressionPattern = (path: string, basename: string, siblingsPatternFn: () => SiblingsPattern | TPromise<SiblingsPattern>) => {
|
||||
if (!parsedPattern(path, basename)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const siblingsPattern = siblingsPatternFn();
|
||||
if (!siblingsPattern) {
|
||||
return null; // pattern is malformed or we don't have siblings
|
||||
}
|
||||
|
||||
return TPromise.is(siblingsPattern) ?
|
||||
siblingsPattern.then(siblingsPatternToMatchingPattern) :
|
||||
siblingsPatternToMatchingPattern(siblingsPattern);
|
||||
};
|
||||
result.requiresSiblings = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Expression is Anything
|
||||
return parsedPattern;
|
||||
}
|
||||
|
||||
function aggregateBasenameMatches(parsedPatterns: (ParsedStringPattern | ParsedExpressionPattern)[], result?: string): (ParsedStringPattern | ParsedExpressionPattern)[] {
|
||||
const basenamePatterns = parsedPatterns.filter(parsedPattern => !!(<ParsedStringPattern>parsedPattern).basenames);
|
||||
if (basenamePatterns.length < 2) {
|
||||
return parsedPatterns;
|
||||
}
|
||||
|
||||
const basenames = basenamePatterns.reduce<string[]>((all, current) => all.concat((<ParsedStringPattern>current).basenames), []);
|
||||
let patterns: string[];
|
||||
if (result) {
|
||||
patterns = [];
|
||||
for (let i = 0, n = basenames.length; i < n; i++) {
|
||||
patterns.push(result);
|
||||
}
|
||||
} else {
|
||||
patterns = basenamePatterns.reduce((all, current) => all.concat((<ParsedStringPattern>current).patterns), []);
|
||||
}
|
||||
const aggregate: ParsedStringPattern = function (path, basename) {
|
||||
if (!path) {
|
||||
return null;
|
||||
}
|
||||
if (!basename) {
|
||||
let i: number;
|
||||
for (i = path.length; i > 0; i--) {
|
||||
const ch = path.charCodeAt(i - 1);
|
||||
if (ch === CharCode.Slash || ch === CharCode.Backslash) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
basename = path.substr(i);
|
||||
}
|
||||
const index = basenames.indexOf(basename);
|
||||
return index !== -1 ? patterns[index] : null;
|
||||
};
|
||||
aggregate.basenames = basenames;
|
||||
aggregate.patterns = patterns;
|
||||
aggregate.allBasenames = basenames;
|
||||
|
||||
const aggregatedPatterns = parsedPatterns.filter(parsedPattern => !(<ParsedStringPattern>parsedPattern).basenames);
|
||||
aggregatedPatterns.push(aggregate);
|
||||
return aggregatedPatterns;
|
||||
}
|
||||
105
src/vs/base/common/graph.ts
Normal file
105
src/vs/base/common/graph.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { isEmptyObject } from 'vs/base/common/types';
|
||||
import { forEach } from 'vs/base/common/collections';
|
||||
|
||||
export interface Node<T> {
|
||||
data: T;
|
||||
incoming: { [key: string]: Node<T> };
|
||||
outgoing: { [key: string]: Node<T> };
|
||||
}
|
||||
|
||||
function newNode<T>(data: T): Node<T> {
|
||||
return {
|
||||
data: data,
|
||||
incoming: Object.create(null),
|
||||
outgoing: Object.create(null)
|
||||
};
|
||||
}
|
||||
|
||||
export class Graph<T> {
|
||||
|
||||
private _nodes: { [key: string]: Node<T> } = Object.create(null);
|
||||
|
||||
constructor(private _hashFn: (element: T) => string) {
|
||||
// empty
|
||||
}
|
||||
|
||||
roots(): Node<T>[] {
|
||||
var ret: Node<T>[] = [];
|
||||
forEach(this._nodes, entry => {
|
||||
if (isEmptyObject(entry.value.outgoing)) {
|
||||
ret.push(entry.value);
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
traverse(start: T, inwards: boolean, callback: (data: T) => void): void {
|
||||
var startNode = this.lookup(start);
|
||||
if (!startNode) {
|
||||
return;
|
||||
}
|
||||
this._traverse(startNode, inwards, Object.create(null), callback);
|
||||
}
|
||||
|
||||
private _traverse(node: Node<T>, inwards: boolean, seen: { [key: string]: boolean }, callback: (data: T) => void): void {
|
||||
var key = this._hashFn(node.data);
|
||||
if (seen[key]) {
|
||||
return;
|
||||
}
|
||||
seen[key] = true;
|
||||
callback(node.data);
|
||||
var nodes = inwards ? node.outgoing : node.incoming;
|
||||
forEach(nodes, (entry) => this._traverse(entry.value, inwards, seen, callback));
|
||||
}
|
||||
|
||||
insertEdge(from: T, to: T): void {
|
||||
var fromNode = this.lookupOrInsertNode(from),
|
||||
toNode = this.lookupOrInsertNode(to);
|
||||
|
||||
fromNode.outgoing[this._hashFn(to)] = toNode;
|
||||
toNode.incoming[this._hashFn(from)] = fromNode;
|
||||
}
|
||||
|
||||
removeNode(data: T): void {
|
||||
var key = this._hashFn(data);
|
||||
delete this._nodes[key];
|
||||
forEach(this._nodes, (entry) => {
|
||||
delete entry.value.outgoing[key];
|
||||
delete entry.value.incoming[key];
|
||||
});
|
||||
}
|
||||
|
||||
lookupOrInsertNode(data: T): Node<T> {
|
||||
const key = this._hashFn(data);
|
||||
let node = this._nodes[key];
|
||||
|
||||
if (!node) {
|
||||
node = newNode(data);
|
||||
this._nodes[key] = node;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
lookup(data: T): Node<T> {
|
||||
return this._nodes[this._hashFn(data)];
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
return Object.keys(this._nodes).length;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let data: string[] = [];
|
||||
forEach(this._nodes, entry => {
|
||||
data.push(`${entry.key}, (incoming)[${Object.keys(entry.value.incoming).join(', ')}], (outgoing)[${Object.keys(entry.value.outgoing).join(',')}]`);
|
||||
});
|
||||
return data.join('\n');
|
||||
}
|
||||
}
|
||||
59
src/vs/base/common/hash.ts
Normal file
59
src/vs/base/common/hash.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Return a hash value for an object.
|
||||
*/
|
||||
export function hash(obj: any, hashVal = 0): number {
|
||||
switch (typeof obj) {
|
||||
case 'object':
|
||||
if (obj === null) {
|
||||
return numberHash(349, hashVal);
|
||||
} else if (Array.isArray(obj)) {
|
||||
return arrayHash(obj, hashVal);
|
||||
}
|
||||
return objectHash(obj, hashVal);
|
||||
case 'string':
|
||||
return stringHash(obj, hashVal);
|
||||
case 'boolean':
|
||||
return booleanHash(obj, hashVal);
|
||||
case 'number':
|
||||
return numberHash(obj, hashVal);
|
||||
case 'undefined':
|
||||
return numberHash(obj, 937);
|
||||
default:
|
||||
return numberHash(obj, 617);
|
||||
}
|
||||
}
|
||||
|
||||
function numberHash(val: number, initialHashVal: number): number {
|
||||
return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32
|
||||
}
|
||||
|
||||
function booleanHash(b: boolean, initialHashVal: number): number {
|
||||
return numberHash(b ? 433 : 863, initialHashVal);
|
||||
}
|
||||
|
||||
function stringHash(s: string, hashVal: number) {
|
||||
hashVal = numberHash(149417, hashVal);
|
||||
for (let i = 0, length = s.length; i < length; i++) {
|
||||
hashVal = numberHash(s.charCodeAt(i), hashVal);
|
||||
}
|
||||
return hashVal;
|
||||
}
|
||||
|
||||
function arrayHash(arr: any[], initialHashVal: number): number {
|
||||
initialHashVal = numberHash(104579, initialHashVal);
|
||||
return arr.reduce((hashVal, item) => hash(item, hashVal), initialHashVal);
|
||||
}
|
||||
|
||||
function objectHash(obj: any, initialHashVal: number): number {
|
||||
initialHashVal = numberHash(181387, initialHashVal);
|
||||
return Object.keys(obj).sort().reduce((hashVal, key) => {
|
||||
hashVal = stringHash(key, hashVal);
|
||||
return hash(obj[key], hashVal);
|
||||
}, initialHashVal);
|
||||
}
|
||||
93
src/vs/base/common/history.ts
Normal file
93
src/vs/base/common/history.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { INavigator, ArrayNavigator } from 'vs/base/common/iterator';
|
||||
|
||||
export class HistoryNavigator<T> implements INavigator<T> {
|
||||
|
||||
private _history: Set<T>;
|
||||
private _limit: number;
|
||||
private _navigator: ArrayNavigator<T>;
|
||||
|
||||
constructor(history: T[] = [], limit: number = 10) {
|
||||
this._initialize(history);
|
||||
this._limit = limit;
|
||||
this._onChange();
|
||||
}
|
||||
|
||||
public getHistory(): T[] {
|
||||
return this._elements;
|
||||
}
|
||||
|
||||
public add(t: T) {
|
||||
this._history.delete(t);
|
||||
this._history.add(t);
|
||||
this._onChange();
|
||||
}
|
||||
|
||||
public addIfNotPresent(t: T) {
|
||||
if (!this._history.has(t)) {
|
||||
this.add(t);
|
||||
}
|
||||
}
|
||||
|
||||
public next(): T {
|
||||
if (this._navigator.next()) {
|
||||
return this._navigator.current();
|
||||
}
|
||||
this.last();
|
||||
return null;
|
||||
}
|
||||
|
||||
public previous(): T {
|
||||
if (this._navigator.previous()) {
|
||||
return this._navigator.current();
|
||||
}
|
||||
this.first();
|
||||
return null;
|
||||
}
|
||||
|
||||
public current(): T {
|
||||
return this._navigator.current();
|
||||
}
|
||||
|
||||
public parent(): T {
|
||||
return null;
|
||||
}
|
||||
|
||||
public first(): T {
|
||||
return this._navigator.first();
|
||||
}
|
||||
|
||||
public last(): T {
|
||||
return this._navigator.last();
|
||||
}
|
||||
|
||||
private _onChange() {
|
||||
this._reduceToLimit();
|
||||
this._navigator = new ArrayNavigator(this._elements);
|
||||
this._navigator.last();
|
||||
}
|
||||
|
||||
private _reduceToLimit() {
|
||||
let data = this._elements;
|
||||
if (data.length > this._limit) {
|
||||
this._initialize(data.slice(data.length - this._limit));
|
||||
}
|
||||
}
|
||||
|
||||
private _initialize(history: T[]): void {
|
||||
this._history = new Set();
|
||||
for (const entry of history) {
|
||||
this._history.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private get _elements(): T[] {
|
||||
const elements: T[] = [];
|
||||
this._history.forEach(e => elements.push(e));
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
108
src/vs/base/common/htmlContent.ts
Normal file
108
src/vs/base/common/htmlContent.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { marked } from 'vs/base/common/marked/marked';
|
||||
|
||||
export interface IMarkdownString {
|
||||
value: string;
|
||||
isTrusted?: boolean;
|
||||
}
|
||||
|
||||
export class MarkdownString implements IMarkdownString {
|
||||
|
||||
value: string;
|
||||
isTrusted?: boolean;
|
||||
|
||||
constructor(value: string = '') {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
appendText(value: string): MarkdownString {
|
||||
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
|
||||
this.value += value.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&');
|
||||
return this;
|
||||
}
|
||||
|
||||
appendMarkdown(value: string): MarkdownString {
|
||||
this.value += value;
|
||||
return this;
|
||||
}
|
||||
|
||||
appendCodeblock(langId: string, code: string): MarkdownString {
|
||||
this.value += '\n```';
|
||||
this.value += langId;
|
||||
this.value += '\n';
|
||||
this.value += code;
|
||||
this.value += '\n```\n';
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export function isEmptyMarkdownString(oneOrMany: IMarkdownString | IMarkdownString[]): boolean {
|
||||
if (isMarkdownString(oneOrMany)) {
|
||||
return !oneOrMany.value;
|
||||
} else if (Array.isArray(oneOrMany)) {
|
||||
return oneOrMany.every(isEmptyMarkdownString);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function isMarkdownString(thing: any): thing is IMarkdownString {
|
||||
if (thing instanceof MarkdownString) {
|
||||
return true;
|
||||
} else if (typeof thing === 'object') {
|
||||
return typeof (<IMarkdownString>thing).value === 'string'
|
||||
&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === void 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function markedStringsEquals(a: IMarkdownString | IMarkdownString[], b: IMarkdownString | IMarkdownString[]): boolean {
|
||||
if (!a && !b) {
|
||||
return true;
|
||||
} else if (!a || !b) {
|
||||
return false;
|
||||
} else if (Array.isArray(a) && Array.isArray(b)) {
|
||||
return equals(a, b, markdownStringEqual);
|
||||
} else if (isMarkdownString(a) && isMarkdownString(b)) {
|
||||
return markdownStringEqual(a, b);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function markdownStringEqual(a: IMarkdownString, b: IMarkdownString): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
} else if (!a || !b) {
|
||||
return false;
|
||||
} else {
|
||||
return a.value === b.value && a.isTrusted === b.isTrusted;
|
||||
}
|
||||
}
|
||||
|
||||
export function removeMarkdownEscapes(text: string): string {
|
||||
if (!text) {
|
||||
return text;
|
||||
}
|
||||
return text.replace(/\\([\\`*_{}[\]()#+\-.!])/g, '$1');
|
||||
}
|
||||
|
||||
export function containsCommandLink(value: string): boolean {
|
||||
let uses = false;
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.link = (href, title, text): string => {
|
||||
if (href.match(/^command:/i)) {
|
||||
uses = true;
|
||||
}
|
||||
return 'link';
|
||||
};
|
||||
marked(value, { renderer });
|
||||
return uses;
|
||||
}
|
||||
22
src/vs/base/common/idGenerator.ts
Normal file
22
src/vs/base/common/idGenerator.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export class IdGenerator {
|
||||
|
||||
private _prefix: string;
|
||||
private _lastId: number;
|
||||
|
||||
constructor(prefix: string) {
|
||||
this._prefix = prefix;
|
||||
this._lastId = 0;
|
||||
}
|
||||
|
||||
public nextId(): string {
|
||||
return this._prefix + (++this._lastId);
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultGenerator = new IdGenerator('id#');
|
||||
106
src/vs/base/common/iterator.ts
Normal file
106
src/vs/base/common/iterator.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export interface IIterator<T> {
|
||||
next(): T;
|
||||
}
|
||||
|
||||
export class ArrayIterator<T> implements IIterator<T> {
|
||||
|
||||
private items: T[];
|
||||
protected start: number;
|
||||
protected end: number;
|
||||
protected index: number;
|
||||
|
||||
constructor(items: T[], start: number = 0, end: number = items.length) {
|
||||
this.items = items;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.index = start - 1;
|
||||
}
|
||||
|
||||
public first(): T {
|
||||
this.index = this.start;
|
||||
return this.current();
|
||||
}
|
||||
|
||||
public next(): T {
|
||||
this.index = Math.min(this.index + 1, this.end);
|
||||
return this.current();
|
||||
}
|
||||
|
||||
protected current(): T {
|
||||
if (this.index === this.start - 1 || this.index === this.end) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.items[this.index];
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrayNavigator<T> extends ArrayIterator<T> implements INavigator<T> {
|
||||
|
||||
constructor(items: T[], start: number = 0, end: number = items.length) {
|
||||
super(items, start, end);
|
||||
}
|
||||
|
||||
public current(): T {
|
||||
return super.current();
|
||||
}
|
||||
|
||||
public previous(): T {
|
||||
this.index = Math.max(this.index - 1, this.start - 1);
|
||||
return this.current();
|
||||
}
|
||||
|
||||
public first(): T {
|
||||
this.index = this.start;
|
||||
return this.current();
|
||||
}
|
||||
|
||||
public last(): T {
|
||||
this.index = this.end - 1;
|
||||
return this.current();
|
||||
}
|
||||
|
||||
public parent(): T {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class MappedIterator<T, R> implements IIterator<R> {
|
||||
|
||||
constructor(protected iterator: IIterator<T>, protected fn: (item: T) => R) {
|
||||
// noop
|
||||
}
|
||||
|
||||
next() { return this.fn(this.iterator.next()); }
|
||||
}
|
||||
|
||||
export interface INavigator<T> extends IIterator<T> {
|
||||
current(): T;
|
||||
previous(): T;
|
||||
parent(): T;
|
||||
first(): T;
|
||||
last(): T;
|
||||
next(): T;
|
||||
}
|
||||
|
||||
export class MappedNavigator<T, R> extends MappedIterator<T, R> implements INavigator<R> {
|
||||
|
||||
constructor(protected navigator: INavigator<T>, fn: (item: T) => R) {
|
||||
super(navigator, fn);
|
||||
}
|
||||
|
||||
current() { return this.fn(this.navigator.current()); }
|
||||
previous() { return this.fn(this.navigator.previous()); }
|
||||
parent() { return this.fn(this.navigator.parent()); }
|
||||
first() { return this.fn(this.navigator.first()); }
|
||||
last() { return this.fn(this.navigator.last()); }
|
||||
next() { return this.fn(this.navigator.next()); }
|
||||
}
|
||||
1222
src/vs/base/common/json.ts
Normal file
1222
src/vs/base/common/json.ts
Normal file
File diff suppressed because it is too large
Load Diff
142
src/vs/base/common/jsonEdit.ts
Normal file
142
src/vs/base/common/jsonEdit.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ParseError, Node, parseTree, findNodeAtLocation, JSONPath, Segment } from 'vs/base/common/json';
|
||||
import { Edit, FormattingOptions, format, applyEdit } from 'vs/base/common/jsonFormatter';
|
||||
|
||||
export function removeProperty(text: string, path: JSONPath, formattingOptions: FormattingOptions): Edit[] {
|
||||
return setProperty(text, path, void 0, formattingOptions);
|
||||
}
|
||||
|
||||
export function setProperty(text: string, path: JSONPath, value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number): Edit[] {
|
||||
let errors: ParseError[] = [];
|
||||
let root = parseTree(text, errors);
|
||||
let parent: Node = void 0;
|
||||
|
||||
let lastSegment: Segment = void 0;
|
||||
while (path.length > 0) {
|
||||
lastSegment = path.pop();
|
||||
parent = findNodeAtLocation(root, path);
|
||||
if (parent === void 0 && value !== void 0) {
|
||||
if (typeof lastSegment === 'string') {
|
||||
value = { [lastSegment]: value };
|
||||
} else {
|
||||
value = [value];
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parent) {
|
||||
// empty document
|
||||
if (value === void 0) { // delete
|
||||
throw new Error('Can not delete in empty document');
|
||||
}
|
||||
return withFormatting(text, { offset: root ? root.offset : 0, length: root ? root.length : 0, content: JSON.stringify(value) }, formattingOptions);
|
||||
} else if (parent.type === 'object' && typeof lastSegment === 'string') {
|
||||
let existing = findNodeAtLocation(parent, [lastSegment]);
|
||||
if (existing !== void 0) {
|
||||
if (value === void 0) { // delete
|
||||
let propertyIndex = parent.children.indexOf(existing.parent);
|
||||
let removeBegin: number;
|
||||
let removeEnd = existing.parent.offset + existing.parent.length;
|
||||
if (propertyIndex > 0) {
|
||||
// remove the comma of the previous node
|
||||
let previous = parent.children[propertyIndex - 1];
|
||||
removeBegin = previous.offset + previous.length;
|
||||
} else {
|
||||
removeBegin = parent.offset + 1;
|
||||
if (parent.children.length > 1) {
|
||||
// remove the comma of the next node
|
||||
let next = parent.children[1];
|
||||
removeEnd = next.offset;
|
||||
}
|
||||
}
|
||||
return withFormatting(text, { offset: removeBegin, length: removeEnd - removeBegin, content: '' }, formattingOptions);
|
||||
} else {
|
||||
// set value of existing property
|
||||
return withFormatting(text, { offset: existing.offset, length: existing.length, content: JSON.stringify(value) }, formattingOptions);
|
||||
}
|
||||
} else {
|
||||
if (value === void 0) { // delete
|
||||
return []; // property does not exist, nothing to do
|
||||
}
|
||||
let newProperty = `${JSON.stringify(lastSegment)}: ${JSON.stringify(value)}`;
|
||||
let index = getInsertionIndex ? getInsertionIndex(parent.children.map(p => p.children[0].value)) : parent.children.length;
|
||||
let edit: Edit;
|
||||
if (index > 0) {
|
||||
let previous = parent.children[index - 1];
|
||||
edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
|
||||
} else if (parent.children.length === 0) {
|
||||
edit = { offset: parent.offset + 1, length: 0, content: newProperty };
|
||||
} else {
|
||||
edit = { offset: parent.offset + 1, length: 0, content: newProperty + ',' };
|
||||
}
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
}
|
||||
} else if (parent.type === 'array' && typeof lastSegment === 'number') {
|
||||
let insertIndex = lastSegment;
|
||||
if (insertIndex === -1) {
|
||||
// Insert
|
||||
let newProperty = `${JSON.stringify(value)}`;
|
||||
let edit: Edit;
|
||||
if (parent.children.length === 0) {
|
||||
edit = { offset: parent.offset + 1, length: 0, content: newProperty };
|
||||
} else {
|
||||
let previous = parent.children[parent.children.length - 1];
|
||||
edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
|
||||
}
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
} else {
|
||||
if (value === void 0 && parent.children.length >= 0) {
|
||||
//Removal
|
||||
let removalIndex = lastSegment;
|
||||
let toRemove = parent.children[removalIndex];
|
||||
let edit: Edit;
|
||||
if (parent.children.length === 1) {
|
||||
// only item
|
||||
edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
|
||||
} else if (parent.children.length - 1 === removalIndex) {
|
||||
// last item
|
||||
let previous = parent.children[removalIndex - 1];
|
||||
let offset = previous.offset + previous.length;
|
||||
let parentEndOffset = parent.offset + parent.length;
|
||||
edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
|
||||
} else {
|
||||
edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
|
||||
}
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
} else {
|
||||
throw new Error('Array modification not supported yet');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Can not add ${typeof lastSegment !== 'number' ? 'index' : 'property'} to parent of type ${parent.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function withFormatting(text: string, edit: Edit, formattingOptions: FormattingOptions): Edit[] {
|
||||
// apply the edit
|
||||
let newText = applyEdit(text, edit);
|
||||
|
||||
// format the new text
|
||||
let begin = edit.offset;
|
||||
let end = edit.offset + edit.content.length;
|
||||
let edits = format(newText, { offset: begin, length: end - begin }, formattingOptions);
|
||||
|
||||
// apply the formatting edits and track the begin and end offsets of the changes
|
||||
for (let i = edits.length - 1; i >= 0; i--) {
|
||||
let edit = edits[i];
|
||||
newText = applyEdit(newText, edit);
|
||||
begin = Math.min(begin, edit.offset);
|
||||
end = Math.max(end, edit.offset + edit.length);
|
||||
end += edit.content.length - edit.length;
|
||||
}
|
||||
// create a single edit with all changes
|
||||
let editLength = text.length - (newText.length - end) - begin;
|
||||
return [{ offset: begin, length: editLength, content: newText.substring(begin, end) }];
|
||||
}
|
||||
27
src/vs/base/common/jsonErrorMessages.ts
Normal file
27
src/vs/base/common/jsonErrorMessages.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Extracted from json.ts to keep json nls free.
|
||||
*/
|
||||
import { localize } from 'vs/nls';
|
||||
import { ParseErrorCode } from './json';
|
||||
|
||||
export function getParseErrorMessage(errorCode: ParseErrorCode): string {
|
||||
switch (errorCode) {
|
||||
case ParseErrorCode.InvalidSymbol: return localize('error.invalidSymbol', 'Invalid symbol');
|
||||
case ParseErrorCode.InvalidNumberFormat: return localize('error.invalidNumberFormat', 'Invalid number format');
|
||||
case ParseErrorCode.PropertyNameExpected: return localize('error.propertyNameExpected', 'Property name expected');
|
||||
case ParseErrorCode.ValueExpected: return localize('error.valueExpected', 'Value expected');
|
||||
case ParseErrorCode.ColonExpected: return localize('error.colonExpected', 'Colon expected');
|
||||
case ParseErrorCode.CommaExpected: return localize('error.commaExpected', 'Comma expected');
|
||||
case ParseErrorCode.CloseBraceExpected: return localize('error.closeBraceExpected', 'Closing brace expected');
|
||||
case ParseErrorCode.CloseBracketExpected: return localize('error.closeBracketExpected', 'Closing bracket expected');
|
||||
case ParseErrorCode.EndOfFileExpected: return localize('error.endOfFileExpected', 'End of file expected');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
214
src/vs/base/common/jsonFormatter.ts
Normal file
214
src/vs/base/common/jsonFormatter.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Json = require('./json');
|
||||
|
||||
export interface FormattingOptions {
|
||||
/**
|
||||
* If indentation is based on spaces (`insertSpaces` = true), then what is the number of spaces that make an indent?
|
||||
*/
|
||||
tabSize: number;
|
||||
/**
|
||||
* Is indentation based on spaces?
|
||||
*/
|
||||
insertSpaces: boolean;
|
||||
/**
|
||||
* The default end of line line character
|
||||
*/
|
||||
eol: string;
|
||||
}
|
||||
|
||||
export interface Edit {
|
||||
offset: number;
|
||||
length: number;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export function applyEdit(text: string, edit: Edit): string {
|
||||
return text.substring(0, edit.offset) + edit.content + text.substring(edit.offset + edit.length);
|
||||
}
|
||||
|
||||
export function applyEdits(text: string, edits: Edit[]): string {
|
||||
for (let i = edits.length - 1; i >= 0; i--) {
|
||||
text = applyEdit(text, edits[i]);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export function format(documentText: string, range: { offset: number, length: number }, options: FormattingOptions): Edit[] {
|
||||
let initialIndentLevel: number;
|
||||
let value: string;
|
||||
let rangeStart: number;
|
||||
let rangeEnd: number;
|
||||
if (range) {
|
||||
rangeStart = range.offset;
|
||||
rangeEnd = rangeStart + range.length;
|
||||
while (rangeStart > 0 && !isEOL(documentText, rangeStart - 1)) {
|
||||
rangeStart--;
|
||||
}
|
||||
let scanner = Json.createScanner(documentText, true);
|
||||
scanner.setPosition(rangeEnd);
|
||||
scanner.scan();
|
||||
rangeEnd = scanner.getPosition();
|
||||
|
||||
value = documentText.substring(rangeStart, rangeEnd);
|
||||
initialIndentLevel = computeIndentLevel(value, 0, options);
|
||||
} else {
|
||||
value = documentText;
|
||||
rangeStart = 0;
|
||||
rangeEnd = documentText.length;
|
||||
initialIndentLevel = 0;
|
||||
}
|
||||
let eol = getEOL(options, documentText);
|
||||
|
||||
let lineBreak = false;
|
||||
let indentLevel = 0;
|
||||
let indentValue: string;
|
||||
if (options.insertSpaces) {
|
||||
indentValue = repeat(' ', options.tabSize);
|
||||
} else {
|
||||
indentValue = '\t';
|
||||
}
|
||||
|
||||
let scanner = Json.createScanner(value, false);
|
||||
|
||||
function newLineAndIndent(): string {
|
||||
return eol + repeat(indentValue, initialIndentLevel + indentLevel);
|
||||
}
|
||||
function scanNext(): Json.SyntaxKind {
|
||||
let token = scanner.scan();
|
||||
lineBreak = false;
|
||||
while (token === Json.SyntaxKind.Trivia || token === Json.SyntaxKind.LineBreakTrivia) {
|
||||
lineBreak = lineBreak || (token === Json.SyntaxKind.LineBreakTrivia);
|
||||
token = scanner.scan();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
let editOperations: Edit[] = [];
|
||||
function addEdit(text: string, startOffset: number, endOffset: number) {
|
||||
if (documentText.substring(startOffset, endOffset) !== text) {
|
||||
editOperations.push({ offset: startOffset, length: endOffset - startOffset, content: text });
|
||||
}
|
||||
}
|
||||
|
||||
let firstToken = scanNext();
|
||||
if (firstToken !== Json.SyntaxKind.EOF) {
|
||||
let firstTokenStart = scanner.getTokenOffset() + rangeStart;
|
||||
let initialIndent = repeat(indentValue, initialIndentLevel);
|
||||
addEdit(initialIndent, rangeStart, firstTokenStart);
|
||||
}
|
||||
|
||||
while (firstToken !== Json.SyntaxKind.EOF) {
|
||||
let firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + rangeStart;
|
||||
let secondToken = scanNext();
|
||||
|
||||
let replaceContent = '';
|
||||
while (!lineBreak && (secondToken === Json.SyntaxKind.LineCommentTrivia || secondToken === Json.SyntaxKind.BlockCommentTrivia)) {
|
||||
// comments on the same line: keep them on the same line, but ignore them otherwise
|
||||
let commentTokenStart = scanner.getTokenOffset() + rangeStart;
|
||||
addEdit(' ', firstTokenEnd, commentTokenStart);
|
||||
firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + rangeStart;
|
||||
replaceContent = secondToken === Json.SyntaxKind.LineCommentTrivia ? newLineAndIndent() : '';
|
||||
secondToken = scanNext();
|
||||
}
|
||||
|
||||
if (secondToken === Json.SyntaxKind.CloseBraceToken) {
|
||||
if (firstToken !== Json.SyntaxKind.OpenBraceToken) {
|
||||
indentLevel--;
|
||||
replaceContent = newLineAndIndent();
|
||||
}
|
||||
} else if (secondToken === Json.SyntaxKind.CloseBracketToken) {
|
||||
if (firstToken !== Json.SyntaxKind.OpenBracketToken) {
|
||||
indentLevel--;
|
||||
replaceContent = newLineAndIndent();
|
||||
}
|
||||
} else if (secondToken !== Json.SyntaxKind.EOF) {
|
||||
switch (firstToken) {
|
||||
case Json.SyntaxKind.OpenBracketToken:
|
||||
case Json.SyntaxKind.OpenBraceToken:
|
||||
indentLevel++;
|
||||
replaceContent = newLineAndIndent();
|
||||
break;
|
||||
case Json.SyntaxKind.CommaToken:
|
||||
case Json.SyntaxKind.LineCommentTrivia:
|
||||
replaceContent = newLineAndIndent();
|
||||
break;
|
||||
case Json.SyntaxKind.BlockCommentTrivia:
|
||||
if (lineBreak) {
|
||||
replaceContent = newLineAndIndent();
|
||||
} else {
|
||||
// symbol following comment on the same line: keep on same line, separate with ' '
|
||||
replaceContent = ' ';
|
||||
}
|
||||
break;
|
||||
case Json.SyntaxKind.ColonToken:
|
||||
replaceContent = ' ';
|
||||
break;
|
||||
case Json.SyntaxKind.NullKeyword:
|
||||
case Json.SyntaxKind.TrueKeyword:
|
||||
case Json.SyntaxKind.FalseKeyword:
|
||||
case Json.SyntaxKind.NumericLiteral:
|
||||
if (secondToken === Json.SyntaxKind.NullKeyword || secondToken === Json.SyntaxKind.FalseKeyword || secondToken === Json.SyntaxKind.NumericLiteral) {
|
||||
replaceContent = ' ';
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (lineBreak && (secondToken === Json.SyntaxKind.LineCommentTrivia || secondToken === Json.SyntaxKind.BlockCommentTrivia)) {
|
||||
replaceContent = newLineAndIndent();
|
||||
}
|
||||
|
||||
}
|
||||
let secondTokenStart = scanner.getTokenOffset() + rangeStart;
|
||||
addEdit(replaceContent, firstTokenEnd, secondTokenStart);
|
||||
firstToken = secondToken;
|
||||
}
|
||||
return editOperations;
|
||||
}
|
||||
|
||||
function repeat(s: string, count: number): string {
|
||||
let result = '';
|
||||
for (let i = 0; i < count; i++) {
|
||||
result += s;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function computeIndentLevel(content: string, offset: number, options: FormattingOptions): number {
|
||||
let i = 0;
|
||||
let nChars = 0;
|
||||
let tabSize = options.tabSize || 4;
|
||||
while (i < content.length) {
|
||||
let ch = content.charAt(i);
|
||||
if (ch === ' ') {
|
||||
nChars++;
|
||||
} else if (ch === '\t') {
|
||||
nChars += tabSize;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return Math.floor(nChars / tabSize);
|
||||
}
|
||||
|
||||
function getEOL(options: FormattingOptions, text: string): string {
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
let ch = text.charAt(i);
|
||||
if (ch === '\r') {
|
||||
if (i + 1 < text.length && text.charAt(i + 1) === '\n') {
|
||||
return '\r\n';
|
||||
}
|
||||
return '\r';
|
||||
} else if (ch === '\n') {
|
||||
return '\n';
|
||||
}
|
||||
}
|
||||
return (options && options.eol) || '\n';
|
||||
}
|
||||
|
||||
function isEOL(text: string, offset: number) {
|
||||
return '\r\n'.indexOf(text.charAt(offset)) !== -1;
|
||||
}
|
||||
59
src/vs/base/common/jsonSchema.ts
Normal file
59
src/vs/base/common/jsonSchema.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export interface IJSONSchema {
|
||||
id?: string;
|
||||
$schema?: string;
|
||||
type?: string | string[];
|
||||
title?: string;
|
||||
default?: any;
|
||||
definitions?: IJSONSchemaMap;
|
||||
description?: string;
|
||||
properties?: IJSONSchemaMap;
|
||||
patternProperties?: IJSONSchemaMap;
|
||||
additionalProperties?: boolean | IJSONSchema;
|
||||
minProperties?: number;
|
||||
maxProperties?: number;
|
||||
dependencies?: IJSONSchemaMap | string[];
|
||||
items?: IJSONSchema | IJSONSchema[];
|
||||
minItems?: number;
|
||||
maxItems?: number;
|
||||
uniqueItems?: boolean;
|
||||
additionalItems?: boolean;
|
||||
pattern?: string;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
exclusiveMinimum?: boolean;
|
||||
exclusiveMaximum?: boolean;
|
||||
multipleOf?: number;
|
||||
required?: string[];
|
||||
$ref?: string;
|
||||
anyOf?: IJSONSchema[];
|
||||
allOf?: IJSONSchema[];
|
||||
oneOf?: IJSONSchema[];
|
||||
not?: IJSONSchema;
|
||||
enum?: any[];
|
||||
format?: string;
|
||||
|
||||
defaultSnippets?: IJSONSchemaSnippet[]; // VSCode extension
|
||||
errorMessage?: string; // VSCode extension
|
||||
patternErrorMessage?: string; // VSCode extension
|
||||
deprecationMessage?: string; // VSCode extension
|
||||
enumDescriptions?: string[]; // VSCode extension
|
||||
}
|
||||
|
||||
export interface IJSONSchemaMap {
|
||||
[name: string]: IJSONSchema;
|
||||
}
|
||||
|
||||
export interface IJSONSchemaSnippet {
|
||||
label?: string;
|
||||
description?: string;
|
||||
body?: any; // a object that will be JSON stringified
|
||||
bodyText?: string; // an already stringified JSON object that can contain new lines (\n) and tabs (\t)
|
||||
}
|
||||
575
src/vs/base/common/keyCodes.ts
Normal file
575
src/vs/base/common/keyCodes.ts
Normal file
@@ -0,0 +1,575 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
|
||||
/**
|
||||
* Virtual Key Codes, the value does not hold any inherent meaning.
|
||||
* Inspired somewhat from https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||
* But these are "more general", as they should work across browsers & OS`s.
|
||||
*/
|
||||
export const enum KeyCode {
|
||||
/**
|
||||
* Placed first to cover the 0 value of the enum.
|
||||
*/
|
||||
Unknown = 0,
|
||||
|
||||
Backspace = 1,
|
||||
Tab = 2,
|
||||
Enter = 3,
|
||||
Shift = 4,
|
||||
Ctrl = 5,
|
||||
Alt = 6,
|
||||
PauseBreak = 7,
|
||||
CapsLock = 8,
|
||||
Escape = 9,
|
||||
Space = 10,
|
||||
PageUp = 11,
|
||||
PageDown = 12,
|
||||
End = 13,
|
||||
Home = 14,
|
||||
LeftArrow = 15,
|
||||
UpArrow = 16,
|
||||
RightArrow = 17,
|
||||
DownArrow = 18,
|
||||
Insert = 19,
|
||||
Delete = 20,
|
||||
|
||||
KEY_0 = 21,
|
||||
KEY_1 = 22,
|
||||
KEY_2 = 23,
|
||||
KEY_3 = 24,
|
||||
KEY_4 = 25,
|
||||
KEY_5 = 26,
|
||||
KEY_6 = 27,
|
||||
KEY_7 = 28,
|
||||
KEY_8 = 29,
|
||||
KEY_9 = 30,
|
||||
|
||||
KEY_A = 31,
|
||||
KEY_B = 32,
|
||||
KEY_C = 33,
|
||||
KEY_D = 34,
|
||||
KEY_E = 35,
|
||||
KEY_F = 36,
|
||||
KEY_G = 37,
|
||||
KEY_H = 38,
|
||||
KEY_I = 39,
|
||||
KEY_J = 40,
|
||||
KEY_K = 41,
|
||||
KEY_L = 42,
|
||||
KEY_M = 43,
|
||||
KEY_N = 44,
|
||||
KEY_O = 45,
|
||||
KEY_P = 46,
|
||||
KEY_Q = 47,
|
||||
KEY_R = 48,
|
||||
KEY_S = 49,
|
||||
KEY_T = 50,
|
||||
KEY_U = 51,
|
||||
KEY_V = 52,
|
||||
KEY_W = 53,
|
||||
KEY_X = 54,
|
||||
KEY_Y = 55,
|
||||
KEY_Z = 56,
|
||||
|
||||
Meta = 57,
|
||||
ContextMenu = 58,
|
||||
|
||||
F1 = 59,
|
||||
F2 = 60,
|
||||
F3 = 61,
|
||||
F4 = 62,
|
||||
F5 = 63,
|
||||
F6 = 64,
|
||||
F7 = 65,
|
||||
F8 = 66,
|
||||
F9 = 67,
|
||||
F10 = 68,
|
||||
F11 = 69,
|
||||
F12 = 70,
|
||||
F13 = 71,
|
||||
F14 = 72,
|
||||
F15 = 73,
|
||||
F16 = 74,
|
||||
F17 = 75,
|
||||
F18 = 76,
|
||||
F19 = 77,
|
||||
|
||||
NumLock = 78,
|
||||
ScrollLock = 79,
|
||||
|
||||
/**
|
||||
* Used for miscellaneous characters; it can vary by keyboard.
|
||||
* For the US standard keyboard, the ';:' key
|
||||
*/
|
||||
US_SEMICOLON = 80,
|
||||
/**
|
||||
* For any country/region, the '+' key
|
||||
* For the US standard keyboard, the '=+' key
|
||||
*/
|
||||
US_EQUAL = 81,
|
||||
/**
|
||||
* For any country/region, the ',' key
|
||||
* For the US standard keyboard, the ',<' key
|
||||
*/
|
||||
US_COMMA = 82,
|
||||
/**
|
||||
* For any country/region, the '-' key
|
||||
* For the US standard keyboard, the '-_' key
|
||||
*/
|
||||
US_MINUS = 83,
|
||||
/**
|
||||
* For any country/region, the '.' key
|
||||
* For the US standard keyboard, the '.>' key
|
||||
*/
|
||||
US_DOT = 84,
|
||||
/**
|
||||
* Used for miscellaneous characters; it can vary by keyboard.
|
||||
* For the US standard keyboard, the '/?' key
|
||||
*/
|
||||
US_SLASH = 85,
|
||||
/**
|
||||
* Used for miscellaneous characters; it can vary by keyboard.
|
||||
* For the US standard keyboard, the '`~' key
|
||||
*/
|
||||
US_BACKTICK = 86,
|
||||
/**
|
||||
* Used for miscellaneous characters; it can vary by keyboard.
|
||||
* For the US standard keyboard, the '[{' key
|
||||
*/
|
||||
US_OPEN_SQUARE_BRACKET = 87,
|
||||
/**
|
||||
* Used for miscellaneous characters; it can vary by keyboard.
|
||||
* For the US standard keyboard, the '\|' key
|
||||
*/
|
||||
US_BACKSLASH = 88,
|
||||
/**
|
||||
* Used for miscellaneous characters; it can vary by keyboard.
|
||||
* For the US standard keyboard, the ']}' key
|
||||
*/
|
||||
US_CLOSE_SQUARE_BRACKET = 89,
|
||||
/**
|
||||
* Used for miscellaneous characters; it can vary by keyboard.
|
||||
* For the US standard keyboard, the ''"' key
|
||||
*/
|
||||
US_QUOTE = 90,
|
||||
/**
|
||||
* Used for miscellaneous characters; it can vary by keyboard.
|
||||
*/
|
||||
OEM_8 = 91,
|
||||
/**
|
||||
* Either the angle bracket key or the backslash key on the RT 102-key keyboard.
|
||||
*/
|
||||
OEM_102 = 92,
|
||||
|
||||
NUMPAD_0 = 93, // VK_NUMPAD0, 0x60, Numeric keypad 0 key
|
||||
NUMPAD_1 = 94, // VK_NUMPAD1, 0x61, Numeric keypad 1 key
|
||||
NUMPAD_2 = 95, // VK_NUMPAD2, 0x62, Numeric keypad 2 key
|
||||
NUMPAD_3 = 96, // VK_NUMPAD3, 0x63, Numeric keypad 3 key
|
||||
NUMPAD_4 = 97, // VK_NUMPAD4, 0x64, Numeric keypad 4 key
|
||||
NUMPAD_5 = 98, // VK_NUMPAD5, 0x65, Numeric keypad 5 key
|
||||
NUMPAD_6 = 99, // VK_NUMPAD6, 0x66, Numeric keypad 6 key
|
||||
NUMPAD_7 = 100, // VK_NUMPAD7, 0x67, Numeric keypad 7 key
|
||||
NUMPAD_8 = 101, // VK_NUMPAD8, 0x68, Numeric keypad 8 key
|
||||
NUMPAD_9 = 102, // VK_NUMPAD9, 0x69, Numeric keypad 9 key
|
||||
|
||||
NUMPAD_MULTIPLY = 103, // VK_MULTIPLY, 0x6A, Multiply key
|
||||
NUMPAD_ADD = 104, // VK_ADD, 0x6B, Add key
|
||||
NUMPAD_SEPARATOR = 105, // VK_SEPARATOR, 0x6C, Separator key
|
||||
NUMPAD_SUBTRACT = 106, // VK_SUBTRACT, 0x6D, Subtract key
|
||||
NUMPAD_DECIMAL = 107, // VK_DECIMAL, 0x6E, Decimal key
|
||||
NUMPAD_DIVIDE = 108, // VK_DIVIDE, 0x6F,
|
||||
|
||||
/**
|
||||
* Cover all key codes when IME is processing input.
|
||||
*/
|
||||
KEY_IN_COMPOSITION = 109,
|
||||
|
||||
ABNT_C1 = 110, // Brazilian (ABNT) Keyboard
|
||||
ABNT_C2 = 111, // Brazilian (ABNT) Keyboard
|
||||
|
||||
/**
|
||||
* Placed last to cover the length of the enum.
|
||||
* Please do not depend on this value!
|
||||
*/
|
||||
MAX_VALUE
|
||||
}
|
||||
|
||||
class KeyCodeStrMap {
|
||||
|
||||
private _keyCodeToStr: string[];
|
||||
private _strToKeyCode: { [str: string]: KeyCode; };
|
||||
|
||||
constructor() {
|
||||
this._keyCodeToStr = [];
|
||||
this._strToKeyCode = Object.create(null);
|
||||
}
|
||||
|
||||
define(keyCode: KeyCode, str: string): void {
|
||||
this._keyCodeToStr[keyCode] = str;
|
||||
this._strToKeyCode[str.toLowerCase()] = keyCode;
|
||||
}
|
||||
|
||||
keyCodeToStr(keyCode: KeyCode): string {
|
||||
return this._keyCodeToStr[keyCode];
|
||||
}
|
||||
|
||||
strToKeyCode(str: string): KeyCode {
|
||||
return this._strToKeyCode[str.toLowerCase()] || KeyCode.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
const uiMap = new KeyCodeStrMap();
|
||||
const userSettingsUSMap = new KeyCodeStrMap();
|
||||
const userSettingsGeneralMap = new KeyCodeStrMap();
|
||||
|
||||
(function () {
|
||||
|
||||
function define(keyCode: KeyCode, uiLabel: string, usUserSettingsLabel: string = uiLabel, generalUserSettingsLabel: string = usUserSettingsLabel): void {
|
||||
uiMap.define(keyCode, uiLabel);
|
||||
userSettingsUSMap.define(keyCode, usUserSettingsLabel);
|
||||
userSettingsGeneralMap.define(keyCode, generalUserSettingsLabel);
|
||||
}
|
||||
|
||||
define(KeyCode.Unknown, 'unknown');
|
||||
|
||||
define(KeyCode.Backspace, 'Backspace');
|
||||
define(KeyCode.Tab, 'Tab');
|
||||
define(KeyCode.Enter, 'Enter');
|
||||
define(KeyCode.Shift, 'Shift');
|
||||
define(KeyCode.Ctrl, 'Ctrl');
|
||||
define(KeyCode.Alt, 'Alt');
|
||||
define(KeyCode.PauseBreak, 'PauseBreak');
|
||||
define(KeyCode.CapsLock, 'CapsLock');
|
||||
define(KeyCode.Escape, 'Escape');
|
||||
define(KeyCode.Space, 'Space');
|
||||
define(KeyCode.PageUp, 'PageUp');
|
||||
define(KeyCode.PageDown, 'PageDown');
|
||||
define(KeyCode.End, 'End');
|
||||
define(KeyCode.Home, 'Home');
|
||||
|
||||
define(KeyCode.LeftArrow, 'LeftArrow', 'Left');
|
||||
define(KeyCode.UpArrow, 'UpArrow', 'Up');
|
||||
define(KeyCode.RightArrow, 'RightArrow', 'Right');
|
||||
define(KeyCode.DownArrow, 'DownArrow', 'Down');
|
||||
define(KeyCode.Insert, 'Insert');
|
||||
define(KeyCode.Delete, 'Delete');
|
||||
|
||||
define(KeyCode.KEY_0, '0');
|
||||
define(KeyCode.KEY_1, '1');
|
||||
define(KeyCode.KEY_2, '2');
|
||||
define(KeyCode.KEY_3, '3');
|
||||
define(KeyCode.KEY_4, '4');
|
||||
define(KeyCode.KEY_5, '5');
|
||||
define(KeyCode.KEY_6, '6');
|
||||
define(KeyCode.KEY_7, '7');
|
||||
define(KeyCode.KEY_8, '8');
|
||||
define(KeyCode.KEY_9, '9');
|
||||
|
||||
define(KeyCode.KEY_A, 'A');
|
||||
define(KeyCode.KEY_B, 'B');
|
||||
define(KeyCode.KEY_C, 'C');
|
||||
define(KeyCode.KEY_D, 'D');
|
||||
define(KeyCode.KEY_E, 'E');
|
||||
define(KeyCode.KEY_F, 'F');
|
||||
define(KeyCode.KEY_G, 'G');
|
||||
define(KeyCode.KEY_H, 'H');
|
||||
define(KeyCode.KEY_I, 'I');
|
||||
define(KeyCode.KEY_J, 'J');
|
||||
define(KeyCode.KEY_K, 'K');
|
||||
define(KeyCode.KEY_L, 'L');
|
||||
define(KeyCode.KEY_M, 'M');
|
||||
define(KeyCode.KEY_N, 'N');
|
||||
define(KeyCode.KEY_O, 'O');
|
||||
define(KeyCode.KEY_P, 'P');
|
||||
define(KeyCode.KEY_Q, 'Q');
|
||||
define(KeyCode.KEY_R, 'R');
|
||||
define(KeyCode.KEY_S, 'S');
|
||||
define(KeyCode.KEY_T, 'T');
|
||||
define(KeyCode.KEY_U, 'U');
|
||||
define(KeyCode.KEY_V, 'V');
|
||||
define(KeyCode.KEY_W, 'W');
|
||||
define(KeyCode.KEY_X, 'X');
|
||||
define(KeyCode.KEY_Y, 'Y');
|
||||
define(KeyCode.KEY_Z, 'Z');
|
||||
|
||||
define(KeyCode.Meta, 'Meta');
|
||||
define(KeyCode.ContextMenu, 'ContextMenu');
|
||||
|
||||
define(KeyCode.F1, 'F1');
|
||||
define(KeyCode.F2, 'F2');
|
||||
define(KeyCode.F3, 'F3');
|
||||
define(KeyCode.F4, 'F4');
|
||||
define(KeyCode.F5, 'F5');
|
||||
define(KeyCode.F6, 'F6');
|
||||
define(KeyCode.F7, 'F7');
|
||||
define(KeyCode.F8, 'F8');
|
||||
define(KeyCode.F9, 'F9');
|
||||
define(KeyCode.F10, 'F10');
|
||||
define(KeyCode.F11, 'F11');
|
||||
define(KeyCode.F12, 'F12');
|
||||
define(KeyCode.F13, 'F13');
|
||||
define(KeyCode.F14, 'F14');
|
||||
define(KeyCode.F15, 'F15');
|
||||
define(KeyCode.F16, 'F16');
|
||||
define(KeyCode.F17, 'F17');
|
||||
define(KeyCode.F18, 'F18');
|
||||
define(KeyCode.F19, 'F19');
|
||||
|
||||
define(KeyCode.NumLock, 'NumLock');
|
||||
define(KeyCode.ScrollLock, 'ScrollLock');
|
||||
|
||||
define(KeyCode.US_SEMICOLON, ';', ';', 'OEM_1');
|
||||
define(KeyCode.US_EQUAL, '=', '=', 'OEM_PLUS');
|
||||
define(KeyCode.US_COMMA, ',', ',', 'OEM_COMMA');
|
||||
define(KeyCode.US_MINUS, '-', '-', 'OEM_MINUS');
|
||||
define(KeyCode.US_DOT, '.', '.', 'OEM_PERIOD');
|
||||
define(KeyCode.US_SLASH, '/', '/', 'OEM_2');
|
||||
define(KeyCode.US_BACKTICK, '`', '`', 'OEM_3');
|
||||
define(KeyCode.ABNT_C1, 'ABNT_C1');
|
||||
define(KeyCode.ABNT_C2, 'ABNT_C2');
|
||||
define(KeyCode.US_OPEN_SQUARE_BRACKET, '[', '[', 'OEM_4');
|
||||
define(KeyCode.US_BACKSLASH, '\\', '\\', 'OEM_5');
|
||||
define(KeyCode.US_CLOSE_SQUARE_BRACKET, ']', ']', 'OEM_6');
|
||||
define(KeyCode.US_QUOTE, '\'', '\'', 'OEM_7');
|
||||
define(KeyCode.OEM_8, 'OEM_8');
|
||||
define(KeyCode.OEM_102, 'OEM_102');
|
||||
|
||||
define(KeyCode.NUMPAD_0, 'NumPad0');
|
||||
define(KeyCode.NUMPAD_1, 'NumPad1');
|
||||
define(KeyCode.NUMPAD_2, 'NumPad2');
|
||||
define(KeyCode.NUMPAD_3, 'NumPad3');
|
||||
define(KeyCode.NUMPAD_4, 'NumPad4');
|
||||
define(KeyCode.NUMPAD_5, 'NumPad5');
|
||||
define(KeyCode.NUMPAD_6, 'NumPad6');
|
||||
define(KeyCode.NUMPAD_7, 'NumPad7');
|
||||
define(KeyCode.NUMPAD_8, 'NumPad8');
|
||||
define(KeyCode.NUMPAD_9, 'NumPad9');
|
||||
|
||||
define(KeyCode.NUMPAD_MULTIPLY, 'NumPad_Multiply');
|
||||
define(KeyCode.NUMPAD_ADD, 'NumPad_Add');
|
||||
define(KeyCode.NUMPAD_SEPARATOR, 'NumPad_Separator');
|
||||
define(KeyCode.NUMPAD_SUBTRACT, 'NumPad_Subtract');
|
||||
define(KeyCode.NUMPAD_DECIMAL, 'NumPad_Decimal');
|
||||
define(KeyCode.NUMPAD_DIVIDE, 'NumPad_Divide');
|
||||
|
||||
})();
|
||||
|
||||
export namespace KeyCodeUtils {
|
||||
export function toString(keyCode: KeyCode): string {
|
||||
return uiMap.keyCodeToStr(keyCode);
|
||||
}
|
||||
export function fromString(key: string): KeyCode {
|
||||
return uiMap.strToKeyCode(key);
|
||||
}
|
||||
|
||||
export function toUserSettingsUS(keyCode: KeyCode): string {
|
||||
return userSettingsUSMap.keyCodeToStr(keyCode);
|
||||
}
|
||||
export function toUserSettingsGeneral(keyCode: KeyCode): string {
|
||||
return userSettingsGeneralMap.keyCodeToStr(keyCode);
|
||||
}
|
||||
export function fromUserSettings(key: string): KeyCode {
|
||||
return userSettingsUSMap.strToKeyCode(key) || userSettingsGeneralMap.strToKeyCode(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary encoding strategy:
|
||||
* ```
|
||||
* 1111 11
|
||||
* 5432 1098 7654 3210
|
||||
* ---- CSAW KKKK KKKK
|
||||
* C = bit 11 = ctrlCmd flag
|
||||
* S = bit 10 = shift flag
|
||||
* A = bit 9 = alt flag
|
||||
* W = bit 8 = winCtrl flag
|
||||
* K = bits 0-7 = key code
|
||||
* ```
|
||||
*/
|
||||
const enum BinaryKeybindingsMask {
|
||||
CtrlCmd = (1 << 11) >>> 0,
|
||||
Shift = (1 << 10) >>> 0,
|
||||
Alt = (1 << 9) >>> 0,
|
||||
WinCtrl = (1 << 8) >>> 0,
|
||||
KeyCode = 0x000000ff
|
||||
}
|
||||
|
||||
export const enum KeyMod {
|
||||
CtrlCmd = (1 << 11) >>> 0,
|
||||
Shift = (1 << 10) >>> 0,
|
||||
Alt = (1 << 9) >>> 0,
|
||||
WinCtrl = (1 << 8) >>> 0,
|
||||
}
|
||||
|
||||
export function KeyChord(firstPart: number, secondPart: number): number {
|
||||
let chordPart = ((secondPart & 0x0000ffff) << 16) >>> 0;
|
||||
return (firstPart | chordPart) >>> 0;
|
||||
}
|
||||
|
||||
export function createKeybinding(keybinding: number, OS: OperatingSystem): Keybinding {
|
||||
if (keybinding === 0) {
|
||||
return null;
|
||||
}
|
||||
const firstPart = (keybinding & 0x0000ffff) >>> 0;
|
||||
const chordPart = (keybinding & 0xffff0000) >>> 16;
|
||||
if (chordPart !== 0) {
|
||||
return new ChordKeybinding(
|
||||
createSimpleKeybinding(firstPart, OS),
|
||||
createSimpleKeybinding(chordPart, OS),
|
||||
);
|
||||
}
|
||||
return createSimpleKeybinding(firstPart, OS);
|
||||
}
|
||||
|
||||
export function createSimpleKeybinding(keybinding: number, OS: OperatingSystem): SimpleKeybinding {
|
||||
|
||||
const ctrlCmd = (keybinding & BinaryKeybindingsMask.CtrlCmd ? true : false);
|
||||
const winCtrl = (keybinding & BinaryKeybindingsMask.WinCtrl ? true : false);
|
||||
|
||||
const ctrlKey = (OS === OperatingSystem.Macintosh ? winCtrl : ctrlCmd);
|
||||
const shiftKey = (keybinding & BinaryKeybindingsMask.Shift ? true : false);
|
||||
const altKey = (keybinding & BinaryKeybindingsMask.Alt ? true : false);
|
||||
const metaKey = (OS === OperatingSystem.Macintosh ? ctrlCmd : winCtrl);
|
||||
const keyCode = (keybinding & BinaryKeybindingsMask.KeyCode);
|
||||
|
||||
return new SimpleKeybinding(ctrlKey, shiftKey, altKey, metaKey, keyCode);
|
||||
}
|
||||
|
||||
export const enum KeybindingType {
|
||||
Simple = 1,
|
||||
Chord = 2
|
||||
}
|
||||
|
||||
export class SimpleKeybinding {
|
||||
public readonly type = KeybindingType.Simple;
|
||||
|
||||
public readonly ctrlKey: boolean;
|
||||
public readonly shiftKey: boolean;
|
||||
public readonly altKey: boolean;
|
||||
public readonly metaKey: boolean;
|
||||
public readonly keyCode: KeyCode;
|
||||
|
||||
constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, keyCode: KeyCode) {
|
||||
this.ctrlKey = ctrlKey;
|
||||
this.shiftKey = shiftKey;
|
||||
this.altKey = altKey;
|
||||
this.metaKey = metaKey;
|
||||
this.keyCode = keyCode;
|
||||
}
|
||||
|
||||
public equals(other: Keybinding): boolean {
|
||||
if (other.type !== KeybindingType.Simple) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
this.ctrlKey === other.ctrlKey
|
||||
&& this.shiftKey === other.shiftKey
|
||||
&& this.altKey === other.altKey
|
||||
&& this.metaKey === other.metaKey
|
||||
&& this.keyCode === other.keyCode
|
||||
);
|
||||
}
|
||||
|
||||
public isModifierKey(): boolean {
|
||||
return (
|
||||
this.keyCode === KeyCode.Unknown
|
||||
|| this.keyCode === KeyCode.Ctrl
|
||||
|| this.keyCode === KeyCode.Meta
|
||||
|| this.keyCode === KeyCode.Alt
|
||||
|| this.keyCode === KeyCode.Shift
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this keybinding refer to the key code of a modifier and it also has the modifier flag?
|
||||
*/
|
||||
public isDuplicateModifierCase(): boolean {
|
||||
return (
|
||||
(this.ctrlKey && this.keyCode === KeyCode.Ctrl)
|
||||
|| (this.shiftKey && this.keyCode === KeyCode.Shift)
|
||||
|| (this.altKey && this.keyCode === KeyCode.Alt)
|
||||
|| (this.metaKey && this.keyCode === KeyCode.Meta)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ChordKeybinding {
|
||||
public readonly type = KeybindingType.Chord;
|
||||
|
||||
public readonly firstPart: SimpleKeybinding;
|
||||
public readonly chordPart: SimpleKeybinding;
|
||||
|
||||
constructor(firstPart: SimpleKeybinding, chordPart: SimpleKeybinding) {
|
||||
this.firstPart = firstPart;
|
||||
this.chordPart = chordPart;
|
||||
}
|
||||
}
|
||||
|
||||
export type Keybinding = SimpleKeybinding | ChordKeybinding;
|
||||
|
||||
export class ResolvedKeybindingPart {
|
||||
readonly ctrlKey: boolean;
|
||||
readonly shiftKey: boolean;
|
||||
readonly altKey: boolean;
|
||||
readonly metaKey: boolean;
|
||||
|
||||
readonly keyLabel: string;
|
||||
readonly keyAriaLabel: string;
|
||||
|
||||
constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, kbLabel: string, kbAriaLabel: string) {
|
||||
this.ctrlKey = ctrlKey;
|
||||
this.shiftKey = shiftKey;
|
||||
this.altKey = altKey;
|
||||
this.metaKey = metaKey;
|
||||
this.keyLabel = kbLabel;
|
||||
this.keyAriaLabel = kbAriaLabel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A resolved keybinding. Can be a simple keybinding or a chord keybinding.
|
||||
*/
|
||||
export abstract class ResolvedKeybinding {
|
||||
/**
|
||||
* This prints the binding in a format suitable for displaying in the UI.
|
||||
*/
|
||||
public abstract getLabel(): string;
|
||||
/**
|
||||
* This prints the binding in a format suitable for ARIA.
|
||||
*/
|
||||
public abstract getAriaLabel(): string;
|
||||
/**
|
||||
* This prints the binding in a format suitable for electron's accelerators.
|
||||
* See https://github.com/electron/electron/blob/master/docs/api/accelerator.md
|
||||
*/
|
||||
public abstract getElectronAccelerator(): string;
|
||||
/**
|
||||
* This prints the binding in a format suitable for user settings.
|
||||
*/
|
||||
public abstract getUserSettingsLabel(): string;
|
||||
/**
|
||||
* Is the user settings label reflecting the label?
|
||||
*/
|
||||
public abstract isWYSIWYG(): boolean;
|
||||
|
||||
/**
|
||||
* Is the binding a chord?
|
||||
*/
|
||||
public abstract isChord(): boolean;
|
||||
|
||||
/**
|
||||
* Returns the firstPart, chordPart that should be used for dispatching.
|
||||
*/
|
||||
public abstract getDispatchParts(): [string, string];
|
||||
/**
|
||||
* Returns the firstPart, chordPart of the keybinding.
|
||||
* For simple keybindings, the second element will be null.
|
||||
*/
|
||||
public abstract getParts(): [ResolvedKeybindingPart, ResolvedKeybindingPart];
|
||||
}
|
||||
172
src/vs/base/common/keybindingLabels.ts
Normal file
172
src/vs/base/common/keybindingLabels.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
|
||||
export interface ModifierLabels {
|
||||
readonly ctrlKey: string;
|
||||
readonly shiftKey: string;
|
||||
readonly altKey: string;
|
||||
readonly metaKey: string;
|
||||
readonly separator: string;
|
||||
}
|
||||
|
||||
export interface Modifiers {
|
||||
readonly ctrlKey: boolean;
|
||||
readonly shiftKey: boolean;
|
||||
readonly altKey: boolean;
|
||||
readonly metaKey: boolean;
|
||||
}
|
||||
|
||||
export class ModifierLabelProvider {
|
||||
|
||||
public readonly modifierLabels: ModifierLabels[];
|
||||
|
||||
constructor(mac: ModifierLabels, windows: ModifierLabels, linux: ModifierLabels = windows) {
|
||||
this.modifierLabels = [null];
|
||||
this.modifierLabels[OperatingSystem.Macintosh] = mac;
|
||||
this.modifierLabels[OperatingSystem.Windows] = windows;
|
||||
this.modifierLabels[OperatingSystem.Linux] = linux;
|
||||
}
|
||||
|
||||
public toLabel(firstPartMod: Modifiers, firstPartKey: string, chordPartMod: Modifiers, chordPartKey: string, OS: OperatingSystem): string {
|
||||
if (firstPartKey === null && chordPartKey === null) {
|
||||
return null;
|
||||
}
|
||||
return _asString(firstPartMod, firstPartKey, chordPartMod, chordPartKey, this.modifierLabels[OS]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A label provider that prints modifiers in a suitable format for displaying in the UI.
|
||||
*/
|
||||
export const UILabelProvider = new ModifierLabelProvider(
|
||||
{
|
||||
ctrlKey: '⌃',
|
||||
shiftKey: '⇧',
|
||||
altKey: '⌥',
|
||||
metaKey: '⌘',
|
||||
separator: '',
|
||||
},
|
||||
{
|
||||
ctrlKey: nls.localize('ctrlKey', "Ctrl"),
|
||||
shiftKey: nls.localize('shiftKey', "Shift"),
|
||||
altKey: nls.localize('altKey', "Alt"),
|
||||
metaKey: nls.localize('windowsKey', "Windows"),
|
||||
separator: '+',
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* A label provider that prints modifiers in a suitable format for ARIA.
|
||||
*/
|
||||
export const AriaLabelProvider = new ModifierLabelProvider(
|
||||
{
|
||||
ctrlKey: nls.localize('ctrlKey.long', "Control"),
|
||||
shiftKey: nls.localize('shiftKey.long', "Shift"),
|
||||
altKey: nls.localize('altKey.long', "Alt"),
|
||||
metaKey: nls.localize('cmdKey.long', "Command"),
|
||||
separator: '+',
|
||||
},
|
||||
{
|
||||
ctrlKey: nls.localize('ctrlKey.long', "Control"),
|
||||
shiftKey: nls.localize('shiftKey.long', "Shift"),
|
||||
altKey: nls.localize('altKey.long', "Alt"),
|
||||
metaKey: nls.localize('windowsKey.long', "Windows"),
|
||||
separator: '+',
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* A label provider that prints modifiers in a suitable format for Electron Accelerators.
|
||||
* See https://github.com/electron/electron/blob/master/docs/api/accelerator.md
|
||||
*/
|
||||
export const ElectronAcceleratorLabelProvider = new ModifierLabelProvider(
|
||||
{
|
||||
ctrlKey: 'Ctrl',
|
||||
shiftKey: 'Shift',
|
||||
altKey: 'Alt',
|
||||
metaKey: 'Cmd',
|
||||
separator: '+',
|
||||
},
|
||||
{
|
||||
ctrlKey: 'Ctrl',
|
||||
shiftKey: 'Shift',
|
||||
altKey: 'Alt',
|
||||
metaKey: 'Super',
|
||||
separator: '+',
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* A label provider that prints modifiers in a suitable format for user settings.
|
||||
*/
|
||||
export const UserSettingsLabelProvider = new ModifierLabelProvider(
|
||||
{
|
||||
ctrlKey: 'ctrl',
|
||||
shiftKey: 'shift',
|
||||
altKey: 'alt',
|
||||
metaKey: 'cmd',
|
||||
separator: '+',
|
||||
},
|
||||
{
|
||||
ctrlKey: 'ctrl',
|
||||
shiftKey: 'shift',
|
||||
altKey: 'alt',
|
||||
metaKey: 'win',
|
||||
separator: '+',
|
||||
},
|
||||
{
|
||||
ctrlKey: 'ctrl',
|
||||
shiftKey: 'shift',
|
||||
altKey: 'alt',
|
||||
metaKey: 'meta',
|
||||
separator: '+',
|
||||
}
|
||||
);
|
||||
|
||||
function _simpleAsString(modifiers: Modifiers, key: string, labels: ModifierLabels): string {
|
||||
if (key === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let result: string[] = [];
|
||||
|
||||
// translate modifier keys: Ctrl-Shift-Alt-Meta
|
||||
if (modifiers.ctrlKey) {
|
||||
result.push(labels.ctrlKey);
|
||||
}
|
||||
|
||||
if (modifiers.shiftKey) {
|
||||
result.push(labels.shiftKey);
|
||||
}
|
||||
|
||||
if (modifiers.altKey) {
|
||||
result.push(labels.altKey);
|
||||
}
|
||||
|
||||
if (modifiers.metaKey) {
|
||||
result.push(labels.metaKey);
|
||||
}
|
||||
|
||||
// the actual key
|
||||
result.push(key);
|
||||
|
||||
return result.join(labels.separator);
|
||||
}
|
||||
|
||||
function _asString(firstPartMod: Modifiers, firstPartKey: string, chordPartMod: Modifiers, chordPartKey: string, labels: ModifierLabels): string {
|
||||
let result = _simpleAsString(firstPartMod, firstPartKey, labels);
|
||||
|
||||
if (chordPartKey !== null) {
|
||||
result += ' ';
|
||||
result += _simpleAsString(chordPartMod, chordPartKey, labels);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
334
src/vs/base/common/labels.ts
Normal file
334
src/vs/base/common/labels.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import platform = require('vs/base/common/platform');
|
||||
import { nativeSep, normalize, isEqualOrParent, isEqual, basename, join } from 'vs/base/common/paths';
|
||||
import { endsWith, ltrim } from 'vs/base/common/strings';
|
||||
|
||||
export interface ILabelProvider {
|
||||
|
||||
/**
|
||||
* Given an element returns a label for it to display in the UI.
|
||||
*/
|
||||
getLabel(element: any): string;
|
||||
}
|
||||
|
||||
export interface IRootProvider {
|
||||
getRoot(resource: URI): URI;
|
||||
getWorkspace(): {
|
||||
roots: URI[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IUserHomeProvider {
|
||||
userHome: string;
|
||||
}
|
||||
|
||||
export function getPathLabel(resource: URI | string, rootProvider?: IRootProvider, userHomeProvider?: IUserHomeProvider): string {
|
||||
if (!resource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof resource === 'string') {
|
||||
resource = URI.file(resource);
|
||||
}
|
||||
|
||||
// return early if we can resolve a relative path label from the root
|
||||
const baseResource = rootProvider ? rootProvider.getRoot(resource) : null;
|
||||
if (baseResource) {
|
||||
const hasMultipleRoots = rootProvider.getWorkspace().roots.length > 1;
|
||||
|
||||
let pathLabel: string;
|
||||
if (isEqual(baseResource.fsPath, resource.fsPath, !platform.isLinux /* ignorecase */)) {
|
||||
pathLabel = ''; // no label if pathes are identical
|
||||
} else {
|
||||
pathLabel = normalize(ltrim(resource.fsPath.substr(baseResource.fsPath.length), nativeSep), true);
|
||||
}
|
||||
|
||||
if (hasMultipleRoots) {
|
||||
const rootName = basename(baseResource.fsPath);
|
||||
pathLabel = pathLabel ? join(rootName, pathLabel) : rootName; // always show root basename if there are multiple
|
||||
}
|
||||
|
||||
return pathLabel;
|
||||
}
|
||||
|
||||
// convert c:\something => C:\something
|
||||
if (platform.isWindows && resource.fsPath && resource.fsPath[1] === ':') {
|
||||
return normalize(resource.fsPath.charAt(0).toUpperCase() + resource.fsPath.slice(1), true);
|
||||
}
|
||||
|
||||
// normalize and tildify (macOS, Linux only)
|
||||
let res = normalize(resource.fsPath, true);
|
||||
if (!platform.isWindows && userHomeProvider) {
|
||||
res = tildify(res, userHomeProvider.userHome);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export function tildify(path: string, userHome: string): string {
|
||||
if (path && (platform.isMacintosh || platform.isLinux) && isEqualOrParent(path, userHome, !platform.isLinux /* ignorecase */)) {
|
||||
path = `~${path.substr(userHome.length)}`;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortens the paths but keeps them easy to distinguish.
|
||||
* Replaces not important parts with ellipsis.
|
||||
* Every shorten path matches only one original path and vice versa.
|
||||
*
|
||||
* Algorithm for shortening paths is as follows:
|
||||
* 1. For every path in list, find unique substring of that path.
|
||||
* 2. Unique substring along with ellipsis is shortened path of that path.
|
||||
* 3. To find unique substring of path, consider every segment of length from 1 to path.length of path from end of string
|
||||
* and if present segment is not substring to any other paths then present segment is unique path,
|
||||
* else check if it is not present as suffix of any other path and present segment is suffix of path itself,
|
||||
* if it is true take present segment as unique path.
|
||||
* 4. Apply ellipsis to unique segment according to whether segment is present at start/in-between/end of path.
|
||||
*
|
||||
* Example 1
|
||||
* 1. consider 2 paths i.e. ['a\\b\\c\\d', 'a\\f\\b\\c\\d']
|
||||
* 2. find unique path of first path,
|
||||
* a. 'd' is present in path2 and is suffix of path2, hence not unique of present path.
|
||||
* b. 'c' is present in path2 and 'c' is not suffix of present path, similarly for 'b' and 'a' also.
|
||||
* c. 'd\\c' is suffix of path2.
|
||||
* d. 'b\\c' is not suffix of present path.
|
||||
* e. 'a\\b' is not present in path2, hence unique path is 'a\\b...'.
|
||||
* 3. for path2, 'f' is not present in path1 hence unique is '...\\f\\...'.
|
||||
*
|
||||
* Example 2
|
||||
* 1. consider 2 paths i.e. ['a\\b', 'a\\b\\c'].
|
||||
* a. Even if 'b' is present in path2, as 'b' is suffix of path1 and is not suffix of path2, unique path will be '...\\b'.
|
||||
* 2. for path2, 'c' is not present in path1 hence unique path is '..\\c'.
|
||||
*/
|
||||
const ellipsis = '\u2026';
|
||||
const unc = '\\\\';
|
||||
const home = '~';
|
||||
export function shorten(paths: string[]): string[] {
|
||||
const shortenedPaths: string[] = new Array(paths.length);
|
||||
|
||||
// for every path
|
||||
let match = false;
|
||||
for (let pathIndex = 0; pathIndex < paths.length; pathIndex++) {
|
||||
let path = paths[pathIndex];
|
||||
|
||||
if (path === '') {
|
||||
shortenedPaths[pathIndex] = `.${nativeSep}`;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
shortenedPaths[pathIndex] = path;
|
||||
continue;
|
||||
}
|
||||
|
||||
match = true;
|
||||
|
||||
// trim for now and concatenate unc path (e.g. \\network) or root path (/etc, ~/etc) later
|
||||
let prefix = '';
|
||||
if (path.indexOf(unc) === 0) {
|
||||
prefix = path.substr(0, path.indexOf(unc) + unc.length);
|
||||
path = path.substr(path.indexOf(unc) + unc.length);
|
||||
} else if (path.indexOf(nativeSep) === 0) {
|
||||
prefix = path.substr(0, path.indexOf(nativeSep) + nativeSep.length);
|
||||
path = path.substr(path.indexOf(nativeSep) + nativeSep.length);
|
||||
} else if (path.indexOf(home) === 0) {
|
||||
prefix = path.substr(0, path.indexOf(home) + home.length);
|
||||
path = path.substr(path.indexOf(home) + home.length);
|
||||
}
|
||||
|
||||
// pick the first shortest subpath found
|
||||
const segments: string[] = path.split(nativeSep);
|
||||
for (let subpathLength = 1; match && subpathLength <= segments.length; subpathLength++) {
|
||||
for (let start = segments.length - subpathLength; match && start >= 0; start--) {
|
||||
match = false;
|
||||
let subpath = segments.slice(start, start + subpathLength).join(nativeSep);
|
||||
|
||||
// that is unique to any other path
|
||||
for (let otherPathIndex = 0; !match && otherPathIndex < paths.length; otherPathIndex++) {
|
||||
|
||||
// suffix subpath treated specially as we consider no match 'x' and 'x/...'
|
||||
if (otherPathIndex !== pathIndex && paths[otherPathIndex] && paths[otherPathIndex].indexOf(subpath) > -1) {
|
||||
const isSubpathEnding: boolean = (start + subpathLength === segments.length);
|
||||
|
||||
// Adding separator as prefix for subpath, such that 'endsWith(src, trgt)' considers subpath as directory name instead of plain string.
|
||||
// prefix is not added when either subpath is root directory or path[otherPathIndex] does not have multiple directories.
|
||||
const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(nativeSep) > -1) ? nativeSep + subpath : subpath;
|
||||
const isOtherPathEnding: boolean = endsWith(paths[otherPathIndex], subpathWithSep);
|
||||
|
||||
match = !isSubpathEnding || isOtherPathEnding;
|
||||
}
|
||||
}
|
||||
|
||||
// found unique subpath
|
||||
if (!match) {
|
||||
let result = '';
|
||||
|
||||
// preserve disk drive or root prefix
|
||||
if (endsWith(segments[0], ':') || prefix !== '') {
|
||||
if (start === 1) {
|
||||
// extend subpath to include disk drive prefix
|
||||
start = 0;
|
||||
subpathLength++;
|
||||
subpath = segments[0] + nativeSep + subpath;
|
||||
}
|
||||
|
||||
if (start > 0) {
|
||||
result = segments[0] + nativeSep;
|
||||
}
|
||||
|
||||
result = prefix + result;
|
||||
}
|
||||
|
||||
// add ellipsis at the beginning if neeeded
|
||||
if (start > 0) {
|
||||
result = result + ellipsis + nativeSep;
|
||||
}
|
||||
|
||||
result = result + subpath;
|
||||
|
||||
// add ellipsis at the end if needed
|
||||
if (start + subpathLength < segments.length) {
|
||||
result = result + nativeSep + ellipsis;
|
||||
}
|
||||
|
||||
shortenedPaths[pathIndex] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
shortenedPaths[pathIndex] = path; // use full path if no unique subpaths found
|
||||
}
|
||||
}
|
||||
|
||||
return shortenedPaths;
|
||||
}
|
||||
|
||||
export interface ISeparator {
|
||||
label: string;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
TEXT,
|
||||
VARIABLE,
|
||||
SEPARATOR
|
||||
}
|
||||
|
||||
interface ISegment {
|
||||
value: string;
|
||||
type: Type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to insert values for specific template variables into the string. E.g. "this $(is) a $(template)" can be
|
||||
* passed to this function together with an object that maps "is" and "template" to strings to have them replaced.
|
||||
* @param value string to which templating is applied
|
||||
* @param values the values of the templates to use
|
||||
*/
|
||||
export function template(template: string, values: { [key: string]: string | ISeparator } = Object.create(null)): string {
|
||||
const segments: ISegment[] = [];
|
||||
|
||||
let inVariable = false;
|
||||
let char: string;
|
||||
let curVal = '';
|
||||
for (let i = 0; i < template.length; i++) {
|
||||
char = template[i];
|
||||
|
||||
// Beginning of variable
|
||||
if (char === '$' || (inVariable && char === '{')) {
|
||||
if (curVal) {
|
||||
segments.push({ value: curVal, type: Type.TEXT });
|
||||
}
|
||||
|
||||
curVal = '';
|
||||
inVariable = true;
|
||||
}
|
||||
|
||||
// End of variable
|
||||
else if (char === '}' && inVariable) {
|
||||
const resolved = values[curVal];
|
||||
|
||||
// Variable
|
||||
if (typeof resolved === 'string') {
|
||||
if (resolved.length) {
|
||||
segments.push({ value: resolved, type: Type.VARIABLE });
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
else if (resolved) {
|
||||
const prevSegment = segments[segments.length - 1];
|
||||
if (!prevSegment || prevSegment.type !== Type.SEPARATOR) {
|
||||
segments.push({ value: resolved.label, type: Type.SEPARATOR }); // prevent duplicate separators
|
||||
}
|
||||
}
|
||||
|
||||
curVal = '';
|
||||
inVariable = false;
|
||||
}
|
||||
|
||||
// Text or Variable Name
|
||||
else {
|
||||
curVal += char;
|
||||
}
|
||||
}
|
||||
|
||||
// Tail
|
||||
if (curVal && !inVariable) {
|
||||
segments.push({ value: curVal, type: Type.TEXT });
|
||||
}
|
||||
|
||||
return segments.filter((segment, index) => {
|
||||
|
||||
// Only keep separator if we have values to the left and right
|
||||
if (segment.type === Type.SEPARATOR) {
|
||||
const left = segments[index - 1];
|
||||
const right = segments[index + 1];
|
||||
|
||||
return [left, right].every(segment => segment && segment.type === Type.VARIABLE && segment.value.length > 0);
|
||||
}
|
||||
|
||||
// accept any TEXT and VARIABLE
|
||||
return true;
|
||||
}).map(segment => segment.value).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles mnemonics for menu items. Depending on OS:
|
||||
* - Windows: Supported via & character (replace && with &)
|
||||
* - Linux: Supported via & character (replace && with &)
|
||||
* - macOS: Unsupported (replace && with empty string)
|
||||
*/
|
||||
export function mnemonicMenuLabel(label: string, forceDisableMnemonics?: boolean): string {
|
||||
if (platform.isMacintosh || forceDisableMnemonics) {
|
||||
return label.replace(/\(&&\w\)|&&/g, '');
|
||||
}
|
||||
|
||||
return label.replace(/&&/g, '&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles mnemonics for buttons. Depending on OS:
|
||||
* - Windows: Supported via & character (replace && with &)
|
||||
* - Linux: Supported via _ character (replace && with _)
|
||||
* - macOS: Unsupported (replace && with empty string)
|
||||
*/
|
||||
export function mnemonicButtonLabel(label: string): string {
|
||||
if (platform.isMacintosh) {
|
||||
return label.replace(/\(&&\w\)|&&/g, '');
|
||||
}
|
||||
|
||||
return label.replace(/&&/g, platform.isWindows ? '&' : '_');
|
||||
}
|
||||
|
||||
export function unmnemonicLabel(label: string): string {
|
||||
return label.replace(/&/g, '&&');
|
||||
}
|
||||
124
src/vs/base/common/lifecycle.ts
Normal file
124
src/vs/base/common/lifecycle.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { once } from 'vs/base/common/functional';
|
||||
|
||||
export const empty: IDisposable = Object.freeze({
|
||||
dispose() { }
|
||||
});
|
||||
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export function dispose<T extends IDisposable>(disposable: T): T;
|
||||
export function dispose<T extends IDisposable>(...disposables: T[]): T[];
|
||||
export function dispose<T extends IDisposable>(disposables: T[]): T[];
|
||||
export function dispose<T extends IDisposable>(first: T | T[], ...rest: T[]): T | T[] {
|
||||
|
||||
if (Array.isArray(first)) {
|
||||
first.forEach(d => d && d.dispose());
|
||||
return [];
|
||||
} else if (rest.length === 0) {
|
||||
if (first) {
|
||||
first.dispose();
|
||||
return first;
|
||||
}
|
||||
return undefined;
|
||||
} else {
|
||||
dispose(first);
|
||||
dispose(rest);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function combinedDisposable(disposables: IDisposable[]): IDisposable {
|
||||
return { dispose: () => dispose(disposables) };
|
||||
}
|
||||
|
||||
export function toDisposable(...fns: (() => void)[]): IDisposable {
|
||||
return {
|
||||
dispose() {
|
||||
for (const fn of fns) {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export abstract class Disposable implements IDisposable {
|
||||
|
||||
private _toDispose: IDisposable[];
|
||||
|
||||
constructor() {
|
||||
this._toDispose = [];
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
protected _register<T extends IDisposable>(t: T): T {
|
||||
this._toDispose.push(t);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
export class OneDisposable implements IDisposable {
|
||||
|
||||
private _value: IDisposable;
|
||||
|
||||
set value(value: IDisposable) {
|
||||
if (this._value) {
|
||||
this._value.dispose();
|
||||
}
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IReference<T> extends IDisposable {
|
||||
readonly object: T;
|
||||
}
|
||||
|
||||
export abstract class ReferenceCollection<T> {
|
||||
|
||||
private references: { [key: string]: { readonly object: T; counter: number; } } = Object.create(null);
|
||||
|
||||
constructor() { }
|
||||
|
||||
acquire(key: string): IReference<T> {
|
||||
let reference = this.references[key];
|
||||
|
||||
if (!reference) {
|
||||
reference = this.references[key] = { counter: 0, object: this.createReferencedObject(key) };
|
||||
}
|
||||
|
||||
const { object } = reference;
|
||||
const dispose = once(() => {
|
||||
if (--reference.counter === 0) {
|
||||
this.destroyReferencedObject(reference.object);
|
||||
delete this.references[key];
|
||||
}
|
||||
});
|
||||
|
||||
reference.counter++;
|
||||
|
||||
return { object, dispose };
|
||||
}
|
||||
|
||||
protected abstract createReferencedObject(key: string): T;
|
||||
protected abstract destroyReferencedObject(object: T): void;
|
||||
}
|
||||
|
||||
export class ImmortalReference<T> implements IReference<T> {
|
||||
constructor(public object: T) { }
|
||||
dispose(): void { /* noop */ }
|
||||
}
|
||||
698
src/vs/base/common/map.ts
Normal file
698
src/vs/base/common/map.ts
Normal file
@@ -0,0 +1,698 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export interface Key {
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export interface Entry<K, T> {
|
||||
key: K;
|
||||
value: T;
|
||||
}
|
||||
|
||||
export function values<K, V>(map: Map<K, V>): V[] {
|
||||
const result: V[] = [];
|
||||
map.forEach(value => result.push(value));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function keys<K, V>(map: Map<K, V>): K[] {
|
||||
const result: K[] = [];
|
||||
map.forEach((value, key) => result.push(key));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {
|
||||
let result = map.get(key);
|
||||
if (result === void 0) {
|
||||
result = value;
|
||||
map.set(key, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface ISerializedBoundedLinkedMap<T> {
|
||||
entries: { key: string; value: T }[];
|
||||
}
|
||||
|
||||
interface LinkedEntry<K, T> extends Entry<K, T> {
|
||||
next?: LinkedEntry<K, T>;
|
||||
prev?: LinkedEntry<K, T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple Map<T> that optionally allows to set a limit of entries to store. Once the limit is hit,
|
||||
* the cache will remove the entry that was last recently added. Or, if a ratio is provided below 1,
|
||||
* all elements will be removed until the ratio is full filled (e.g. 0.75 to remove 25% of old elements).
|
||||
*/
|
||||
export class BoundedMap<T> {
|
||||
private map: Map<string, LinkedEntry<string, T>>;
|
||||
|
||||
private head: LinkedEntry<string, T>;
|
||||
private tail: LinkedEntry<string, T>;
|
||||
private ratio: number;
|
||||
|
||||
constructor(private limit = Number.MAX_VALUE, ratio = 1, value?: ISerializedBoundedLinkedMap<T>) {
|
||||
this.map = new Map<string, LinkedEntry<string, T>>();
|
||||
this.ratio = limit * ratio;
|
||||
|
||||
if (value) {
|
||||
value.entries.forEach(entry => {
|
||||
this.set(entry.key, entry.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setLimit(limit: number): void {
|
||||
if (limit < 0) {
|
||||
return; // invalid limit
|
||||
}
|
||||
|
||||
this.limit = limit;
|
||||
while (this.map.size > this.limit) {
|
||||
this.trim();
|
||||
}
|
||||
}
|
||||
|
||||
public serialize(): ISerializedBoundedLinkedMap<T> {
|
||||
const serialized: ISerializedBoundedLinkedMap<T> = { entries: [] };
|
||||
|
||||
this.map.forEach(entry => {
|
||||
serialized.entries.push({ key: entry.key, value: entry.value });
|
||||
});
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this.map.size;
|
||||
}
|
||||
|
||||
public set(key: string, value: T): boolean {
|
||||
if (this.map.has(key)) {
|
||||
return false; // already present!
|
||||
}
|
||||
|
||||
const entry: LinkedEntry<string, T> = { key, value };
|
||||
this.push(entry);
|
||||
|
||||
if (this.size > this.limit) {
|
||||
this.trim();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public get(key: string): T {
|
||||
const entry = this.map.get(key);
|
||||
|
||||
return entry ? entry.value : null;
|
||||
}
|
||||
|
||||
public getOrSet(k: string, t: T): T {
|
||||
const res = this.get(k);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
|
||||
this.set(k, t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public delete(key: string): T {
|
||||
const entry = this.map.get(key);
|
||||
|
||||
if (entry) {
|
||||
this.map.delete(key);
|
||||
|
||||
if (entry.next) {
|
||||
entry.next.prev = entry.prev; // [A]<-[x]<-[C] = [A]<-[C]
|
||||
} else {
|
||||
this.head = entry.prev; // [A]-[x] = [A]
|
||||
}
|
||||
|
||||
if (entry.prev) {
|
||||
entry.prev.next = entry.next; // [A]->[x]->[C] = [A]->[C]
|
||||
} else {
|
||||
this.tail = entry.next; // [x]-[A] = [A]
|
||||
}
|
||||
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public has(key: string): boolean {
|
||||
return this.map.has(key);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.map.clear();
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
}
|
||||
|
||||
private push(entry: LinkedEntry<string, T>): void {
|
||||
if (this.head) {
|
||||
// [A]-[B] = [A]-[B]->[X]
|
||||
entry.prev = this.head;
|
||||
this.head.next = entry;
|
||||
}
|
||||
|
||||
if (!this.tail) {
|
||||
this.tail = entry;
|
||||
}
|
||||
|
||||
this.head = entry;
|
||||
|
||||
this.map.set(entry.key, entry);
|
||||
}
|
||||
|
||||
private trim(): void {
|
||||
if (this.tail) {
|
||||
|
||||
// Remove all elements until ratio is reached
|
||||
if (this.ratio < this.limit) {
|
||||
let index = 0;
|
||||
let current = this.tail;
|
||||
while (current.next) {
|
||||
|
||||
// Remove the entry
|
||||
this.map.delete(current.key);
|
||||
|
||||
// if we reached the element that overflows our ratio condition
|
||||
// make its next element the new tail of the Map and adjust the size
|
||||
if (index === this.ratio) {
|
||||
this.tail = current.next;
|
||||
this.tail.prev = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Move on
|
||||
current = current.next;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Just remove the tail element
|
||||
else {
|
||||
this.map.delete(this.tail.key);
|
||||
|
||||
// [x]-[B] = [B]
|
||||
this.tail = this.tail.next;
|
||||
if (this.tail) {
|
||||
this.tail.prev = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- trie'ish datastructure
|
||||
|
||||
class Node<E> {
|
||||
element?: E;
|
||||
readonly children = new Map<string, Node<E>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* A trie map that allows for fast look up when keys are substrings
|
||||
* to the actual search keys (dir/subdir-problem).
|
||||
*/
|
||||
export class TrieMap<E> {
|
||||
|
||||
static PathSplitter = (s: string) => s.split(/[\\/]/).filter(s => !!s);
|
||||
|
||||
private readonly _splitter: (s: string) => string[];
|
||||
private _root = new Node<E>();
|
||||
|
||||
constructor(splitter: (s: string) => string[] = TrieMap.PathSplitter) {
|
||||
this._splitter = s => splitter(s).filter(s => Boolean(s));
|
||||
}
|
||||
|
||||
insert(path: string, element: E): void {
|
||||
const parts = this._splitter(path);
|
||||
let i = 0;
|
||||
|
||||
// find insertion node
|
||||
let node = this._root;
|
||||
for (; i < parts.length; i++) {
|
||||
let child = node.children.get(parts[i]);
|
||||
if (child) {
|
||||
node = child;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// create new nodes
|
||||
let newNode: Node<E>;
|
||||
for (; i < parts.length; i++) {
|
||||
newNode = new Node<E>();
|
||||
node.children.set(parts[i], newNode);
|
||||
node = newNode;
|
||||
}
|
||||
|
||||
node.element = element;
|
||||
}
|
||||
|
||||
lookUp(path: string): E {
|
||||
const parts = this._splitter(path);
|
||||
|
||||
let { children } = this._root;
|
||||
let node: Node<E>;
|
||||
for (const part of parts) {
|
||||
node = children.get(part);
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
children = node.children;
|
||||
}
|
||||
|
||||
return node.element;
|
||||
}
|
||||
|
||||
findSubstr(path: string): E {
|
||||
const parts = this._splitter(path);
|
||||
|
||||
let lastNode: Node<E>;
|
||||
let { children } = this._root;
|
||||
for (const part of parts) {
|
||||
const node = children.get(part);
|
||||
if (!node) {
|
||||
break;
|
||||
}
|
||||
if (node.element) {
|
||||
lastNode = node;
|
||||
}
|
||||
children = node.children;
|
||||
}
|
||||
|
||||
// return the last matching node
|
||||
// that had an element
|
||||
if (lastNode) {
|
||||
return lastNode.element;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
findSuperstr(path: string): TrieMap<E> {
|
||||
const parts = this._splitter(path);
|
||||
|
||||
let { children } = this._root;
|
||||
let node: Node<E>;
|
||||
for (const part of parts) {
|
||||
node = children.get(part);
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
children = node.children;
|
||||
}
|
||||
|
||||
const result = new TrieMap<E>(this._splitter);
|
||||
result._root = node;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class ResourceMap<T> {
|
||||
|
||||
protected map: Map<string, T>;
|
||||
|
||||
constructor(private ignoreCase?: boolean) {
|
||||
this.map = new Map<string, T>();
|
||||
}
|
||||
|
||||
public set(resource: URI, value: T): void {
|
||||
this.map.set(this.toKey(resource), value);
|
||||
}
|
||||
|
||||
public get(resource: URI): T {
|
||||
return this.map.get(this.toKey(resource));
|
||||
}
|
||||
|
||||
public has(resource: URI): boolean {
|
||||
return this.map.has(this.toKey(resource));
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this.map.size;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.map.clear();
|
||||
}
|
||||
|
||||
public delete(resource: URI): boolean {
|
||||
return this.map.delete(this.toKey(resource));
|
||||
}
|
||||
|
||||
public forEach(clb: (value: T) => void): void {
|
||||
this.map.forEach(clb);
|
||||
}
|
||||
|
||||
public values(): T[] {
|
||||
return values(this.map);
|
||||
}
|
||||
|
||||
private toKey(resource: URI): string {
|
||||
let key = resource.toString();
|
||||
if (this.ignoreCase) {
|
||||
key = key.toLowerCase();
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
export class StrictResourceMap<T> extends ResourceMap<T> {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public keys(): URI[] {
|
||||
return keys(this.map).map(key => URI.parse(key));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// We should fold BoundedMap and LinkedMap. See https://github.com/Microsoft/vscode/issues/28496
|
||||
|
||||
interface Item<K, V> {
|
||||
previous: Item<K, V> | undefined;
|
||||
next: Item<K, V> | undefined;
|
||||
key: K;
|
||||
value: V;
|
||||
}
|
||||
|
||||
export namespace Touch {
|
||||
export const None: 0 = 0;
|
||||
export const First: 1 = 1;
|
||||
export const Last: 2 = 2;
|
||||
}
|
||||
|
||||
export type Touch = 0 | 1 | 2;
|
||||
|
||||
export class LinkedMap<K, V> {
|
||||
|
||||
private _map: Map<K, Item<K, V>>;
|
||||
private _head: Item<K, V> | undefined;
|
||||
private _tail: Item<K, V> | undefined;
|
||||
private _size: number;
|
||||
|
||||
constructor() {
|
||||
this._map = new Map<K, Item<K, V>>();
|
||||
this._head = undefined;
|
||||
this._tail = undefined;
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._map.clear();
|
||||
this._head = undefined;
|
||||
this._tail = undefined;
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return !this._head && !this._tail;
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
public has(key: K): boolean {
|
||||
return this._map.has(key);
|
||||
}
|
||||
|
||||
public get(key: K): V | undefined {
|
||||
const item = this._map.get(key);
|
||||
if (!item) {
|
||||
return undefined;
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
|
||||
public set(key: K, value: V, touch: Touch = Touch.None): void {
|
||||
let item = this._map.get(key);
|
||||
if (item) {
|
||||
item.value = value;
|
||||
if (touch !== Touch.None) {
|
||||
this.touch(item, touch);
|
||||
}
|
||||
} else {
|
||||
item = { key, value, next: undefined, previous: undefined };
|
||||
switch (touch) {
|
||||
case Touch.None:
|
||||
this.addItemLast(item);
|
||||
break;
|
||||
case Touch.First:
|
||||
this.addItemFirst(item);
|
||||
break;
|
||||
case Touch.Last:
|
||||
this.addItemLast(item);
|
||||
break;
|
||||
default:
|
||||
this.addItemLast(item);
|
||||
break;
|
||||
}
|
||||
this._map.set(key, item);
|
||||
this._size++;
|
||||
}
|
||||
}
|
||||
|
||||
public delete(key: K): boolean {
|
||||
return !!this.remove(key);
|
||||
}
|
||||
|
||||
public remove(key: K): V | undefined {
|
||||
const item = this._map.get(key);
|
||||
if (!item) {
|
||||
return undefined;
|
||||
}
|
||||
this._map.delete(key);
|
||||
this.removeItem(item);
|
||||
this._size--;
|
||||
return item.value;
|
||||
}
|
||||
|
||||
public shift(): V | undefined {
|
||||
if (!this._head && !this._tail) {
|
||||
return undefined;
|
||||
}
|
||||
if (!this._head || !this._tail) {
|
||||
throw new Error('Invalid list');
|
||||
}
|
||||
const item = this._head;
|
||||
this._map.delete(item.key);
|
||||
this.removeItem(item);
|
||||
this._size--;
|
||||
return item.value;
|
||||
}
|
||||
|
||||
public forEach(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {
|
||||
let current = this._head;
|
||||
while (current) {
|
||||
if (thisArg) {
|
||||
callbackfn.bind(thisArg)(current.value, current.key, this);
|
||||
} else {
|
||||
callbackfn(current.value, current.key, this);
|
||||
}
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
|
||||
public forEachReverse(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {
|
||||
let current = this._tail;
|
||||
while (current) {
|
||||
if (thisArg) {
|
||||
callbackfn.bind(thisArg)(current.value, current.key, this);
|
||||
} else {
|
||||
callbackfn(current.value, current.key, this);
|
||||
}
|
||||
current = current.previous;
|
||||
}
|
||||
}
|
||||
|
||||
public values(): V[] {
|
||||
let result: V[] = [];
|
||||
let current = this._head;
|
||||
while (current) {
|
||||
result.push(current.value);
|
||||
current = current.next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public keys(): K[] {
|
||||
let result: K[] = [];
|
||||
let current = this._head;
|
||||
while (current) {
|
||||
result.push(current.key);
|
||||
current = current.next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* VS Code / Monaco editor runs on es5 which has no Symbol.iterator
|
||||
public keys(): IterableIterator<K> {
|
||||
let current = this._head;
|
||||
let iterator: IterableIterator<K> = {
|
||||
[Symbol.iterator]() {
|
||||
return iterator;
|
||||
},
|
||||
next():IteratorResult<K> {
|
||||
if (current) {
|
||||
let result = { value: current.key, done: false };
|
||||
current = current.next;
|
||||
return result;
|
||||
} else {
|
||||
return { value: undefined, done: true };
|
||||
}
|
||||
}
|
||||
};
|
||||
return iterator;
|
||||
}
|
||||
|
||||
public values(): IterableIterator<V> {
|
||||
let current = this._head;
|
||||
let iterator: IterableIterator<V> = {
|
||||
[Symbol.iterator]() {
|
||||
return iterator;
|
||||
},
|
||||
next():IteratorResult<V> {
|
||||
if (current) {
|
||||
let result = { value: current.value, done: false };
|
||||
current = current.next;
|
||||
return result;
|
||||
} else {
|
||||
return { value: undefined, done: true };
|
||||
}
|
||||
}
|
||||
};
|
||||
return iterator;
|
||||
}
|
||||
*/
|
||||
|
||||
private addItemFirst(item: Item<K, V>): void {
|
||||
// First time Insert
|
||||
if (!this._head && !this._tail) {
|
||||
this._tail = item;
|
||||
} else if (!this._head) {
|
||||
throw new Error('Invalid list');
|
||||
} else {
|
||||
item.next = this._head;
|
||||
this._head.previous = item;
|
||||
}
|
||||
this._head = item;
|
||||
}
|
||||
|
||||
private addItemLast(item: Item<K, V>): void {
|
||||
// First time Insert
|
||||
if (!this._head && !this._tail) {
|
||||
this._head = item;
|
||||
} else if (!this._tail) {
|
||||
throw new Error('Invalid list');
|
||||
} else {
|
||||
item.previous = this._tail;
|
||||
this._tail.next = item;
|
||||
}
|
||||
this._tail = item;
|
||||
}
|
||||
|
||||
private removeItem(item: Item<K, V>): void {
|
||||
if (item === this._head && item === this._tail) {
|
||||
this._head = undefined;
|
||||
this._tail = undefined;
|
||||
}
|
||||
else if (item === this._head) {
|
||||
this._head = item.next;
|
||||
}
|
||||
else if (item === this._tail) {
|
||||
this._tail = item.previous;
|
||||
}
|
||||
else {
|
||||
const next = item.next;
|
||||
const previous = item.previous;
|
||||
if (!next || !previous) {
|
||||
throw new Error('Invalid list');
|
||||
}
|
||||
next.previous = previous;
|
||||
previous.next = next;
|
||||
}
|
||||
}
|
||||
|
||||
private touch(item: Item<K, V>, touch: Touch): void {
|
||||
if (!this._head || !this._tail) {
|
||||
throw new Error('Invalid list');
|
||||
}
|
||||
if ((touch !== Touch.First && touch !== Touch.Last)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (touch === Touch.First) {
|
||||
if (item === this._head) {
|
||||
return;
|
||||
}
|
||||
|
||||
const next = item.next;
|
||||
const previous = item.previous;
|
||||
|
||||
// Unlink the item
|
||||
if (item === this._tail) {
|
||||
// previous must be defined since item was not head but is tail
|
||||
// So there are more than on item in the map
|
||||
previous!.next = undefined;
|
||||
this._tail = previous;
|
||||
}
|
||||
else {
|
||||
// Both next and previous are not undefined since item was neither head nor tail.
|
||||
next!.previous = previous;
|
||||
previous!.next = next;
|
||||
}
|
||||
|
||||
// Insert the node at head
|
||||
item.previous = undefined;
|
||||
item.next = this._head;
|
||||
this._head.previous = item;
|
||||
this._head = item;
|
||||
} else if (touch === Touch.Last) {
|
||||
if (item === this._tail) {
|
||||
return;
|
||||
}
|
||||
|
||||
const next = item.next;
|
||||
const previous = item.previous;
|
||||
|
||||
// Unlink the item.
|
||||
if (item === this._head) {
|
||||
// next must be defined since item was not tail but is head
|
||||
// So there are more than on item in the map
|
||||
next!.previous = undefined;
|
||||
this._head = next;
|
||||
} else {
|
||||
// Both next and previous are not undefined since item was neither head nor tail.
|
||||
next!.previous = previous;
|
||||
previous!.next = next;
|
||||
}
|
||||
item.next = undefined;
|
||||
item.previous = this._tail;
|
||||
this._tail.next = item;
|
||||
this._tail = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/vs/base/common/marked/OSSREADME.json
Normal file
8
src/vs/base/common/marked/OSSREADME.json
Normal file
@@ -0,0 +1,8 @@
|
||||
// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS:
|
||||
|
||||
[{
|
||||
"name": "chjj-marked",
|
||||
"repositoryURL": "https://github.com/npmcomponent/chjj-marked",
|
||||
"version": "0.3.6",
|
||||
"license": "MIT"
|
||||
}]
|
||||
162
src/vs/base/common/marked/marked.d.ts
vendored
Normal file
162
src/vs/base/common/marked/marked.d.ts
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// Type definitions for Marked
|
||||
// Project:https://github.com/chjj/marked
|
||||
// Definitions by:William Orr <https://github.com/worr>
|
||||
// Definitions:https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
export interface MarkedStatic {
|
||||
/**
|
||||
* Compiles markdown to HTML.
|
||||
*
|
||||
* @param src String of markdown source to be compiled
|
||||
* @param callback Function called when the markdownString has been fully parsed when using async highlighting
|
||||
* @return String of compiled HTML
|
||||
*/
|
||||
(src: string, callback: Function): string;
|
||||
|
||||
/**
|
||||
* Compiles markdown to HTML.
|
||||
*
|
||||
* @param src String of markdown source to be compiled
|
||||
* @param options Hash of options
|
||||
* @param callback Function called when the markdownString has been fully parsed when using async highlighting
|
||||
* @return String of compiled HTML
|
||||
*/
|
||||
(src: string, options?: MarkedOptions, callback?: Function): string;
|
||||
|
||||
/**
|
||||
* @param src String of markdown source to be compiled
|
||||
* @param options Hash of options
|
||||
*/
|
||||
lexer(src: string, options?: MarkedOptions): any[];
|
||||
|
||||
/**
|
||||
* Compiles markdown to HTML.
|
||||
*
|
||||
* @param src String of markdown source to be compiled
|
||||
* @param callback Function called when the markdownString has been fully parsed when using async highlighting
|
||||
* @return String of compiled HTML
|
||||
*/
|
||||
parse(src: string, callback: Function): string;
|
||||
|
||||
/**
|
||||
* Compiles markdown to HTML.
|
||||
*
|
||||
* @param src String of markdown source to be compiled
|
||||
* @param options Hash of options
|
||||
* @param callback Function called when the markdownString has been fully parsed when using async highlighting
|
||||
* @return String of compiled HTML
|
||||
*/
|
||||
parse(src: string, options?: MarkedOptions, callback?: Function): string;
|
||||
|
||||
/**
|
||||
* @param options Hash of options
|
||||
*/
|
||||
parser(src: any[], options?: MarkedOptions): string;
|
||||
|
||||
/**
|
||||
* Sets the default options.
|
||||
*
|
||||
* @param options Hash of options
|
||||
*/
|
||||
setOptions(options: MarkedOptions): void;
|
||||
|
||||
/**
|
||||
* Custom renderer for marked.
|
||||
*/
|
||||
Renderer: Renderer;
|
||||
}
|
||||
|
||||
export interface Renderer {
|
||||
prototype: MarkedRenderer;
|
||||
new (): MarkedRenderer;
|
||||
}
|
||||
|
||||
export interface MarkedRenderer {
|
||||
image(href: string, title: string, text: string): string;
|
||||
code(code: string, language: string): string;
|
||||
blockquote(quote: string): string;
|
||||
html(html: string): string;
|
||||
heading(text: string, level: number): string;
|
||||
hr(): string;
|
||||
list(body: string, ordered: boolean): string;
|
||||
listitem(text: string): string;
|
||||
paragraph(text: string): string;
|
||||
table(header: string, body: string): string;
|
||||
tablerow(content: string): string;
|
||||
tablecell(content: string, flags: ITableFlags): string;
|
||||
strong(text: string): string;
|
||||
em(text: string): string;
|
||||
codespan(code: string): string;
|
||||
br(): string;
|
||||
del(text: string): string;
|
||||
link(href: string, title: string, text: string): string;
|
||||
}
|
||||
|
||||
export interface ITableFlags {
|
||||
header: boolean;
|
||||
align: string; // 'center' || 'left' || 'right'
|
||||
}
|
||||
|
||||
export interface MarkedOptions {
|
||||
/**
|
||||
* Enable GitHub flavored markdown.
|
||||
*/
|
||||
gfm?: boolean;
|
||||
|
||||
/**
|
||||
* Enable GFM tables. This option requires the gfm option to be true.
|
||||
*/
|
||||
tables?: boolean;
|
||||
|
||||
/**
|
||||
* Enable GFM line breaks. This option requires the gfm option to be true.
|
||||
*/
|
||||
breaks?: boolean;
|
||||
|
||||
/**
|
||||
* Conform to obscure parts of markdown.pl as much as possible. Don't fix any of the original markdown bugs or poor behavior.
|
||||
*/
|
||||
pedantic?: boolean;
|
||||
|
||||
/**
|
||||
* Sanitize the output. Ignore any HTML that has been input.
|
||||
*/
|
||||
sanitize?: boolean;
|
||||
|
||||
/**
|
||||
* Use smarter list behavior than the original markdown. May eventually be default with the old behavior moved into pedantic.
|
||||
*/
|
||||
smartLists?: boolean;
|
||||
|
||||
/**
|
||||
* Shows an HTML error message when rendering fails.
|
||||
*/
|
||||
silent?: boolean;
|
||||
|
||||
/**
|
||||
* A function to highlight code blocks. The function takes three arguments:code, lang, and callback.
|
||||
*/
|
||||
highlight?(code: string, lang: string, callback?: Function): void;
|
||||
|
||||
/**
|
||||
* Set the prefix for code block classes.
|
||||
*/
|
||||
langPrefix?: string;
|
||||
|
||||
/**
|
||||
* Use "smart" typograhic punctuation for things like quotes and dashes.
|
||||
*/
|
||||
smartypants?: boolean;
|
||||
|
||||
/**
|
||||
* The renderer to use with marked rendering.
|
||||
*/
|
||||
renderer?: any;
|
||||
}
|
||||
|
||||
export declare var marked: MarkedStatic;
|
||||
10
src/vs/base/common/marked/marked.js
Normal file
10
src/vs/base/common/marked/marked.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
define(['./raw.marked'], function (marked) {
|
||||
return {
|
||||
marked: marked
|
||||
};
|
||||
});
|
||||
19
src/vs/base/common/marked/marked.license.txt
Normal file
19
src/vs/base/common/marked/marked.license.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2011-2014, Christopher Jeffrey (https://github.com/chjj/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
1293
src/vs/base/common/marked/raw.marked.js
Normal file
1293
src/vs/base/common/marked/raw.marked.js
Normal file
File diff suppressed because it is too large
Load Diff
44
src/vs/base/common/marshalling.ts
Normal file
44
src/vs/base/common/marshalling.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export function stringify(obj: any): string {
|
||||
return JSON.stringify(obj, replacer);
|
||||
}
|
||||
|
||||
export function parse(text: string): any {
|
||||
return JSON.parse(text, reviver);
|
||||
}
|
||||
|
||||
interface MarshalledObject {
|
||||
$mid: number;
|
||||
}
|
||||
|
||||
function replacer(key: string, value: any): any {
|
||||
// URI is done via toJSON-member
|
||||
if (value instanceof RegExp) {
|
||||
return {
|
||||
$mid: 2,
|
||||
source: (<RegExp>value).source,
|
||||
flags: ((<RegExp>value).global ? 'g' : '') + ((<RegExp>value).ignoreCase ? 'i' : '') + ((<RegExp>value).multiline ? 'm' : ''),
|
||||
};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function reviver(key: string, value: any): any {
|
||||
let marshallingConst: number;
|
||||
if (value !== void 0 && value !== null) {
|
||||
marshallingConst = (<MarshalledObject>value).$mid;
|
||||
}
|
||||
|
||||
switch (marshallingConst) {
|
||||
case 1: return URI.revive(value);
|
||||
case 2: return new RegExp(value.source, value.flags);
|
||||
default: return value;
|
||||
}
|
||||
}
|
||||
259
src/vs/base/common/mime.ts
Normal file
259
src/vs/base/common/mime.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import paths = require('vs/base/common/paths');
|
||||
import types = require('vs/base/common/types');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import { match } from 'vs/base/common/glob';
|
||||
|
||||
export const MIME_TEXT = 'text/plain';
|
||||
export const MIME_BINARY = 'application/octet-stream';
|
||||
export const MIME_UNKNOWN = 'application/unknown';
|
||||
|
||||
export interface ITextMimeAssociation {
|
||||
id: string;
|
||||
mime: string;
|
||||
filename?: string;
|
||||
extension?: string;
|
||||
filepattern?: string;
|
||||
firstline?: RegExp;
|
||||
userConfigured?: boolean;
|
||||
}
|
||||
|
||||
interface ITextMimeAssociationItem extends ITextMimeAssociation {
|
||||
filenameLowercase?: string;
|
||||
extensionLowercase?: string;
|
||||
filepatternLowercase?: string;
|
||||
filepatternOnPath?: boolean;
|
||||
}
|
||||
|
||||
let registeredAssociations: ITextMimeAssociationItem[] = [];
|
||||
let nonUserRegisteredAssociations: ITextMimeAssociationItem[] = [];
|
||||
let userRegisteredAssociations: ITextMimeAssociationItem[] = [];
|
||||
|
||||
/**
|
||||
* Associate a text mime to the registry.
|
||||
*/
|
||||
export function registerTextMime(association: ITextMimeAssociation): void {
|
||||
|
||||
// Register
|
||||
const associationItem = toTextMimeAssociationItem(association);
|
||||
registeredAssociations.push(associationItem);
|
||||
if (!associationItem.userConfigured) {
|
||||
nonUserRegisteredAssociations.push(associationItem);
|
||||
} else {
|
||||
userRegisteredAssociations.push(associationItem);
|
||||
}
|
||||
|
||||
// Check for conflicts unless this is a user configured association
|
||||
if (!associationItem.userConfigured) {
|
||||
registeredAssociations.forEach(a => {
|
||||
if (a.mime === associationItem.mime || a.userConfigured) {
|
||||
return; // same mime or userConfigured is ok
|
||||
}
|
||||
|
||||
if (associationItem.extension && a.extension === associationItem.extension) {
|
||||
console.warn(`Overwriting extension <<${associationItem.extension}>> to now point to mime <<${associationItem.mime}>>`);
|
||||
}
|
||||
|
||||
if (associationItem.filename && a.filename === associationItem.filename) {
|
||||
console.warn(`Overwriting filename <<${associationItem.filename}>> to now point to mime <<${associationItem.mime}>>`);
|
||||
}
|
||||
|
||||
if (associationItem.filepattern && a.filepattern === associationItem.filepattern) {
|
||||
console.warn(`Overwriting filepattern <<${associationItem.filepattern}>> to now point to mime <<${associationItem.mime}>>`);
|
||||
}
|
||||
|
||||
if (associationItem.firstline && a.firstline === associationItem.firstline) {
|
||||
console.warn(`Overwriting firstline <<${associationItem.firstline}>> to now point to mime <<${associationItem.mime}>>`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toTextMimeAssociationItem(association: ITextMimeAssociation): ITextMimeAssociationItem {
|
||||
return {
|
||||
id: association.id,
|
||||
mime: association.mime,
|
||||
filename: association.filename,
|
||||
extension: association.extension,
|
||||
filepattern: association.filepattern,
|
||||
firstline: association.firstline,
|
||||
userConfigured: association.userConfigured,
|
||||
filenameLowercase: association.filename ? association.filename.toLowerCase() : void 0,
|
||||
extensionLowercase: association.extension ? association.extension.toLowerCase() : void 0,
|
||||
filepatternLowercase: association.filepattern ? association.filepattern.toLowerCase() : void 0,
|
||||
filepatternOnPath: association.filepattern ? association.filepattern.indexOf(paths.sep) >= 0 : false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear text mimes from the registry.
|
||||
*/
|
||||
export function clearTextMimes(onlyUserConfigured?: boolean): void {
|
||||
if (!onlyUserConfigured) {
|
||||
registeredAssociations = [];
|
||||
nonUserRegisteredAssociations = [];
|
||||
userRegisteredAssociations = [];
|
||||
} else {
|
||||
registeredAssociations = registeredAssociations.filter(a => !a.userConfigured);
|
||||
userRegisteredAssociations = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a file, return the best matching mime type for it
|
||||
*/
|
||||
export function guessMimeTypes(path: string, firstLine?: string): string[] {
|
||||
if (!path) {
|
||||
return [MIME_UNKNOWN];
|
||||
}
|
||||
|
||||
path = path.toLowerCase();
|
||||
let filename = paths.basename(path);
|
||||
|
||||
// 1.) User configured mappings have highest priority
|
||||
let configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations);
|
||||
if (configuredMime) {
|
||||
return [configuredMime, MIME_TEXT];
|
||||
}
|
||||
|
||||
// 2.) Registered mappings have middle priority
|
||||
let registeredMime = guessMimeTypeByPath(path, filename, nonUserRegisteredAssociations);
|
||||
if (registeredMime) {
|
||||
return [registeredMime, MIME_TEXT];
|
||||
}
|
||||
|
||||
// 3.) Firstline has lowest priority
|
||||
if (firstLine) {
|
||||
let firstlineMime = guessMimeTypeByFirstline(firstLine);
|
||||
if (firstlineMime) {
|
||||
return [firstlineMime, MIME_TEXT];
|
||||
}
|
||||
}
|
||||
|
||||
return [MIME_UNKNOWN];
|
||||
}
|
||||
|
||||
function guessMimeTypeByPath(path: string, filename: string, associations: ITextMimeAssociationItem[]): string {
|
||||
let filenameMatch: ITextMimeAssociationItem;
|
||||
let patternMatch: ITextMimeAssociationItem;
|
||||
let extensionMatch: ITextMimeAssociationItem;
|
||||
|
||||
// We want to prioritize associations based on the order they are registered so that the last registered
|
||||
// association wins over all other. This is for https://github.com/Microsoft/vscode/issues/20074
|
||||
for (let i = associations.length - 1; i >= 0; i--) {
|
||||
let association = associations[i];
|
||||
|
||||
// First exact name match
|
||||
if (filename === association.filenameLowercase) {
|
||||
filenameMatch = association;
|
||||
break; // take it!
|
||||
}
|
||||
|
||||
// Longest pattern match
|
||||
if (association.filepattern) {
|
||||
if (!patternMatch || association.filepattern.length > patternMatch.filepattern.length) {
|
||||
let target = association.filepatternOnPath ? path : filename; // match on full path if pattern contains path separator
|
||||
if (match(association.filepatternLowercase, target)) {
|
||||
patternMatch = association;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Longest extension match
|
||||
if (association.extension) {
|
||||
if (!extensionMatch || association.extension.length > extensionMatch.extension.length) {
|
||||
if (strings.endsWith(filename, association.extensionLowercase)) {
|
||||
extensionMatch = association;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1.) Exact name match has second highest prio
|
||||
if (filenameMatch) {
|
||||
return filenameMatch.mime;
|
||||
}
|
||||
|
||||
// 2.) Match on pattern
|
||||
if (patternMatch) {
|
||||
return patternMatch.mime;
|
||||
}
|
||||
|
||||
// 3.) Match on extension comes next
|
||||
if (extensionMatch) {
|
||||
return extensionMatch.mime;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function guessMimeTypeByFirstline(firstLine: string): string {
|
||||
if (strings.startsWithUTF8BOM(firstLine)) {
|
||||
firstLine = firstLine.substr(1);
|
||||
}
|
||||
|
||||
if (firstLine.length > 0) {
|
||||
for (let i = 0; i < registeredAssociations.length; ++i) {
|
||||
let association = registeredAssociations[i];
|
||||
if (!association.firstline) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let matches = firstLine.match(association.firstline);
|
||||
if (matches && matches.length > 0) {
|
||||
return association.mime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isBinaryMime(mimes: string): boolean;
|
||||
export function isBinaryMime(mimes: string[]): boolean;
|
||||
export function isBinaryMime(mimes: any): boolean {
|
||||
if (!mimes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mimeVals: string[];
|
||||
if (types.isArray(mimes)) {
|
||||
mimeVals = (<string[]>mimes);
|
||||
} else {
|
||||
mimeVals = (<string>mimes).split(',').map((mime) => mime.trim());
|
||||
}
|
||||
|
||||
return mimeVals.indexOf(MIME_BINARY) >= 0;
|
||||
}
|
||||
|
||||
export function isUnspecific(mime: string[] | string): boolean {
|
||||
if (!mime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof mime === 'string') {
|
||||
return mime === MIME_BINARY || mime === MIME_TEXT || mime === MIME_UNKNOWN;
|
||||
}
|
||||
|
||||
return mime.length === 1 && isUnspecific(mime[0]);
|
||||
}
|
||||
|
||||
export function suggestFilename(langId: string, prefix: string): string {
|
||||
for (let i = 0; i < registeredAssociations.length; i++) {
|
||||
let association = registeredAssociations[i];
|
||||
if (association.userConfigured) {
|
||||
continue; // only support registered ones
|
||||
}
|
||||
|
||||
if (association.id === langId && association.extension) {
|
||||
return prefix + association.extension;
|
||||
}
|
||||
}
|
||||
|
||||
return prefix; // without any known extension, just return the prefix
|
||||
}
|
||||
42
src/vs/base/common/network.ts
Normal file
42
src/vs/base/common/network.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export namespace Schemas {
|
||||
|
||||
/**
|
||||
* A schema that is used for models that exist in memory
|
||||
* only and that have no correspondence on a server or such.
|
||||
*/
|
||||
export const inMemory: string = 'inmemory';
|
||||
|
||||
/**
|
||||
* A schema that is used for setting files
|
||||
*/
|
||||
export const vscode: string = 'vscode';
|
||||
|
||||
/**
|
||||
* A schema that is used for internal private files
|
||||
*/
|
||||
export const internal: string = 'private';
|
||||
|
||||
/**
|
||||
* A walk-through document.
|
||||
*/
|
||||
export const walkThrough: string = 'walkThrough';
|
||||
|
||||
/**
|
||||
* An embedded code snippet.
|
||||
*/
|
||||
export const walkThroughSnippet: string = 'walkThroughSnippet';
|
||||
|
||||
export const http: string = 'http';
|
||||
|
||||
export const https: string = 'https';
|
||||
|
||||
export const file: string = 'file';
|
||||
|
||||
export const untitled: string = 'untitled';
|
||||
}
|
||||
47
src/vs/base/common/numbers.ts
Normal file
47
src/vs/base/common/numbers.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import types = require('vs/base/common/types');
|
||||
|
||||
export type NumberCallback = (index: number) => void;
|
||||
|
||||
export function count(to: number, callback: NumberCallback): void;
|
||||
export function count(from: number, to: number, callback: NumberCallback): void;
|
||||
export function count(fromOrTo: number, toOrCallback?: NumberCallback | number, callback?: NumberCallback): any {
|
||||
var from: number, to: number;
|
||||
|
||||
if (types.isNumber(toOrCallback)) {
|
||||
from = fromOrTo;
|
||||
to = <number>toOrCallback;
|
||||
} else {
|
||||
from = 0;
|
||||
to = fromOrTo;
|
||||
callback = <NumberCallback>toOrCallback;
|
||||
}
|
||||
|
||||
var op = from <= to ? (i: number) => i + 1 : (i: number) => i - 1;
|
||||
var cmp = from <= to ? (a: number, b: number) => a < b : (a: number, b: number) => a > b;
|
||||
|
||||
for (var i = from; cmp(i, to); i = op(i)) {
|
||||
callback(i);
|
||||
}
|
||||
}
|
||||
|
||||
export function countToArray(to: number): number[];
|
||||
export function countToArray(from: number, to: number): number[];
|
||||
export function countToArray(fromOrTo: number, to?: number): number[] {
|
||||
var result: number[] = [];
|
||||
var fn = (i: number) => result.push(i);
|
||||
|
||||
if (types.isUndefined(to)) {
|
||||
count(fromOrTo, fn);
|
||||
} else {
|
||||
count(fromOrTo, to, fn);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
290
src/vs/base/common/objects.ts
Normal file
290
src/vs/base/common/objects.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isObject, isUndefinedOrNull, isArray } from 'vs/base/common/types';
|
||||
|
||||
export function clone<T>(obj: T): T {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
if (obj instanceof RegExp) {
|
||||
// See https://github.com/Microsoft/TypeScript/issues/10990
|
||||
return obj as any;
|
||||
}
|
||||
const result = (Array.isArray(obj)) ? <any>[] : <any>{};
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (obj[key] && typeof obj[key] === 'object') {
|
||||
result[key] = clone(obj[key]);
|
||||
} else {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function deepClone<T>(obj: T): T {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
const result = (Array.isArray(obj)) ? <any>[] : <any>{};
|
||||
Object.getOwnPropertyNames(obj).forEach((key) => {
|
||||
if (obj[key] && typeof obj[key] === 'object') {
|
||||
result[key] = deepClone(obj[key]);
|
||||
} else {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
export function cloneAndChange(obj: any, changer: (orig: any) => any): any {
|
||||
return _cloneAndChange(obj, changer, []);
|
||||
}
|
||||
|
||||
function _cloneAndChange(obj: any, changer: (orig: any) => any, encounteredObjects: any[]): any {
|
||||
if (isUndefinedOrNull(obj)) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const changed = changer(obj);
|
||||
if (typeof changed !== 'undefined') {
|
||||
return changed;
|
||||
}
|
||||
|
||||
if (isArray(obj)) {
|
||||
const r1: any[] = [];
|
||||
for (let i1 = 0; i1 < obj.length; i1++) {
|
||||
r1.push(_cloneAndChange(obj[i1], changer, encounteredObjects));
|
||||
}
|
||||
return r1;
|
||||
}
|
||||
|
||||
if (isObject(obj)) {
|
||||
if (encounteredObjects.indexOf(obj) >= 0) {
|
||||
throw new Error('Cannot clone recursive data-structure');
|
||||
}
|
||||
encounteredObjects.push(obj);
|
||||
const r2 = {};
|
||||
for (let i2 in obj) {
|
||||
if (hasOwnProperty.call(obj, i2)) {
|
||||
r2[i2] = _cloneAndChange(obj[i2], changer, encounteredObjects);
|
||||
}
|
||||
}
|
||||
encounteredObjects.pop();
|
||||
return r2;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all properties of source into destination. The optional parameter "overwrite" allows to control
|
||||
* if existing properties on the destination should be overwritten or not. Defaults to true (overwrite).
|
||||
*/
|
||||
export function mixin(destination: any, source: any, overwrite: boolean = true): any {
|
||||
if (!isObject(destination)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
if (isObject(source)) {
|
||||
Object.keys(source).forEach((key) => {
|
||||
if (key in destination) {
|
||||
if (overwrite) {
|
||||
if (isObject(destination[key]) && isObject(source[key])) {
|
||||
mixin(destination[key], source[key], overwrite);
|
||||
} else {
|
||||
destination[key] = source[key];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destination[key] = source[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
export function assign(destination: any, ...sources: any[]): any {
|
||||
sources.forEach(source => Object.keys(source).forEach((key) => destination[key] = source[key]));
|
||||
return destination;
|
||||
}
|
||||
|
||||
export function toObject<T>(arr: T[], keyMap: (t: T) => string): { [key: string]: T } {
|
||||
return arr.reduce((o, d) => assign(o, { [keyMap(d)]: d }), Object.create(null));
|
||||
}
|
||||
|
||||
export function equals(one: any, other: any): boolean {
|
||||
if (one === other) {
|
||||
return true;
|
||||
}
|
||||
if (one === null || one === undefined || other === null || other === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (typeof one !== typeof other) {
|
||||
return false;
|
||||
}
|
||||
if (typeof one !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if ((Array.isArray(one)) !== (Array.isArray(other))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let i: number;
|
||||
let key: string;
|
||||
|
||||
if (Array.isArray(one)) {
|
||||
if (one.length !== other.length) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < one.length; i++) {
|
||||
if (!equals(one[i], other[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const oneKeys: string[] = [];
|
||||
|
||||
for (key in one) {
|
||||
oneKeys.push(key);
|
||||
}
|
||||
oneKeys.sort();
|
||||
const otherKeys: string[] = [];
|
||||
for (key in other) {
|
||||
otherKeys.push(key);
|
||||
}
|
||||
otherKeys.sort();
|
||||
if (!equals(oneKeys, otherKeys)) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < oneKeys.length; i++) {
|
||||
if (!equals(one[oneKeys[i]], other[oneKeys[i]])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function ensureProperty(obj: any, property: string, defaultValue: any) {
|
||||
if (typeof obj[property] === 'undefined') {
|
||||
obj[property] = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
export function arrayToHash(array: any[]) {
|
||||
const result: any = {};
|
||||
for (let i = 0; i < array.length; ++i) {
|
||||
result[array[i]] = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of strings, returns a function which, given a string
|
||||
* returns true or false whether the string is in that array.
|
||||
*/
|
||||
export function createKeywordMatcher(arr: string[], caseInsensitive: boolean = false): (str: string) => boolean {
|
||||
if (caseInsensitive) {
|
||||
arr = arr.map(function (x) { return x.toLowerCase(); });
|
||||
}
|
||||
const hash = arrayToHash(arr);
|
||||
if (caseInsensitive) {
|
||||
return function (word) {
|
||||
return hash[word.toLowerCase()] !== undefined && hash.hasOwnProperty(word.toLowerCase());
|
||||
};
|
||||
} else {
|
||||
return function (word) {
|
||||
return hash[word] !== undefined && hash.hasOwnProperty(word);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Started from TypeScript's __extends function to make a type a subclass of a specific class.
|
||||
* Modified to work with properties already defined on the derivedClass, since we can't get TS
|
||||
* to call this method before the constructor definition.
|
||||
*/
|
||||
export function derive(baseClass: any, derivedClass: any): void {
|
||||
for (let prop in baseClass) {
|
||||
if (baseClass.hasOwnProperty(prop)) {
|
||||
derivedClass[prop] = baseClass[prop];
|
||||
}
|
||||
}
|
||||
|
||||
derivedClass = derivedClass || function () { };
|
||||
const basePrototype = baseClass.prototype;
|
||||
const derivedPrototype = derivedClass.prototype;
|
||||
derivedClass.prototype = Object.create(basePrototype);
|
||||
|
||||
for (let prop in derivedPrototype) {
|
||||
if (derivedPrototype.hasOwnProperty(prop)) {
|
||||
// handle getters and setters properly
|
||||
Object.defineProperty(derivedClass.prototype, prop, Object.getOwnPropertyDescriptor(derivedPrototype, prop));
|
||||
}
|
||||
}
|
||||
|
||||
// Cast to any due to Bug 16188:PropertyDescriptor set and get function should be optional.
|
||||
Object.defineProperty(derivedClass.prototype, 'constructor', <any>{ value: derivedClass, writable: true, configurable: true, enumerable: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls JSON.Stringify with a replacer to break apart any circular references.
|
||||
* This prevents JSON.stringify from throwing the exception
|
||||
* "Uncaught TypeError: Converting circular structure to JSON"
|
||||
*/
|
||||
export function safeStringify(obj: any): string {
|
||||
const seen: any[] = [];
|
||||
return JSON.stringify(obj, (key, value) => {
|
||||
if (isObject(value) || Array.isArray(value)) {
|
||||
if (seen.indexOf(value) !== -1) {
|
||||
return '[Circular]';
|
||||
} else {
|
||||
seen.push(value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
export function getOrDefault<T, R>(obj: T, fn: (obj: T) => R, defaultValue: R = null): R {
|
||||
const result = fn(obj);
|
||||
return typeof result === 'undefined' ? defaultValue : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that has keys for each value that is different in the base object. Keys
|
||||
* that do not exist in the target but in the base object are not considered.
|
||||
*
|
||||
* Note: This is not a deep-diffing method, so the values are strictly taken into the resulting
|
||||
* object if they differ.
|
||||
*
|
||||
* @param base the object to diff against
|
||||
* @param obj the object to use for diffing
|
||||
*/
|
||||
export type obj = { [key: string]: any; };
|
||||
export function distinct(base: obj, target: obj): obj {
|
||||
const result = Object.create(null);
|
||||
|
||||
if (!base || !target) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const targetKeys = Object.keys(target);
|
||||
targetKeys.forEach(k => {
|
||||
const baseValue = base[k];
|
||||
const targetValue = target[k];
|
||||
|
||||
if (!equals(baseValue, targetValue)) {
|
||||
result[k] = targetValue;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
146
src/vs/base/common/paging.ts
Normal file
146
src/vs/base/common/paging.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { isArray } from 'vs/base/common/types';
|
||||
|
||||
/**
|
||||
* A Pager is a stateless abstraction over a paged collection.
|
||||
*/
|
||||
export interface IPager<T> {
|
||||
firstPage: T[];
|
||||
total: number;
|
||||
pageSize: number;
|
||||
getPage(pageIndex: number): TPromise<T[]>;
|
||||
}
|
||||
|
||||
interface IPage<T> {
|
||||
isResolved: boolean;
|
||||
promise: TPromise<any>;
|
||||
promiseIndexes: Set<number>;
|
||||
elements: T[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A PagedModel is a stateful model over an abstracted paged collection.
|
||||
*/
|
||||
export interface IPagedModel<T> {
|
||||
length: number;
|
||||
isResolved(index: number): boolean;
|
||||
get(index: number): T;
|
||||
resolve(index: number): TPromise<T>;
|
||||
}
|
||||
|
||||
export function singlePagePager<T>(elements: T[]): IPager<T> {
|
||||
return {
|
||||
firstPage: elements,
|
||||
total: elements.length,
|
||||
pageSize: elements.length,
|
||||
getPage: null
|
||||
};
|
||||
}
|
||||
|
||||
export class PagedModel<T> implements IPagedModel<T> {
|
||||
|
||||
private pager: IPager<T>;
|
||||
private pages: IPage<T>[] = [];
|
||||
|
||||
get length(): number { return this.pager.total; }
|
||||
|
||||
constructor(private arg: IPager<T> | T[], private pageTimeout: number = 500) {
|
||||
this.pager = isArray(arg) ? singlePagePager<T>(arg) : arg;
|
||||
|
||||
this.pages = [{ isResolved: true, promise: null, promiseIndexes: new Set<number>(), elements: this.pager.firstPage.slice() }];
|
||||
|
||||
const totalPages = Math.ceil(this.pager.total / this.pager.pageSize);
|
||||
|
||||
for (let i = 0, len = totalPages - 1; i < len; i++) {
|
||||
this.pages.push({ isResolved: false, promise: null, promiseIndexes: new Set<number>(), elements: [] });
|
||||
}
|
||||
}
|
||||
|
||||
isResolved(index: number): boolean {
|
||||
const pageIndex = Math.floor(index / this.pager.pageSize);
|
||||
const page = this.pages[pageIndex];
|
||||
return !!page.isResolved;
|
||||
}
|
||||
|
||||
get(index: number): T {
|
||||
const pageIndex = Math.floor(index / this.pager.pageSize);
|
||||
const indexInPage = index % this.pager.pageSize;
|
||||
const page = this.pages[pageIndex];
|
||||
|
||||
return page.elements[indexInPage];
|
||||
}
|
||||
|
||||
resolve(index: number): TPromise<T> {
|
||||
const pageIndex = Math.floor(index / this.pager.pageSize);
|
||||
const indexInPage = index % this.pager.pageSize;
|
||||
const page = this.pages[pageIndex];
|
||||
|
||||
if (page.isResolved) {
|
||||
return TPromise.as(page.elements[indexInPage]);
|
||||
}
|
||||
|
||||
if (!page.promise) {
|
||||
page.promise = TPromise.timeout(this.pageTimeout)
|
||||
.then(() => this.pager.getPage(pageIndex))
|
||||
.then(elements => {
|
||||
page.elements = elements;
|
||||
page.isResolved = true;
|
||||
page.promise = null;
|
||||
}, err => {
|
||||
page.isResolved = false;
|
||||
page.promise = null;
|
||||
return TPromise.wrapError(err);
|
||||
});
|
||||
}
|
||||
|
||||
return new TPromise<T>((c, e) => {
|
||||
page.promiseIndexes.add(index);
|
||||
page.promise.done(() => c(page.elements[indexInPage]));
|
||||
}, () => {
|
||||
if (!page.promise) {
|
||||
return;
|
||||
}
|
||||
|
||||
page.promiseIndexes.delete(index);
|
||||
|
||||
if (page.promiseIndexes.size === 0) {
|
||||
page.promise.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to array.map, `mapPager` lets you map the elements of an
|
||||
* abstract paged collection to another type.
|
||||
*/
|
||||
export function mapPager<T, R>(pager: IPager<T>, fn: (t: T) => R): IPager<R> {
|
||||
return {
|
||||
firstPage: pager.firstPage.map(fn),
|
||||
total: pager.total,
|
||||
pageSize: pager.pageSize,
|
||||
getPage: pageIndex => pager.getPage(pageIndex).then(r => r.map(fn))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two pagers.
|
||||
*/
|
||||
export function mergePagers<T>(one: IPager<T>, other: IPager<T>): IPager<T> {
|
||||
return {
|
||||
firstPage: [...one.firstPage, ...other.firstPage],
|
||||
total: one.total + other.total,
|
||||
pageSize: one.pageSize + other.pageSize,
|
||||
getPage(pageIndex: number): TPromise<T[]> {
|
||||
return TPromise.join([one.getPage(pageIndex), other.getPage(pageIndex)])
|
||||
.then(([onePage, otherPage]) => [...onePage, ...otherPage]);
|
||||
}
|
||||
};
|
||||
}
|
||||
217
src/vs/base/common/parsers.ts
Normal file
217
src/vs/base/common/parsers.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as Types from 'vs/base/common/types';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
|
||||
export enum ValidationState {
|
||||
OK = 0,
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3,
|
||||
Fatal = 4
|
||||
}
|
||||
|
||||
export class ValidationStatus {
|
||||
private _state: ValidationState;
|
||||
|
||||
constructor() {
|
||||
this._state = ValidationState.OK;
|
||||
}
|
||||
|
||||
public get state(): ValidationState {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public set state(value: ValidationState) {
|
||||
if (value > this._state) {
|
||||
this._state = value;
|
||||
}
|
||||
}
|
||||
|
||||
public isOK(): boolean {
|
||||
return this._state === ValidationState.OK;
|
||||
}
|
||||
|
||||
public isFatal(): boolean {
|
||||
return this._state === ValidationState.Fatal;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IProblemReporter {
|
||||
info(message: string): void;
|
||||
warn(message: string): void;
|
||||
error(message: string): void;
|
||||
fatal(message: string): void;
|
||||
status: ValidationStatus;
|
||||
}
|
||||
|
||||
export class NullProblemReporter implements IProblemReporter {
|
||||
info(message: string): void { };
|
||||
warn(message: string): void { };
|
||||
error(message: string): void { };
|
||||
fatal(message: string): void { };
|
||||
status: ValidationStatus = new ValidationStatus();
|
||||
}
|
||||
|
||||
export abstract class Parser {
|
||||
|
||||
private _problemReporter: IProblemReporter;
|
||||
|
||||
constructor(problemReporter: IProblemReporter) {
|
||||
this._problemReporter = problemReporter;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._problemReporter.status.state = ValidationState.OK;
|
||||
}
|
||||
|
||||
public get problemReporter(): IProblemReporter {
|
||||
return this._problemReporter;
|
||||
}
|
||||
|
||||
public info(message: string): void {
|
||||
this._problemReporter.info(message);
|
||||
}
|
||||
|
||||
public warn(message: string): void {
|
||||
this._problemReporter.warn(message);
|
||||
}
|
||||
|
||||
public error(message: string): void {
|
||||
this._problemReporter.error(message);
|
||||
}
|
||||
|
||||
public fatal(message: string): void {
|
||||
this._problemReporter.fatal(message);
|
||||
}
|
||||
|
||||
protected is(value: any, func: (value: any) => boolean, wrongTypeState?: ValidationState, wrongTypeMessage?: string, undefinedState?: ValidationState, undefinedMessage?: string): boolean {
|
||||
if (Types.isUndefined(value)) {
|
||||
if (undefinedState) {
|
||||
this._problemReporter.status.state = undefinedState;
|
||||
}
|
||||
if (undefinedMessage) {
|
||||
this._problemReporter.info(undefinedMessage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!func(value)) {
|
||||
if (wrongTypeState) {
|
||||
this._problemReporter.status.state = wrongTypeState;
|
||||
}
|
||||
if (wrongTypeMessage) {
|
||||
this.info(wrongTypeMessage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static merge<T>(destination: T, source: T, overwrite: boolean): void {
|
||||
Object.keys(source).forEach((key) => {
|
||||
let destValue = destination[key];
|
||||
let sourceValue = source[key];
|
||||
if (Types.isUndefined(sourceValue)) {
|
||||
return;
|
||||
}
|
||||
if (Types.isUndefined(destValue)) {
|
||||
destination[key] = sourceValue;
|
||||
} else {
|
||||
if (overwrite) {
|
||||
if (Types.isObject(destValue) && Types.isObject(sourceValue)) {
|
||||
this.merge(destValue, sourceValue, overwrite);
|
||||
} else {
|
||||
destination[key] = sourceValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface ISystemVariables {
|
||||
resolve(value: string): string;
|
||||
resolve(value: string[]): string[];
|
||||
resolve(value: IStringDictionary<string>): IStringDictionary<string>;
|
||||
resolve(value: IStringDictionary<string[]>): IStringDictionary<string[]>;
|
||||
resolve(value: IStringDictionary<IStringDictionary<string>>): IStringDictionary<IStringDictionary<string>>;
|
||||
resolveAny<T>(value: T): T;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export abstract class AbstractSystemVariables implements ISystemVariables {
|
||||
|
||||
public resolve(value: string): string;
|
||||
public resolve(value: string[]): string[];
|
||||
public resolve(value: IStringDictionary<string>): IStringDictionary<string>;
|
||||
public resolve(value: IStringDictionary<string[]>): IStringDictionary<string[]>;
|
||||
public resolve(value: IStringDictionary<IStringDictionary<string>>): IStringDictionary<IStringDictionary<string>>;
|
||||
public resolve(value: any): any {
|
||||
if (Types.isString(value)) {
|
||||
return this.resolveString(value);
|
||||
} else if (Types.isArray(value)) {
|
||||
return this.__resolveArray(value);
|
||||
} else if (Types.isObject(value)) {
|
||||
return this.__resolveLiteral(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
resolveAny<T>(value: T): T;
|
||||
resolveAny<T>(value: any): any {
|
||||
if (Types.isString(value)) {
|
||||
return this.resolveString(value);
|
||||
} else if (Types.isArray(value)) {
|
||||
return this.__resolveAnyArray(value);
|
||||
} else if (Types.isObject(value)) {
|
||||
return this.__resolveAnyLiteral(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected resolveString(value: string): string {
|
||||
let regexp = /\$\{(.*?)\}/g;
|
||||
return value.replace(regexp, (match: string, name: string) => {
|
||||
let newValue = (<any>this)[name];
|
||||
if (Types.isString(newValue)) {
|
||||
return newValue;
|
||||
} else {
|
||||
return match && match.indexOf('env.') > 0 ? '' : match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private __resolveLiteral(values: IStringDictionary<string | IStringDictionary<string> | string[]>): IStringDictionary<string | IStringDictionary<string> | string[]> {
|
||||
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
|
||||
Object.keys(values).forEach(key => {
|
||||
let value = values[key];
|
||||
result[key] = <any>this.resolve(<any>value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private __resolveAnyLiteral<T>(values: T): T;
|
||||
private __resolveAnyLiteral<T>(values: any): any {
|
||||
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
|
||||
Object.keys(values).forEach(key => {
|
||||
let value = values[key];
|
||||
result[key] = <any>this.resolveAny(<any>value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private __resolveArray(value: string[]): string[] {
|
||||
return value.map(s => this.resolveString(s));
|
||||
}
|
||||
|
||||
private __resolveAnyArray<T>(value: T[]): T[];
|
||||
private __resolveAnyArray(value: any[]): any[] {
|
||||
return value.map(s => this.resolveAny(s));
|
||||
}
|
||||
}
|
||||
428
src/vs/base/common/paths.ts
Normal file
428
src/vs/base/common/paths.ts
Normal file
@@ -0,0 +1,428 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { fill } from 'vs/base/common/arrays';
|
||||
import { rtrim, beginsWithIgnoreCase, equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
/**
|
||||
* The forward slash path separator.
|
||||
*/
|
||||
export const sep = '/';
|
||||
|
||||
/**
|
||||
* The native path separator depending on the OS.
|
||||
*/
|
||||
export const nativeSep = isWindows ? '\\' : '/';
|
||||
|
||||
export function relative(from: string, to: string): string {
|
||||
// ignore trailing slashes
|
||||
const originalNormalizedFrom = rtrim(normalize(from), sep);
|
||||
const originalNormalizedTo = rtrim(normalize(to), sep);
|
||||
|
||||
// we're assuming here that any non=linux OS is case insensitive
|
||||
// so we must compare each part in its lowercase form
|
||||
const normalizedFrom = isLinux ? originalNormalizedFrom : originalNormalizedFrom.toLowerCase();
|
||||
const normalizedTo = isLinux ? originalNormalizedTo : originalNormalizedTo.toLowerCase();
|
||||
|
||||
const fromParts = normalizedFrom.split(sep);
|
||||
const toParts = normalizedTo.split(sep);
|
||||
|
||||
let i = 0, max = Math.min(fromParts.length, toParts.length);
|
||||
|
||||
for (; i < max; i++) {
|
||||
if (fromParts[i] !== toParts[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const result = [
|
||||
...fill(fromParts.length - i, () => '..'),
|
||||
...originalNormalizedTo.split(sep).slice(i)
|
||||
];
|
||||
|
||||
return result.join(sep);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the directory name of a path.
|
||||
*/
|
||||
export function dirname(path: string): string {
|
||||
const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\');
|
||||
if (idx === 0) {
|
||||
return '.';
|
||||
} else if (~idx === 0) {
|
||||
return path[0];
|
||||
} else {
|
||||
let res = path.substring(0, ~idx);
|
||||
if (isWindows && res[res.length - 1] === ':') {
|
||||
res += nativeSep; // make sure drive letters end with backslash
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the base name of a path.
|
||||
*/
|
||||
export function basename(path: string): string {
|
||||
const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\');
|
||||
if (idx === 0) {
|
||||
return path;
|
||||
} else if (~idx === path.length - 1) {
|
||||
return basename(path.substring(0, path.length - 1));
|
||||
} else {
|
||||
return path.substr(~idx + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{.far}} from boo.far or the empty string.
|
||||
*/
|
||||
export function extname(path: string): string {
|
||||
path = basename(path);
|
||||
const idx = ~path.lastIndexOf('.');
|
||||
return idx ? path.substring(~idx) : '';
|
||||
}
|
||||
|
||||
const _posixBadPath = /(\/\.\.?\/)|(\/\.\.?)$|^(\.\.?\/)|(\/\/+)|(\\)/;
|
||||
const _winBadPath = /(\\\.\.?\\)|(\\\.\.?)$|^(\.\.?\\)|(\\\\+)|(\/)/;
|
||||
|
||||
function _isNormal(path: string, win: boolean): boolean {
|
||||
return win
|
||||
? !_winBadPath.test(path)
|
||||
: !_posixBadPath.test(path);
|
||||
}
|
||||
|
||||
export function normalize(path: string, toOSPath?: boolean): string {
|
||||
|
||||
if (path === null || path === void 0) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const len = path.length;
|
||||
if (len === 0) {
|
||||
return '.';
|
||||
}
|
||||
|
||||
const wantsBackslash = isWindows && toOSPath;
|
||||
if (_isNormal(path, wantsBackslash)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const sep = wantsBackslash ? '\\' : '/';
|
||||
const root = getRoot(path, sep);
|
||||
|
||||
// skip the root-portion of the path
|
||||
let start = root.length;
|
||||
let skip = false;
|
||||
let res = '';
|
||||
|
||||
for (let end = root.length; end <= len; end++) {
|
||||
|
||||
// either at the end or at a path-separator character
|
||||
if (end === len || path.charCodeAt(end) === CharCode.Slash || path.charCodeAt(end) === CharCode.Backslash) {
|
||||
|
||||
if (streql(path, start, end, '..')) {
|
||||
// skip current and remove parent (if there is already something)
|
||||
let prev_start = res.lastIndexOf(sep);
|
||||
let prev_part = res.slice(prev_start + 1);
|
||||
if ((root || prev_part.length > 0) && prev_part !== '..') {
|
||||
res = prev_start === -1 ? '' : res.slice(0, prev_start);
|
||||
skip = true;
|
||||
}
|
||||
} else if (streql(path, start, end, '.') && (root || res || end < len - 1)) {
|
||||
// skip current (if there is already something or if there is more to come)
|
||||
skip = true;
|
||||
}
|
||||
|
||||
if (!skip) {
|
||||
let part = path.slice(start, end);
|
||||
if (res !== '' && res[res.length - 1] !== sep) {
|
||||
res += sep;
|
||||
}
|
||||
res += part;
|
||||
}
|
||||
start = end + 1;
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
|
||||
return root + res;
|
||||
}
|
||||
|
||||
function streql(value: string, start: number, end: number, other: string): boolean {
|
||||
return start + other.length === end && value.indexOf(other, start) === start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the _root_ this path, like `getRoot('c:\files') === c:\`,
|
||||
* `getRoot('files:///files/path') === files:///`,
|
||||
* or `getRoot('\\server\shares\path') === \\server\shares\`
|
||||
*/
|
||||
export function getRoot(path: string, sep: string = '/'): string {
|
||||
|
||||
if (!path) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let len = path.length;
|
||||
let code = path.charCodeAt(0);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
|
||||
code = path.charCodeAt(1);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
// UNC candidate \\localhost\shares\ddd
|
||||
// ^^^^^^^^^^^^^^^^^^^
|
||||
code = path.charCodeAt(2);
|
||||
if (code !== CharCode.Slash && code !== CharCode.Backslash) {
|
||||
let pos = 3;
|
||||
let start = pos;
|
||||
for (; pos < len; pos++) {
|
||||
code = path.charCodeAt(pos);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
code = path.charCodeAt(pos + 1);
|
||||
if (start !== pos && code !== CharCode.Slash && code !== CharCode.Backslash) {
|
||||
pos += 1;
|
||||
for (; pos < len; pos++) {
|
||||
code = path.charCodeAt(pos);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
return path.slice(0, pos + 1) // consume this separator
|
||||
.replace(/[\\/]/g, sep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /user/far
|
||||
// ^
|
||||
return sep;
|
||||
|
||||
} else if ((code >= CharCode.A && code <= CharCode.Z) || (code >= CharCode.a && code <= CharCode.z)) {
|
||||
// check for windows drive letter c:\ or c:
|
||||
|
||||
if (path.charCodeAt(1) === CharCode.Colon) {
|
||||
code = path.charCodeAt(2);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
// C:\fff
|
||||
// ^^^
|
||||
return path.slice(0, 2) + sep;
|
||||
} else {
|
||||
// C:
|
||||
// ^^
|
||||
return path.slice(0, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for URI
|
||||
// scheme://authority/path
|
||||
// ^^^^^^^^^^^^^^^^^^^
|
||||
let pos = path.indexOf('://');
|
||||
if (pos !== -1) {
|
||||
pos += 3; // 3 -> "://".length
|
||||
for (; pos < len; pos++) {
|
||||
code = path.charCodeAt(pos);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
return path.slice(0, pos + 1); // consume this separator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export const join: (...parts: string[]) => string = function () {
|
||||
// Not using a function with var-args because of how TS compiles
|
||||
// them to JS - it would result in 2*n runtime cost instead
|
||||
// of 1*n, where n is parts.length.
|
||||
|
||||
let value = '';
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
let part = arguments[i];
|
||||
if (i > 0) {
|
||||
// add the separater between two parts unless
|
||||
// there already is one
|
||||
let last = value.charCodeAt(value.length - 1);
|
||||
if (last !== CharCode.Slash && last !== CharCode.Backslash) {
|
||||
let next = part.charCodeAt(0);
|
||||
if (next !== CharCode.Slash && next !== CharCode.Backslash) {
|
||||
|
||||
value += sep;
|
||||
}
|
||||
}
|
||||
}
|
||||
value += part;
|
||||
}
|
||||
|
||||
return normalize(value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if the path follows this pattern: `\\hostname\sharename`.
|
||||
*
|
||||
* @see https://msdn.microsoft.com/en-us/library/gg465305.aspx
|
||||
* @return A boolean indication if the path is a UNC path, on none-windows
|
||||
* always false.
|
||||
*/
|
||||
export function isUNC(path: string): boolean {
|
||||
if (!isWindows) {
|
||||
// UNC is a windows concept
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!path || path.length < 5) {
|
||||
// at least \\a\b
|
||||
return false;
|
||||
}
|
||||
|
||||
let code = path.charCodeAt(0);
|
||||
if (code !== CharCode.Backslash) {
|
||||
return false;
|
||||
}
|
||||
code = path.charCodeAt(1);
|
||||
if (code !== CharCode.Backslash) {
|
||||
return false;
|
||||
}
|
||||
let pos = 2;
|
||||
let start = pos;
|
||||
for (; pos < path.length; pos++) {
|
||||
code = path.charCodeAt(pos);
|
||||
if (code === CharCode.Backslash) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (start === pos) {
|
||||
return false;
|
||||
}
|
||||
code = path.charCodeAt(pos + 1);
|
||||
if (isNaN(code) || code === CharCode.Backslash) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reference: https://en.wikipedia.org/wiki/Filename
|
||||
const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g;
|
||||
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i;
|
||||
export function isValidBasename(name: string): boolean {
|
||||
if (!name || name.length === 0 || /^\s+$/.test(name)) {
|
||||
return false; // require a name that is not just whitespace
|
||||
}
|
||||
|
||||
INVALID_FILE_CHARS.lastIndex = 0; // the holy grail of software development
|
||||
if (INVALID_FILE_CHARS.test(name)) {
|
||||
return false; // check for certain invalid file characters
|
||||
}
|
||||
|
||||
if (isWindows && WINDOWS_FORBIDDEN_NAMES.test(name)) {
|
||||
return false; // check for certain invalid file names
|
||||
}
|
||||
|
||||
if (name === '.' || name === '..') {
|
||||
return false; // check for reserved values
|
||||
}
|
||||
|
||||
if (isWindows && name[name.length - 1] === '.') {
|
||||
return false; // Windows: file cannot end with a "."
|
||||
}
|
||||
|
||||
if (isWindows && name.length !== name.trim().length) {
|
||||
return false; // Windows: file cannot end with a whitespace
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isEqual(pathA: string, pathB: string, ignoreCase?: boolean): boolean {
|
||||
const identityEquals = (pathA === pathB);
|
||||
if (!ignoreCase || identityEquals) {
|
||||
return identityEquals;
|
||||
}
|
||||
|
||||
if (!pathA || !pathB) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return equalsIgnoreCase(pathA, pathB);
|
||||
}
|
||||
|
||||
export function isEqualOrParent(path: string, candidate: string, ignoreCase?: boolean): boolean {
|
||||
if (path === candidate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!path || !candidate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (candidate.length > path.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ignoreCase) {
|
||||
const beginsWith = beginsWithIgnoreCase(path, candidate);
|
||||
if (!beginsWith) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (candidate.length === path.length) {
|
||||
return true; // same path, different casing
|
||||
}
|
||||
|
||||
let sepOffset = candidate.length;
|
||||
if (candidate.charAt(candidate.length - 1) === nativeSep) {
|
||||
sepOffset--; // adjust the expected sep offset in case our candidate already ends in separator character
|
||||
}
|
||||
|
||||
return path.charAt(sepOffset) === nativeSep;
|
||||
}
|
||||
|
||||
if (candidate.charAt(candidate.length - 1) !== nativeSep) {
|
||||
candidate += nativeSep;
|
||||
}
|
||||
|
||||
return path.indexOf(candidate) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapted from Node's path.isAbsolute functions
|
||||
*/
|
||||
export function isAbsolute(path: string): boolean {
|
||||
return isWindows ?
|
||||
isAbsolute_win32(path) :
|
||||
isAbsolute_posix(path);
|
||||
}
|
||||
|
||||
export function isAbsolute_win32(path: string): boolean {
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char0 = path.charCodeAt(0);
|
||||
if (char0 === CharCode.Slash || char0 === CharCode.Backslash) {
|
||||
return true;
|
||||
} else if ((char0 >= CharCode.A && char0 <= CharCode.Z) || (char0 >= CharCode.a && char0 <= CharCode.z)) {
|
||||
if (path.length > 2 && path.charCodeAt(1) === CharCode.Colon) {
|
||||
const char2 = path.charCodeAt(2);
|
||||
if (char2 === CharCode.Slash || char2 === CharCode.Backslash) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isAbsolute_posix(path: string): boolean {
|
||||
return path && path.charCodeAt(0) === CharCode.Slash;
|
||||
}
|
||||
155
src/vs/base/common/platform.ts
Normal file
155
src/vs/base/common/platform.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
// --- THIS FILE IS TEMPORARY UNTIL ENV.TS IS CLEANED UP. IT CAN SAFELY BE USED IN ALL TARGET EXECUTION ENVIRONMENTS (node & dom) ---
|
||||
|
||||
let _isWindows = false;
|
||||
let _isMacintosh = false;
|
||||
let _isLinux = false;
|
||||
let _isRootUser = false;
|
||||
let _isNative = false;
|
||||
let _isWeb = false;
|
||||
let _locale: string = undefined;
|
||||
let _language: string = undefined;
|
||||
|
||||
interface NLSConfig {
|
||||
locale: string;
|
||||
availableLanguages: { [key: string]: string; };
|
||||
}
|
||||
|
||||
export interface IProcessEnvironment {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface INodeProcess {
|
||||
platform: string;
|
||||
env: IProcessEnvironment;
|
||||
getuid(): number;
|
||||
}
|
||||
declare let process: INodeProcess;
|
||||
declare let global: any;
|
||||
|
||||
interface INavigator {
|
||||
userAgent: string;
|
||||
language: string;
|
||||
}
|
||||
declare let navigator: INavigator;
|
||||
declare let self: any;
|
||||
|
||||
export const LANGUAGE_DEFAULT = 'en';
|
||||
|
||||
// OS detection
|
||||
if (typeof process === 'object') {
|
||||
_isWindows = (process.platform === 'win32');
|
||||
_isMacintosh = (process.platform === 'darwin');
|
||||
_isLinux = (process.platform === 'linux');
|
||||
_isRootUser = !_isWindows && (process.getuid() === 0);
|
||||
let rawNlsConfig = process.env['VSCODE_NLS_CONFIG'];
|
||||
if (rawNlsConfig) {
|
||||
try {
|
||||
let nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
|
||||
let resolved = nlsConfig.availableLanguages['*'];
|
||||
_locale = nlsConfig.locale;
|
||||
// VSCode's default language is 'en'
|
||||
_language = resolved ? resolved : LANGUAGE_DEFAULT;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
_isNative = true;
|
||||
} else if (typeof navigator === 'object') {
|
||||
let userAgent = navigator.userAgent;
|
||||
_isWindows = userAgent.indexOf('Windows') >= 0;
|
||||
_isMacintosh = userAgent.indexOf('Macintosh') >= 0;
|
||||
_isLinux = userAgent.indexOf('Linux') >= 0;
|
||||
_isWeb = true;
|
||||
_locale = navigator.language;
|
||||
_language = _locale;
|
||||
}
|
||||
|
||||
export enum Platform {
|
||||
Web,
|
||||
Mac,
|
||||
Linux,
|
||||
Windows
|
||||
}
|
||||
|
||||
let _platform: Platform = Platform.Web;
|
||||
if (_isNative) {
|
||||
if (_isMacintosh) {
|
||||
_platform = Platform.Mac;
|
||||
} else if (_isWindows) {
|
||||
_platform = Platform.Windows;
|
||||
} else if (_isLinux) {
|
||||
_platform = Platform.Linux;
|
||||
}
|
||||
}
|
||||
|
||||
export const isWindows = _isWindows;
|
||||
export const isMacintosh = _isMacintosh;
|
||||
export const isLinux = _isLinux;
|
||||
export const isRootUser = _isRootUser;
|
||||
export const isNative = _isNative;
|
||||
export const isWeb = _isWeb;
|
||||
export const platform = _platform;
|
||||
|
||||
/**
|
||||
* The language used for the user interface. The format of
|
||||
* the string is all lower case (e.g. zh-tw for Traditional
|
||||
* Chinese)
|
||||
*/
|
||||
export const language = _language;
|
||||
|
||||
/**
|
||||
* The OS locale or the locale specified by --locale. The format of
|
||||
* the string is all lower case (e.g. zh-tw for Traditional
|
||||
* Chinese). The UI is not necessarily shown in the provided locale.
|
||||
*/
|
||||
export const locale = _locale;
|
||||
|
||||
export interface TimeoutToken {
|
||||
}
|
||||
|
||||
export interface IntervalToken {
|
||||
}
|
||||
|
||||
interface IGlobals {
|
||||
Worker?: any;
|
||||
setTimeout(callback: (...args: any[]) => void, delay: number, ...args: any[]): TimeoutToken;
|
||||
clearTimeout(token: TimeoutToken): void;
|
||||
|
||||
setInterval(callback: (...args: any[]) => void, delay: number, ...args: any[]): IntervalToken;
|
||||
clearInterval(token: IntervalToken): void;
|
||||
}
|
||||
|
||||
const _globals = <IGlobals>(typeof self === 'object' ? self : global);
|
||||
export const globals: any = _globals;
|
||||
|
||||
export function hasWebWorkerSupport(): boolean {
|
||||
return typeof _globals.Worker !== 'undefined';
|
||||
}
|
||||
export const setTimeout = _globals.setTimeout.bind(_globals);
|
||||
export const clearTimeout = _globals.clearTimeout.bind(_globals);
|
||||
|
||||
export const setInterval = _globals.setInterval.bind(_globals);
|
||||
export const clearInterval = _globals.clearInterval.bind(_globals);
|
||||
|
||||
export const enum OperatingSystem {
|
||||
Windows = 1,
|
||||
Macintosh = 2,
|
||||
Linux = 3
|
||||
}
|
||||
export const OS = (_isMacintosh ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux));
|
||||
|
||||
export const enum AccessibilitySupport {
|
||||
/**
|
||||
* This should be the browser case where it is not known if a screen reader is attached or no.
|
||||
*/
|
||||
Unknown = 0,
|
||||
|
||||
Disabled = 1,
|
||||
|
||||
Enabled = 2
|
||||
}
|
||||
252
src/vs/base/common/processes.ts
Normal file
252
src/vs/base/common/processes.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import NLS = require('vs/nls');
|
||||
|
||||
import * as Objects from 'vs/base/common/objects';
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import * as Types from 'vs/base/common/types';
|
||||
|
||||
import { ValidationState, IProblemReporter, Parser } from 'vs/base/common/parsers';
|
||||
|
||||
/**
|
||||
* Options to be passed to the external program or shell.
|
||||
*/
|
||||
export interface CommandOptions {
|
||||
/**
|
||||
* The current working directory of the executed program or shell.
|
||||
* If omitted VSCode's current workspace root is used.
|
||||
*/
|
||||
cwd?: string;
|
||||
|
||||
/**
|
||||
* The environment of the executed program or shell. If omitted
|
||||
* the parent process' environment is used.
|
||||
*/
|
||||
env?: { [key: string]: string; };
|
||||
}
|
||||
|
||||
export interface Executable {
|
||||
/**
|
||||
* The command to be executed. Can be an external program or a shell
|
||||
* command.
|
||||
*/
|
||||
command: string;
|
||||
|
||||
/**
|
||||
* Specifies whether the command is a shell command and therefore must
|
||||
* be executed in a shell interpreter (e.g. cmd.exe, bash, ...).
|
||||
*/
|
||||
isShellCommand: boolean;
|
||||
|
||||
/**
|
||||
* The arguments passed to the command.
|
||||
*/
|
||||
args: string[];
|
||||
|
||||
/**
|
||||
* The command options used when the command is executed. Can be omitted.
|
||||
*/
|
||||
options?: CommandOptions;
|
||||
}
|
||||
|
||||
export interface ForkOptions extends CommandOptions {
|
||||
execArgv?: string[];
|
||||
}
|
||||
|
||||
export enum Source {
|
||||
stdout,
|
||||
stderr
|
||||
}
|
||||
|
||||
/**
|
||||
* The data send via a success callback
|
||||
*/
|
||||
export interface SuccessData {
|
||||
error?: Error;
|
||||
cmdCode?: number;
|
||||
terminated?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The data send via a error callback
|
||||
*/
|
||||
export interface ErrorData {
|
||||
error?: Error;
|
||||
terminated?: boolean;
|
||||
stdout?: string;
|
||||
stderr?: string;
|
||||
}
|
||||
|
||||
export interface TerminateResponse {
|
||||
success: boolean;
|
||||
code?: TerminateResponseCode;
|
||||
error?: any;
|
||||
}
|
||||
|
||||
export enum TerminateResponseCode {
|
||||
Success = 0,
|
||||
Unknown = 1,
|
||||
AccessDenied = 2,
|
||||
ProcessNotFound = 3,
|
||||
}
|
||||
|
||||
export namespace Config {
|
||||
/**
|
||||
* Options to be passed to the external program or shell
|
||||
*/
|
||||
export interface CommandOptions {
|
||||
/**
|
||||
* The current working directory of the executed program or shell.
|
||||
* If omitted VSCode's current workspace root is used.
|
||||
*/
|
||||
cwd?: string;
|
||||
|
||||
/**
|
||||
* The additional environment of the executed program or shell. If omitted
|
||||
* the parent process' environment is used.
|
||||
*/
|
||||
env?: IStringDictionary<string>;
|
||||
|
||||
/**
|
||||
* Index signature
|
||||
*/
|
||||
[key: string]: string | string[] | IStringDictionary<string>;
|
||||
}
|
||||
|
||||
export interface BaseExecutable {
|
||||
/**
|
||||
* The command to be executed. Can be an external program or a shell
|
||||
* command.
|
||||
*/
|
||||
command?: string;
|
||||
|
||||
/**
|
||||
* Specifies whether the command is a shell command and therefore must
|
||||
* be executed in a shell interpreter (e.g. cmd.exe, bash, ...).
|
||||
*
|
||||
* Defaults to false if omitted.
|
||||
*/
|
||||
isShellCommand?: boolean;
|
||||
|
||||
/**
|
||||
* The arguments passed to the command. Can be omitted.
|
||||
*/
|
||||
args?: string[];
|
||||
|
||||
/**
|
||||
* The command options used when the command is executed. Can be omitted.
|
||||
*/
|
||||
options?: CommandOptions;
|
||||
}
|
||||
|
||||
export interface Executable extends BaseExecutable {
|
||||
|
||||
/**
|
||||
* Windows specific executable configuration
|
||||
*/
|
||||
windows?: BaseExecutable;
|
||||
|
||||
/**
|
||||
* Mac specific executable configuration
|
||||
*/
|
||||
osx?: BaseExecutable;
|
||||
|
||||
/**
|
||||
* Linux specific executable configuration
|
||||
*/
|
||||
linux?: BaseExecutable;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export interface ParserOptions {
|
||||
globals?: Executable;
|
||||
emptyCommand?: boolean;
|
||||
noDefaults?: boolean;
|
||||
}
|
||||
|
||||
export class ExecutableParser extends Parser {
|
||||
|
||||
constructor(logger: IProblemReporter) {
|
||||
super(logger);
|
||||
}
|
||||
|
||||
public parse(json: Config.Executable, parserOptions: ParserOptions = { globals: null, emptyCommand: false, noDefaults: false }): Executable {
|
||||
let result = this.parseExecutable(json, parserOptions.globals);
|
||||
if (this.problemReporter.status.isFatal()) {
|
||||
return result;
|
||||
}
|
||||
let osExecutable: Executable;
|
||||
if (json.windows && Platform.platform === Platform.Platform.Windows) {
|
||||
osExecutable = this.parseExecutable(json.windows);
|
||||
} else if (json.osx && Platform.platform === Platform.Platform.Mac) {
|
||||
osExecutable = this.parseExecutable(json.osx);
|
||||
} else if (json.linux && Platform.platform === Platform.Platform.Linux) {
|
||||
osExecutable = this.parseExecutable(json.linux);
|
||||
}
|
||||
if (osExecutable) {
|
||||
result = ExecutableParser.mergeExecutable(result, osExecutable);
|
||||
}
|
||||
if ((!result || !result.command) && !parserOptions.emptyCommand) {
|
||||
this.fatal(NLS.localize('ExecutableParser.commandMissing', 'Error: executable info must define a command of type string.'));
|
||||
return null;
|
||||
}
|
||||
if (!parserOptions.noDefaults) {
|
||||
Parser.merge(result, {
|
||||
command: undefined,
|
||||
isShellCommand: false,
|
||||
args: [],
|
||||
options: {}
|
||||
}, false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public parseExecutable(json: Config.BaseExecutable, globals?: Executable): Executable {
|
||||
let command: string = undefined;
|
||||
let isShellCommand: boolean = undefined;
|
||||
let args: string[] = undefined;
|
||||
let options: CommandOptions = undefined;
|
||||
|
||||
if (this.is(json.command, Types.isString)) {
|
||||
command = json.command;
|
||||
}
|
||||
if (this.is(json.isShellCommand, Types.isBoolean, ValidationState.Warning, NLS.localize('ExecutableParser.isShellCommand', 'Warning: isShellCommand must be of type boolean. Ignoring value {0}.', json.isShellCommand))) {
|
||||
isShellCommand = json.isShellCommand;
|
||||
}
|
||||
if (this.is(json.args, Types.isStringArray, ValidationState.Warning, NLS.localize('ExecutableParser.args', 'Warning: args must be of type string[]. Ignoring value {0}.', json.isShellCommand))) {
|
||||
args = json.args.slice(0);
|
||||
}
|
||||
if (this.is(json.options, Types.isObject)) {
|
||||
options = this.parseCommandOptions(json.options);
|
||||
}
|
||||
return { command, isShellCommand, args, options };
|
||||
}
|
||||
|
||||
private parseCommandOptions(json: Config.CommandOptions): CommandOptions {
|
||||
let result: CommandOptions = {};
|
||||
if (!json) {
|
||||
return result;
|
||||
}
|
||||
if (this.is(json.cwd, Types.isString, ValidationState.Warning, NLS.localize('ExecutableParser.invalidCWD', 'Warning: options.cwd must be of type string. Ignoring value {0}.', json.cwd))) {
|
||||
result.cwd = json.cwd;
|
||||
}
|
||||
if (!Types.isUndefined(json.env)) {
|
||||
result.env = Objects.clone(json.env);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static mergeExecutable(executable: Executable, other: Executable): Executable {
|
||||
if (!executable) {
|
||||
return other;
|
||||
}
|
||||
Parser.merge(executable, other, true);
|
||||
return executable;
|
||||
}
|
||||
}
|
||||
134
src/vs/base/common/scorer.ts
Normal file
134
src/vs/base/common/scorer.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Based on material from:
|
||||
/*!
|
||||
BEGIN THIRD PARTY
|
||||
*/
|
||||
/*!
|
||||
* string_score.js: String Scoring Algorithm 0.1.22
|
||||
*
|
||||
* http://joshaven.com/string_score
|
||||
* https://github.com/joshaven/string_score
|
||||
*
|
||||
* Copyright (C) 2009-2014 Joshaven Potter <yourtech@gmail.com>
|
||||
* Special thanks to all of the contributors listed here https://github.com/joshaven/string_score
|
||||
* Source EULA: http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Date: Tue Mar 1 2011
|
||||
* Updated: Tue Mar 10 2015
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compute a score for the given string and the given query.
|
||||
*
|
||||
* Rules:
|
||||
* Character score: 1
|
||||
* Same case bonus: 1
|
||||
* Upper case bonus: 1
|
||||
* Consecutive match bonus: 5
|
||||
* Start of word/path bonus: 7
|
||||
* Start of string bonus: 8
|
||||
*/
|
||||
const wordPathBoundary = ['-', '_', ' ', '/', '\\', '.'];
|
||||
export function score(target: string, query: string, cache?: { [id: string]: number }): number {
|
||||
if (!target || !query) {
|
||||
return 0; // return early if target or query are undefined
|
||||
}
|
||||
|
||||
const hash = target + query;
|
||||
const cached = cache && cache[hash];
|
||||
if (typeof cached === 'number') {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const queryLen = query.length;
|
||||
const targetLower = target.toLowerCase();
|
||||
const queryLower = query.toLowerCase();
|
||||
|
||||
let index = 0;
|
||||
let startAt = 0;
|
||||
let score = 0;
|
||||
while (index < queryLen) {
|
||||
let indexOf = targetLower.indexOf(queryLower[index], startAt);
|
||||
if (indexOf < 0) {
|
||||
score = 0; // This makes sure that the query is contained in the target
|
||||
break;
|
||||
}
|
||||
|
||||
// Character match bonus
|
||||
score += 1;
|
||||
|
||||
// Consecutive match bonus
|
||||
if (startAt === indexOf) {
|
||||
score += 5;
|
||||
}
|
||||
|
||||
// Same case bonus
|
||||
if (target[indexOf] === query[indexOf]) {
|
||||
score += 1;
|
||||
}
|
||||
|
||||
// Start of word bonus
|
||||
if (indexOf === 0) {
|
||||
score += 8;
|
||||
}
|
||||
|
||||
// After separator bonus
|
||||
else if (wordPathBoundary.some(w => w === target[indexOf - 1])) {
|
||||
score += 7;
|
||||
}
|
||||
|
||||
// Inside word upper case bonus
|
||||
else if (isUpper(target.charCodeAt(indexOf))) {
|
||||
score += 1;
|
||||
}
|
||||
|
||||
startAt = indexOf + 1;
|
||||
index++;
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
cache[hash] = score;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
function isUpper(code: number): boolean {
|
||||
return 65 <= code && code <= 90;
|
||||
}
|
||||
|
||||
/**
|
||||
* A fast method to check if a given string would produce a score > 0 for the given query.
|
||||
*/
|
||||
export function matches(target: string, queryLower: string): boolean {
|
||||
if (!target || !queryLower) {
|
||||
return false; // return early if target or query are undefined
|
||||
}
|
||||
|
||||
const queryLen = queryLower.length;
|
||||
const targetLower = target.toLowerCase();
|
||||
|
||||
let index = 0;
|
||||
let lastIndexOf = -1;
|
||||
while (index < queryLen) {
|
||||
let indexOf = targetLower.indexOf(queryLower[index], lastIndexOf + 1);
|
||||
if (indexOf < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lastIndexOf = indexOf;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/*!
|
||||
END THIRD PARTY
|
||||
*/
|
||||
461
src/vs/base/common/scrollable.ts
Normal file
461
src/vs/base/common/scrollable.ts
Normal file
@@ -0,0 +1,461 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export enum ScrollbarVisibility {
|
||||
Auto = 1,
|
||||
Hidden = 2,
|
||||
Visible = 3
|
||||
}
|
||||
|
||||
export interface ScrollEvent {
|
||||
width: number;
|
||||
scrollWidth: number;
|
||||
scrollLeft: number;
|
||||
|
||||
height: number;
|
||||
scrollHeight: number;
|
||||
scrollTop: number;
|
||||
|
||||
widthChanged: boolean;
|
||||
scrollWidthChanged: boolean;
|
||||
scrollLeftChanged: boolean;
|
||||
|
||||
heightChanged: boolean;
|
||||
scrollHeightChanged: boolean;
|
||||
scrollTopChanged: boolean;
|
||||
}
|
||||
|
||||
export class ScrollState implements IScrollDimensions, IScrollPosition {
|
||||
_scrollStateBrand: void;
|
||||
|
||||
public readonly width: number;
|
||||
public readonly scrollWidth: number;
|
||||
public readonly scrollLeft: number;
|
||||
public readonly height: number;
|
||||
public readonly scrollHeight: number;
|
||||
public readonly scrollTop: number;
|
||||
|
||||
constructor(
|
||||
width: number,
|
||||
scrollWidth: number,
|
||||
scrollLeft: number,
|
||||
height: number,
|
||||
scrollHeight: number,
|
||||
scrollTop: number
|
||||
) {
|
||||
width = width | 0;
|
||||
scrollWidth = scrollWidth | 0;
|
||||
scrollLeft = scrollLeft | 0;
|
||||
height = height | 0;
|
||||
scrollHeight = scrollHeight | 0;
|
||||
scrollTop = scrollTop | 0;
|
||||
|
||||
if (width < 0) {
|
||||
width = 0;
|
||||
}
|
||||
if (scrollLeft + width > scrollWidth) {
|
||||
scrollLeft = scrollWidth - width;
|
||||
}
|
||||
if (scrollLeft < 0) {
|
||||
scrollLeft = 0;
|
||||
}
|
||||
|
||||
if (height < 0) {
|
||||
height = 0;
|
||||
}
|
||||
if (scrollTop + height > scrollHeight) {
|
||||
scrollTop = scrollHeight - height;
|
||||
}
|
||||
if (scrollTop < 0) {
|
||||
scrollTop = 0;
|
||||
}
|
||||
|
||||
this.width = width;
|
||||
this.scrollWidth = scrollWidth;
|
||||
this.scrollLeft = scrollLeft;
|
||||
this.height = height;
|
||||
this.scrollHeight = scrollHeight;
|
||||
this.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
public equals(other: ScrollState): boolean {
|
||||
return (
|
||||
this.width === other.width
|
||||
&& this.scrollWidth === other.scrollWidth
|
||||
&& this.scrollLeft === other.scrollLeft
|
||||
&& this.height === other.height
|
||||
&& this.scrollHeight === other.scrollHeight
|
||||
&& this.scrollTop === other.scrollTop
|
||||
);
|
||||
}
|
||||
|
||||
public withScrollDimensions(update: INewScrollDimensions): ScrollState {
|
||||
return new ScrollState(
|
||||
(typeof update.width !== 'undefined' ? update.width : this.width),
|
||||
(typeof update.scrollWidth !== 'undefined' ? update.scrollWidth : this.scrollWidth),
|
||||
this.scrollLeft,
|
||||
(typeof update.height !== 'undefined' ? update.height : this.height),
|
||||
(typeof update.scrollHeight !== 'undefined' ? update.scrollHeight : this.scrollHeight),
|
||||
this.scrollTop
|
||||
);
|
||||
}
|
||||
|
||||
public withScrollPosition(update: INewScrollPosition): ScrollState {
|
||||
return new ScrollState(
|
||||
this.width,
|
||||
this.scrollWidth,
|
||||
(typeof update.scrollLeft !== 'undefined' ? update.scrollLeft : this.scrollLeft),
|
||||
this.height,
|
||||
this.scrollHeight,
|
||||
(typeof update.scrollTop !== 'undefined' ? update.scrollTop : this.scrollTop)
|
||||
);
|
||||
}
|
||||
|
||||
public createScrollEvent(previous: ScrollState): ScrollEvent {
|
||||
let widthChanged = (this.width !== previous.width);
|
||||
let scrollWidthChanged = (this.scrollWidth !== previous.scrollWidth);
|
||||
let scrollLeftChanged = (this.scrollLeft !== previous.scrollLeft);
|
||||
|
||||
let heightChanged = (this.height !== previous.height);
|
||||
let scrollHeightChanged = (this.scrollHeight !== previous.scrollHeight);
|
||||
let scrollTopChanged = (this.scrollTop !== previous.scrollTop);
|
||||
|
||||
return {
|
||||
width: this.width,
|
||||
scrollWidth: this.scrollWidth,
|
||||
scrollLeft: this.scrollLeft,
|
||||
|
||||
height: this.height,
|
||||
scrollHeight: this.scrollHeight,
|
||||
scrollTop: this.scrollTop,
|
||||
|
||||
widthChanged: widthChanged,
|
||||
scrollWidthChanged: scrollWidthChanged,
|
||||
scrollLeftChanged: scrollLeftChanged,
|
||||
|
||||
heightChanged: heightChanged,
|
||||
scrollHeightChanged: scrollHeightChanged,
|
||||
scrollTopChanged: scrollTopChanged,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface IScrollDimensions {
|
||||
readonly width: number;
|
||||
readonly scrollWidth: number;
|
||||
readonly height: number;
|
||||
readonly scrollHeight: number;
|
||||
}
|
||||
export interface INewScrollDimensions {
|
||||
width?: number;
|
||||
scrollWidth?: number;
|
||||
height?: number;
|
||||
scrollHeight?: number;
|
||||
}
|
||||
|
||||
export interface IScrollPosition {
|
||||
readonly scrollLeft: number;
|
||||
readonly scrollTop: number;
|
||||
}
|
||||
export interface ISmoothScrollPosition {
|
||||
readonly scrollLeft: number;
|
||||
readonly scrollTop: number;
|
||||
|
||||
readonly width: number;
|
||||
readonly height: number;
|
||||
}
|
||||
export interface INewScrollPosition {
|
||||
scrollLeft?: number;
|
||||
scrollTop?: number;
|
||||
}
|
||||
|
||||
export class Scrollable extends Disposable {
|
||||
|
||||
_scrollableBrand: void;
|
||||
|
||||
private _smoothScrollDuration: number;
|
||||
private readonly _scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable;
|
||||
private _state: ScrollState;
|
||||
private _smoothScrolling: SmoothScrollingOperation;
|
||||
|
||||
private _onScroll = this._register(new Emitter<ScrollEvent>());
|
||||
public onScroll: Event<ScrollEvent> = this._onScroll.event;
|
||||
|
||||
constructor(smoothScrollDuration: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) {
|
||||
super();
|
||||
|
||||
this._smoothScrollDuration = smoothScrollDuration;
|
||||
this._scheduleAtNextAnimationFrame = scheduleAtNextAnimationFrame;
|
||||
this._state = new ScrollState(0, 0, 0, 0, 0, 0);
|
||||
this._smoothScrolling = null;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._smoothScrolling) {
|
||||
this._smoothScrolling.dispose();
|
||||
this._smoothScrolling = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public setSmoothScrollDuration(smoothScrollDuration: number): void {
|
||||
this._smoothScrollDuration = smoothScrollDuration;
|
||||
}
|
||||
|
||||
public validateScrollPosition(scrollPosition: INewScrollPosition): IScrollPosition {
|
||||
return this._state.withScrollPosition(scrollPosition);
|
||||
}
|
||||
|
||||
public getScrollDimensions(): IScrollDimensions {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public setScrollDimensions(dimensions: INewScrollDimensions): void {
|
||||
const newState = this._state.withScrollDimensions(dimensions);
|
||||
this._setState(newState);
|
||||
|
||||
// Validate outstanding animated scroll position target
|
||||
if (this._smoothScrolling) {
|
||||
this._smoothScrolling.acceptScrollDimensions(this._state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final scroll position that the instance will have once the smooth scroll animation concludes.
|
||||
* If no scroll animation is occuring, it will return the current scroll position instead.
|
||||
*/
|
||||
public getFutureScrollPosition(): IScrollPosition {
|
||||
if (this._smoothScrolling) {
|
||||
return this._smoothScrolling.to;
|
||||
}
|
||||
return this._state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current scroll position.
|
||||
* Note: This result might be an intermediate scroll position, as there might be an ongoing smooth scroll animation.
|
||||
*/
|
||||
public getCurrentScrollPosition(): IScrollPosition {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public setScrollPositionNow(update: INewScrollPosition): void {
|
||||
// no smooth scrolling requested
|
||||
const newState = this._state.withScrollPosition(update);
|
||||
|
||||
// Terminate any outstanding smooth scrolling
|
||||
if (this._smoothScrolling) {
|
||||
this._smoothScrolling.dispose();
|
||||
this._smoothScrolling = null;
|
||||
}
|
||||
|
||||
this._setState(newState);
|
||||
}
|
||||
|
||||
public setScrollPositionSmooth(update: INewScrollPosition): void {
|
||||
if (this._smoothScrollDuration === 0) {
|
||||
// Smooth scrolling not supported.
|
||||
return this.setScrollPositionNow(update);
|
||||
}
|
||||
|
||||
if (this._smoothScrolling) {
|
||||
// Combine our pending scrollLeft/scrollTop with incoming scrollLeft/scrollTop
|
||||
update = {
|
||||
scrollLeft: (typeof update.scrollLeft === 'undefined' ? this._smoothScrolling.to.scrollLeft : update.scrollLeft),
|
||||
scrollTop: (typeof update.scrollTop === 'undefined' ? this._smoothScrolling.to.scrollTop : update.scrollTop)
|
||||
};
|
||||
|
||||
// Validate `update`
|
||||
const validTarget = this._state.withScrollPosition(update);
|
||||
|
||||
if (this._smoothScrolling.to.scrollLeft === validTarget.scrollLeft && this._smoothScrolling.to.scrollTop === validTarget.scrollTop) {
|
||||
// No need to interrupt or extend the current animation since we're going to the same place
|
||||
return;
|
||||
}
|
||||
|
||||
const newSmoothScrolling = this._smoothScrolling.combine(this._state, validTarget, this._smoothScrollDuration);
|
||||
this._smoothScrolling.dispose();
|
||||
this._smoothScrolling = newSmoothScrolling;
|
||||
} else {
|
||||
// Validate `update`
|
||||
const validTarget = this._state.withScrollPosition(update);
|
||||
|
||||
this._smoothScrolling = SmoothScrollingOperation.start(this._state, validTarget, this._smoothScrollDuration);
|
||||
}
|
||||
|
||||
// Begin smooth scrolling animation
|
||||
this._smoothScrolling.animationFrameDisposable = this._scheduleAtNextAnimationFrame(() => {
|
||||
if (!this._smoothScrolling) {
|
||||
return;
|
||||
}
|
||||
this._smoothScrolling.animationFrameDisposable = null;
|
||||
this._performSmoothScrolling();
|
||||
});
|
||||
}
|
||||
|
||||
private _performSmoothScrolling(): void {
|
||||
const update = this._smoothScrolling.tick();
|
||||
const newState = this._state.withScrollPosition(update);
|
||||
|
||||
this._setState(newState);
|
||||
|
||||
if (update.isDone) {
|
||||
this._smoothScrolling.dispose();
|
||||
this._smoothScrolling = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue smooth scrolling animation
|
||||
this._smoothScrolling.animationFrameDisposable = this._scheduleAtNextAnimationFrame(() => {
|
||||
if (!this._smoothScrolling) {
|
||||
return;
|
||||
}
|
||||
this._smoothScrolling.animationFrameDisposable = null;
|
||||
this._performSmoothScrolling();
|
||||
});
|
||||
}
|
||||
|
||||
private _setState(newState: ScrollState): void {
|
||||
const oldState = this._state;
|
||||
if (oldState.equals(newState)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
this._state = newState;
|
||||
this._onScroll.fire(this._state.createScrollEvent(oldState));
|
||||
}
|
||||
}
|
||||
|
||||
export class SmoothScrollingUpdate {
|
||||
|
||||
public readonly scrollLeft: number;
|
||||
public readonly scrollTop: number;
|
||||
public readonly isDone: boolean;
|
||||
|
||||
constructor(scrollLeft: number, scrollTop: number, isDone: boolean) {
|
||||
this.scrollLeft = scrollLeft;
|
||||
this.scrollTop = scrollTop;
|
||||
this.isDone = isDone;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface IAnimation {
|
||||
(completion: number): number;
|
||||
}
|
||||
|
||||
function createEaseOutCubic(from: number, to: number): IAnimation {
|
||||
const delta = to - from;
|
||||
return function (completion: number): number {
|
||||
return from + delta * easeOutCubic(completion);
|
||||
};
|
||||
}
|
||||
|
||||
function createComposed(a: IAnimation, b: IAnimation, cut: number): IAnimation {
|
||||
return function (completion: number): number {
|
||||
if (completion < cut) {
|
||||
return a(completion / cut);
|
||||
}
|
||||
return b((completion - cut) / (1 - cut));
|
||||
};
|
||||
}
|
||||
|
||||
export class SmoothScrollingOperation {
|
||||
|
||||
public readonly from: ISmoothScrollPosition;
|
||||
public to: ISmoothScrollPosition;
|
||||
public readonly duration: number;
|
||||
private readonly _startTime: number;
|
||||
public animationFrameDisposable: IDisposable;
|
||||
|
||||
private scrollLeft: IAnimation;
|
||||
private scrollTop: IAnimation;
|
||||
|
||||
protected constructor(from: ISmoothScrollPosition, to: ISmoothScrollPosition, startTime: number, duration: number) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.duration = duration;
|
||||
this._startTime = startTime;
|
||||
|
||||
this.animationFrameDisposable = null;
|
||||
|
||||
this._initAnimations();
|
||||
}
|
||||
|
||||
private _initAnimations(): void {
|
||||
this.scrollLeft = this._initAnimation(this.from.scrollLeft, this.to.scrollLeft, this.to.width);
|
||||
this.scrollTop = this._initAnimation(this.from.scrollTop, this.to.scrollTop, this.to.height);
|
||||
}
|
||||
|
||||
private _initAnimation(from: number, to: number, viewportSize: number): IAnimation {
|
||||
const delta = Math.abs(from - to);
|
||||
if (delta > 2.5 * viewportSize) {
|
||||
let stop1: number, stop2: number;
|
||||
if (from < to) {
|
||||
// scroll to 75% of the viewportSize
|
||||
stop1 = from + 0.75 * viewportSize;
|
||||
stop2 = to - 0.75 * viewportSize;
|
||||
} else {
|
||||
stop1 = from - 0.75 * viewportSize;
|
||||
stop2 = to + 0.75 * viewportSize;
|
||||
}
|
||||
return createComposed(createEaseOutCubic(from, stop1), createEaseOutCubic(stop2, to), 0.33);
|
||||
}
|
||||
return createEaseOutCubic(from, to);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.animationFrameDisposable !== null) {
|
||||
this.animationFrameDisposable.dispose();
|
||||
this.animationFrameDisposable = null;
|
||||
}
|
||||
}
|
||||
|
||||
public acceptScrollDimensions(state: ScrollState): void {
|
||||
this.to = state.withScrollPosition(this.to);
|
||||
this._initAnimations();
|
||||
}
|
||||
|
||||
public tick(): SmoothScrollingUpdate {
|
||||
return this._tick(Date.now());
|
||||
}
|
||||
|
||||
protected _tick(now: number): SmoothScrollingUpdate {
|
||||
const completion = (now - this._startTime) / this.duration;
|
||||
|
||||
if (completion < 1) {
|
||||
const newScrollLeft = this.scrollLeft(completion);
|
||||
const newScrollTop = this.scrollTop(completion);
|
||||
return new SmoothScrollingUpdate(newScrollLeft, newScrollTop, false);
|
||||
}
|
||||
|
||||
return new SmoothScrollingUpdate(this.to.scrollLeft, this.to.scrollTop, true);
|
||||
}
|
||||
|
||||
public combine(from: ISmoothScrollPosition, to: ISmoothScrollPosition, duration: number): SmoothScrollingOperation {
|
||||
return SmoothScrollingOperation.start(from, to, duration);
|
||||
}
|
||||
|
||||
public static start(from: ISmoothScrollPosition, to: ISmoothScrollPosition, duration: number): SmoothScrollingOperation {
|
||||
// +10 / -10 : pretend the animation already started for a quicker response to a scroll request
|
||||
duration = duration + 10;
|
||||
const startTime = Date.now() - 10;
|
||||
|
||||
return new SmoothScrollingOperation(from, to, startTime, duration);
|
||||
}
|
||||
}
|
||||
|
||||
function easeInCubic(t: number) {
|
||||
return Math.pow(t, 3);
|
||||
}
|
||||
|
||||
function easeOutCubic(t: number) {
|
||||
return 1 - easeInCubic(1 - t);
|
||||
}
|
||||
62
src/vs/base/common/severity.ts
Normal file
62
src/vs/base/common/severity.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import strings = require('vs/base/common/strings');
|
||||
|
||||
enum Severity {
|
||||
Ignore = 0,
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3
|
||||
}
|
||||
|
||||
namespace Severity {
|
||||
|
||||
var _error = 'error',
|
||||
_warning = 'warning',
|
||||
_warn = 'warn',
|
||||
_info = 'info';
|
||||
|
||||
var _displayStrings: { [value: number]: string; } = Object.create(null);
|
||||
_displayStrings[Severity.Error] = nls.localize('sev.error', "Error");
|
||||
_displayStrings[Severity.Warning] = nls.localize('sev.warning', "Warning");
|
||||
_displayStrings[Severity.Info] = nls.localize('sev.info', "Info");
|
||||
|
||||
/**
|
||||
* Parses 'error', 'warning', 'warn', 'info' in call casings
|
||||
* and falls back to ignore.
|
||||
*/
|
||||
export function fromValue(value: string): Severity {
|
||||
if (!value) {
|
||||
return Severity.Ignore;
|
||||
}
|
||||
|
||||
if (strings.equalsIgnoreCase(_error, value)) {
|
||||
return Severity.Error;
|
||||
}
|
||||
|
||||
if (strings.equalsIgnoreCase(_warning, value) || strings.equalsIgnoreCase(_warn, value)) {
|
||||
return Severity.Warning;
|
||||
}
|
||||
|
||||
if (strings.equalsIgnoreCase(_info, value)) {
|
||||
return Severity.Info;
|
||||
}
|
||||
|
||||
return Severity.Ignore;
|
||||
}
|
||||
|
||||
export function toString(value: Severity): string {
|
||||
return _displayStrings[value] || strings.empty;
|
||||
}
|
||||
|
||||
export function compare(a: Severity, b: Severity): number {
|
||||
return b - a;
|
||||
}
|
||||
}
|
||||
|
||||
export default Severity;
|
||||
41
src/vs/base/common/stopwatch.ts
Normal file
41
src/vs/base/common/stopwatch.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { globals } from 'vs/base/common/platform';
|
||||
|
||||
const hasPerformanceNow = (globals.performance && typeof globals.performance.now === 'function');
|
||||
|
||||
export class StopWatch {
|
||||
|
||||
private _highResolution: boolean;
|
||||
private _startTime: number;
|
||||
private _stopTime: number;
|
||||
|
||||
public static create(highResolution: boolean = true): StopWatch {
|
||||
return new StopWatch(highResolution);
|
||||
}
|
||||
|
||||
constructor(highResolution: boolean) {
|
||||
this._highResolution = hasPerformanceNow && highResolution;
|
||||
this._startTime = this._now();
|
||||
this._stopTime = -1;
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
this._stopTime = this._now();
|
||||
}
|
||||
|
||||
public elapsed(): number {
|
||||
if (this._stopTime !== -1) {
|
||||
return this._stopTime - this._startTime;
|
||||
}
|
||||
return this._now() - this._startTime;
|
||||
}
|
||||
|
||||
private _now(): number {
|
||||
return this._highResolution ? globals.performance.now() : new Date().getTime();
|
||||
}
|
||||
}
|
||||
737
src/vs/base/common/strings.ts
Normal file
737
src/vs/base/common/strings.ts
Normal file
@@ -0,0 +1,737 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { BoundedMap } from 'vs/base/common/map';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
/**
|
||||
* The empty string.
|
||||
*/
|
||||
export const empty = '';
|
||||
|
||||
export function isFalsyOrWhitespace(str: string): boolean {
|
||||
if (!str || typeof str !== 'string') {
|
||||
return true;
|
||||
}
|
||||
return str.trim().length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the provided number with the given number of preceding zeros.
|
||||
*/
|
||||
export function pad(n: number, l: number, char: string = '0'): string {
|
||||
let str = '' + n;
|
||||
let r = [str];
|
||||
|
||||
for (let i = str.length; i < l; i++) {
|
||||
r.push(char);
|
||||
}
|
||||
|
||||
return r.reverse().join('');
|
||||
}
|
||||
|
||||
const _formatRegexp = /{(\d+)}/g;
|
||||
|
||||
/**
|
||||
* Helper to produce a string with a variable number of arguments. Insert variable segments
|
||||
* into the string using the {n} notation where N is the index of the argument following the string.
|
||||
* @param value string to which formatting is applied
|
||||
* @param args replacements for {n}-entries
|
||||
*/
|
||||
export function format(value: string, ...args: any[]): string {
|
||||
if (args.length === 0) {
|
||||
return value;
|
||||
}
|
||||
return value.replace(_formatRegexp, function (match, group) {
|
||||
let idx = parseInt(group, 10);
|
||||
return isNaN(idx) || idx < 0 || idx >= args.length ?
|
||||
match :
|
||||
args[idx];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts HTML characters inside the string to use entities instead. Makes the string safe from
|
||||
* being used e.g. in HTMLElement.innerHTML.
|
||||
*/
|
||||
export function escape(html: string): string {
|
||||
return html.replace(/[<|>|&]/g, function (match) {
|
||||
switch (match) {
|
||||
case '<': return '<';
|
||||
case '>': return '>';
|
||||
case '&': return '&';
|
||||
default: return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes regular expression characters in a given string
|
||||
*/
|
||||
export function escapeRegExpCharacters(value: string): string {
|
||||
return value.replace(/[\-\\\{\}\*\+\?\|\^\$\.\[\]\(\)\#]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all occurrences of needle from the beginning and end of haystack.
|
||||
* @param haystack string to trim
|
||||
* @param needle the thing to trim (default is a blank)
|
||||
*/
|
||||
export function trim(haystack: string, needle: string = ' '): string {
|
||||
let trimmed = ltrim(haystack, needle);
|
||||
return rtrim(trimmed, needle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all occurrences of needle from the beginning of haystack.
|
||||
* @param haystack string to trim
|
||||
* @param needle the thing to trim
|
||||
*/
|
||||
export function ltrim(haystack?: string, needle?: string): string {
|
||||
if (!haystack || !needle) {
|
||||
return haystack;
|
||||
}
|
||||
|
||||
let needleLen = needle.length;
|
||||
if (needleLen === 0 || haystack.length === 0) {
|
||||
return haystack;
|
||||
}
|
||||
|
||||
let offset = 0,
|
||||
idx = -1;
|
||||
|
||||
while ((idx = haystack.indexOf(needle, offset)) === offset) {
|
||||
offset = offset + needleLen;
|
||||
}
|
||||
return haystack.substring(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all occurrences of needle from the end of haystack.
|
||||
* @param haystack string to trim
|
||||
* @param needle the thing to trim
|
||||
*/
|
||||
export function rtrim(haystack?: string, needle?: string): string {
|
||||
if (!haystack || !needle) {
|
||||
return haystack;
|
||||
}
|
||||
|
||||
let needleLen = needle.length,
|
||||
haystackLen = haystack.length;
|
||||
|
||||
if (needleLen === 0 || haystackLen === 0) {
|
||||
return haystack;
|
||||
}
|
||||
|
||||
let offset = haystackLen,
|
||||
idx = -1;
|
||||
|
||||
while (true) {
|
||||
idx = haystack.lastIndexOf(needle, offset - 1);
|
||||
if (idx === -1 || idx + needleLen !== offset) {
|
||||
break;
|
||||
}
|
||||
if (idx === 0) {
|
||||
return '';
|
||||
}
|
||||
offset = idx;
|
||||
}
|
||||
|
||||
return haystack.substring(0, offset);
|
||||
}
|
||||
|
||||
export function convertSimple2RegExpPattern(pattern: string): string {
|
||||
return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*');
|
||||
}
|
||||
|
||||
export function stripWildcards(pattern: string): string {
|
||||
return pattern.replace(/\*/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if haystack starts with needle.
|
||||
*/
|
||||
export function startsWith(haystack: string, needle: string): boolean {
|
||||
if (haystack.length < needle.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < needle.length; i++) {
|
||||
if (haystack[i] !== needle[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if haystack ends with needle.
|
||||
*/
|
||||
export function endsWith(haystack: string, needle: string): boolean {
|
||||
let diff = haystack.length - needle.length;
|
||||
if (diff > 0) {
|
||||
return haystack.indexOf(needle, diff) === diff;
|
||||
} else if (diff === 0) {
|
||||
return haystack === needle;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface RegExpOptions {
|
||||
matchCase?: boolean;
|
||||
wholeWord?: boolean;
|
||||
multiline?: boolean;
|
||||
global?: boolean;
|
||||
}
|
||||
|
||||
export function createRegExp(searchString: string, isRegex: boolean, options: RegExpOptions = {}): RegExp {
|
||||
if (!searchString) {
|
||||
throw new Error('Cannot create regex from empty string');
|
||||
}
|
||||
if (!isRegex) {
|
||||
searchString = escapeRegExpCharacters(searchString);
|
||||
}
|
||||
if (options.wholeWord) {
|
||||
if (!/\B/.test(searchString.charAt(0))) {
|
||||
searchString = '\\b' + searchString;
|
||||
}
|
||||
if (!/\B/.test(searchString.charAt(searchString.length - 1))) {
|
||||
searchString = searchString + '\\b';
|
||||
}
|
||||
}
|
||||
let modifiers = '';
|
||||
if (options.global) {
|
||||
modifiers += 'g';
|
||||
}
|
||||
if (!options.matchCase) {
|
||||
modifiers += 'i';
|
||||
}
|
||||
if (options.multiline) {
|
||||
modifiers += 'm';
|
||||
}
|
||||
|
||||
return new RegExp(searchString, modifiers);
|
||||
}
|
||||
|
||||
export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean {
|
||||
// Exit early if it's one of these special cases which are meant to match
|
||||
// against an empty string
|
||||
if (regexp.source === '^' || regexp.source === '^$' || regexp.source === '$') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We check against an empty string. If the regular expression doesn't advance
|
||||
// (e.g. ends in an endless loop) it will match an empty string.
|
||||
let match = regexp.exec('');
|
||||
return (match && <any>regexp.lastIndex === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The normalize() method returns the Unicode Normalization Form of a given string. The form will be
|
||||
* the Normalization Form Canonical Composition.
|
||||
*
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize}
|
||||
*/
|
||||
export const canNormalize = typeof ((<any>'').normalize) === 'function';
|
||||
const nonAsciiCharactersPattern = /[^\u0000-\u0080]/;
|
||||
const normalizedCache = new BoundedMap<string>(10000); // bounded to 10000 elements
|
||||
export function normalizeNFC(str: string): string {
|
||||
if (!canNormalize || !str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
const cached = normalizedCache.get(str);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
let res: string;
|
||||
if (nonAsciiCharactersPattern.test(str)) {
|
||||
res = (<any>str).normalize('NFC');
|
||||
} else {
|
||||
res = str;
|
||||
}
|
||||
|
||||
// Use the cache for fast lookup
|
||||
normalizedCache.set(str, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first index of the string that is not whitespace.
|
||||
* If string is empty or contains only whitespaces, returns -1
|
||||
*/
|
||||
export function firstNonWhitespaceIndex(str: string): number {
|
||||
for (let i = 0, len = str.length; i < len; i++) {
|
||||
let chCode = str.charCodeAt(i);
|
||||
if (chCode !== CharCode.Space && chCode !== CharCode.Tab) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the leading whitespace of the string.
|
||||
* If the string contains only whitespaces, returns entire string
|
||||
*/
|
||||
export function getLeadingWhitespace(str: string, start: number = 0, end: number = str.length): string {
|
||||
for (let i = start; i < end; i++) {
|
||||
let chCode = str.charCodeAt(i);
|
||||
if (chCode !== CharCode.Space && chCode !== CharCode.Tab) {
|
||||
return str.substring(start, i);
|
||||
}
|
||||
}
|
||||
return str.substring(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last index of the string that is not whitespace.
|
||||
* If string is empty or contains only whitespaces, returns -1
|
||||
*/
|
||||
export function lastNonWhitespaceIndex(str: string, startIndex: number = str.length - 1): number {
|
||||
for (let i = startIndex; i >= 0; i--) {
|
||||
let chCode = str.charCodeAt(i);
|
||||
if (chCode !== CharCode.Space && chCode !== CharCode.Tab) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function compare(a: string, b: string): number {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a > b) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function compareIgnoreCase(a: string, b: string): number {
|
||||
const len = Math.min(a.length, b.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
let codeA = a.charCodeAt(i);
|
||||
let codeB = b.charCodeAt(i);
|
||||
|
||||
if (codeA === codeB) {
|
||||
// equal
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isUpperAsciiLetter(codeA)) {
|
||||
codeA += 32;
|
||||
}
|
||||
|
||||
if (isUpperAsciiLetter(codeB)) {
|
||||
codeB += 32;
|
||||
}
|
||||
|
||||
const diff = codeA - codeB;
|
||||
|
||||
if (diff === 0) {
|
||||
// equal -> ignoreCase
|
||||
continue;
|
||||
|
||||
} else if (isLowerAsciiLetter(codeA) && isLowerAsciiLetter(codeB)) {
|
||||
//
|
||||
return diff;
|
||||
|
||||
} else {
|
||||
return compare(a.toLowerCase(), b.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
if (a.length < b.length) {
|
||||
return -1;
|
||||
} else if (a.length > b.length) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function isLowerAsciiLetter(code: number): boolean {
|
||||
return code >= CharCode.a && code <= CharCode.z;
|
||||
}
|
||||
|
||||
function isUpperAsciiLetter(code: number): boolean {
|
||||
return code >= CharCode.A && code <= CharCode.Z;
|
||||
}
|
||||
|
||||
function isAsciiLetter(code: number): boolean {
|
||||
return isLowerAsciiLetter(code) || isUpperAsciiLetter(code);
|
||||
}
|
||||
|
||||
export function equalsIgnoreCase(a: string, b: string): boolean {
|
||||
const len1 = a ? a.length : 0;
|
||||
const len2 = b ? b.length : 0;
|
||||
|
||||
if (len1 !== len2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return doEqualsIgnoreCase(a, b);
|
||||
}
|
||||
|
||||
function doEqualsIgnoreCase(a: string, b: string, stopAt = a.length): boolean {
|
||||
if (typeof a !== 'string' || typeof b !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < stopAt; i++) {
|
||||
const codeA = a.charCodeAt(i);
|
||||
const codeB = b.charCodeAt(i);
|
||||
|
||||
if (codeA === codeB) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// a-z A-Z
|
||||
if (isAsciiLetter(codeA) && isAsciiLetter(codeB)) {
|
||||
let diff = Math.abs(codeA - codeB);
|
||||
if (diff !== 0 && diff !== 32) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Any other charcode
|
||||
else {
|
||||
if (String.fromCharCode(codeA).toLowerCase() !== String.fromCharCode(codeB).toLowerCase()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function beginsWithIgnoreCase(str: string, candidate: string): boolean {
|
||||
const candidateLength = candidate.length;
|
||||
if (candidate.length > str.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return doEqualsIgnoreCase(str, candidate, candidateLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the length of the common prefix of the two strings.
|
||||
*/
|
||||
export function commonPrefixLength(a: string, b: string): number {
|
||||
|
||||
let i: number,
|
||||
len = Math.min(a.length, b.length);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (a.charCodeAt(i) !== b.charCodeAt(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the length of the common suffix of the two strings.
|
||||
*/
|
||||
export function commonSuffixLength(a: string, b: string): number {
|
||||
|
||||
let i: number,
|
||||
len = Math.min(a.length, b.length);
|
||||
|
||||
let aLastIndex = a.length - 1;
|
||||
let bLastIndex = b.length - 1;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (a.charCodeAt(aLastIndex - i) !== b.charCodeAt(bLastIndex - i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
function substrEquals(a: string, aStart: number, aEnd: number, b: string, bStart: number, bEnd: number): boolean {
|
||||
while (aStart < aEnd && bStart < bEnd) {
|
||||
if (a[aStart] !== b[bStart]) {
|
||||
return false;
|
||||
}
|
||||
aStart += 1;
|
||||
bStart += 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the overlap between the suffix of `a` and the prefix of `b`.
|
||||
* For instance `overlap("foobar", "arr, I'm a pirate") === 2`.
|
||||
*/
|
||||
export function overlap(a: string, b: string): number {
|
||||
let aEnd = a.length;
|
||||
let bEnd = b.length;
|
||||
let aStart = aEnd - bEnd;
|
||||
|
||||
if (aStart === 0) {
|
||||
return a === b ? aEnd : 0;
|
||||
} else if (aStart < 0) {
|
||||
bEnd += aStart;
|
||||
aStart = 0;
|
||||
}
|
||||
|
||||
while (aStart < aEnd && bEnd > 0) {
|
||||
if (substrEquals(a, aStart, aEnd, b, 0, bEnd)) {
|
||||
return bEnd;
|
||||
}
|
||||
bEnd -= 1;
|
||||
aStart += 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- unicode
|
||||
// http://en.wikipedia.org/wiki/Surrogate_pair
|
||||
// Returns the code point starting at a specified index in a string
|
||||
// Code points U+0000 to U+D7FF and U+E000 to U+FFFF are represented on a single character
|
||||
// Code points U+10000 to U+10FFFF are represented on two consecutive characters
|
||||
//export function getUnicodePoint(str:string, index:number, len:number):number {
|
||||
// let chrCode = str.charCodeAt(index);
|
||||
// if (0xD800 <= chrCode && chrCode <= 0xDBFF && index + 1 < len) {
|
||||
// let nextChrCode = str.charCodeAt(index + 1);
|
||||
// if (0xDC00 <= nextChrCode && nextChrCode <= 0xDFFF) {
|
||||
// return (chrCode - 0xD800) << 10 + (nextChrCode - 0xDC00) + 0x10000;
|
||||
// }
|
||||
// }
|
||||
// return chrCode;
|
||||
//}
|
||||
export function isHighSurrogate(charCode: number): boolean {
|
||||
return (0xD800 <= charCode && charCode <= 0xDBFF);
|
||||
}
|
||||
|
||||
export function isLowSurrogate(charCode: number): boolean {
|
||||
return (0xDC00 <= charCode && charCode <= 0xDFFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-rtl-test.js
|
||||
*/
|
||||
const CONTAINS_RTL = /(?:[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u0710\u0712-\u072F\u074D-\u07A5\u07B1-\u07EA\u07F4\u07F5\u07FA-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u08BD\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE33\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDCFF]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD50-\uDFFF]|\uD83B[\uDC00-\uDEBB])/;
|
||||
|
||||
/**
|
||||
* Returns true if `str` contains any Unicode character that is classified as "R" or "AL".
|
||||
*/
|
||||
export function containsRTL(str: string): boolean {
|
||||
return CONTAINS_RTL.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-emoji-test.js
|
||||
*/
|
||||
const CONTAINS_EMOJI = /(?:[\u231A\u231B\u23F0\u23F3\u2600-\u27BF\u2B50\u2B55]|\uD83C[\uDDE6-\uDDFF\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F\uDE80-\uDEF8]|\uD83E[\uDD00-\uDDE6])/;
|
||||
|
||||
export function containsEmoji(str: string): boolean {
|
||||
return CONTAINS_EMOJI.test(str);
|
||||
}
|
||||
|
||||
const IS_BASIC_ASCII = /^[\t\n\r\x20-\x7E]*$/;
|
||||
/**
|
||||
* Returns true if `str` contains only basic ASCII characters in the range 32 - 126 (including 32 and 126) or \n, \r, \t
|
||||
*/
|
||||
export function isBasicASCII(str: string): boolean {
|
||||
return IS_BASIC_ASCII.test(str);
|
||||
}
|
||||
|
||||
export function containsFullWidthCharacter(str: string): boolean {
|
||||
for (let i = 0, len = str.length; i < len; i++) {
|
||||
if (isFullWidthCharacter(str.charCodeAt(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isFullWidthCharacter(charCode: number): boolean {
|
||||
// Do a cheap trick to better support wrapping of wide characters, treat them as 2 columns
|
||||
// http://jrgraphix.net/research/unicode_blocks.php
|
||||
// 2E80 — 2EFF CJK Radicals Supplement
|
||||
// 2F00 — 2FDF Kangxi Radicals
|
||||
// 2FF0 — 2FFF Ideographic Description Characters
|
||||
// 3000 — 303F CJK Symbols and Punctuation
|
||||
// 3040 — 309F Hiragana
|
||||
// 30A0 — 30FF Katakana
|
||||
// 3100 — 312F Bopomofo
|
||||
// 3130 — 318F Hangul Compatibility Jamo
|
||||
// 3190 — 319F Kanbun
|
||||
// 31A0 — 31BF Bopomofo Extended
|
||||
// 31F0 — 31FF Katakana Phonetic Extensions
|
||||
// 3200 — 32FF Enclosed CJK Letters and Months
|
||||
// 3300 — 33FF CJK Compatibility
|
||||
// 3400 — 4DBF CJK Unified Ideographs Extension A
|
||||
// 4DC0 — 4DFF Yijing Hexagram Symbols
|
||||
// 4E00 — 9FFF CJK Unified Ideographs
|
||||
// A000 — A48F Yi Syllables
|
||||
// A490 — A4CF Yi Radicals
|
||||
// AC00 — D7AF Hangul Syllables
|
||||
// [IGNORE] D800 — DB7F High Surrogates
|
||||
// [IGNORE] DB80 — DBFF High Private Use Surrogates
|
||||
// [IGNORE] DC00 — DFFF Low Surrogates
|
||||
// [IGNORE] E000 — F8FF Private Use Area
|
||||
// F900 — FAFF CJK Compatibility Ideographs
|
||||
// [IGNORE] FB00 — FB4F Alphabetic Presentation Forms
|
||||
// [IGNORE] FB50 — FDFF Arabic Presentation Forms-A
|
||||
// [IGNORE] FE00 — FE0F Variation Selectors
|
||||
// [IGNORE] FE20 — FE2F Combining Half Marks
|
||||
// [IGNORE] FE30 — FE4F CJK Compatibility Forms
|
||||
// [IGNORE] FE50 — FE6F Small Form Variants
|
||||
// [IGNORE] FE70 — FEFF Arabic Presentation Forms-B
|
||||
// FF00 — FFEF Halfwidth and Fullwidth Forms
|
||||
// [https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms]
|
||||
// of which FF01 - FF5E fullwidth ASCII of 21 to 7E
|
||||
// [IGNORE] and FF65 - FFDC halfwidth of Katakana and Hangul
|
||||
// [IGNORE] FFF0 — FFFF Specials
|
||||
charCode = +charCode; // @perf
|
||||
return (
|
||||
(charCode >= 0x2E80 && charCode <= 0xD7AF)
|
||||
|| (charCode >= 0xF900 && charCode <= 0xFAFF)
|
||||
|| (charCode >= 0xFF01 && charCode <= 0xFF5E)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference score for two strings. More similar strings have a higher score.
|
||||
* We use largest common subsequence dynamic programming approach but penalize in the end for length differences.
|
||||
* Strings that have a large length difference will get a bad default score 0.
|
||||
* Complexity - both time and space O(first.length * second.length)
|
||||
* Dynamic programming LCS computation http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
|
||||
*
|
||||
* @param first a string
|
||||
* @param second a string
|
||||
*/
|
||||
export function difference(first: string, second: string, maxLenDelta: number = 4): number {
|
||||
let lengthDifference = Math.abs(first.length - second.length);
|
||||
// We only compute score if length of the currentWord and length of entry.name are similar.
|
||||
if (lengthDifference > maxLenDelta) {
|
||||
return 0;
|
||||
}
|
||||
// Initialize LCS (largest common subsequence) matrix.
|
||||
let LCS: number[][] = [];
|
||||
let zeroArray: number[] = [];
|
||||
let i: number, j: number;
|
||||
for (i = 0; i < second.length + 1; ++i) {
|
||||
zeroArray.push(0);
|
||||
}
|
||||
for (i = 0; i < first.length + 1; ++i) {
|
||||
LCS.push(zeroArray);
|
||||
}
|
||||
for (i = 1; i < first.length + 1; ++i) {
|
||||
for (j = 1; j < second.length + 1; ++j) {
|
||||
if (first[i - 1] === second[j - 1]) {
|
||||
LCS[i][j] = LCS[i - 1][j - 1] + 1;
|
||||
} else {
|
||||
LCS[i][j] = Math.max(LCS[i - 1][j], LCS[i][j - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return LCS[first.length][second.length] - Math.sqrt(lengthDifference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array in which every entry is the offset of a
|
||||
* line. There is always one entry which is zero.
|
||||
*/
|
||||
export function computeLineStarts(text: string): number[] {
|
||||
let regexp = /\r\n|\r|\n/g,
|
||||
ret: number[] = [0],
|
||||
match: RegExpExecArray;
|
||||
while ((match = regexp.exec(text))) {
|
||||
ret.push(regexp.lastIndex);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string and a max length returns a shorted version. Shorting
|
||||
* happens at favorable positions - such as whitespace or punctuation characters.
|
||||
*/
|
||||
export function lcut(text: string, n: number): string {
|
||||
|
||||
if (text.length < n) {
|
||||
return text;
|
||||
}
|
||||
|
||||
let segments = text.split(/\b/),
|
||||
count = 0;
|
||||
|
||||
for (let i = segments.length - 1; i >= 0; i--) {
|
||||
count += segments[i].length;
|
||||
|
||||
if (count > n) {
|
||||
segments.splice(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return segments.join(empty).replace(/^\s/, empty);
|
||||
}
|
||||
|
||||
// Escape codes
|
||||
// http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
const EL = /\x1B\x5B[12]?K/g; // Erase in line
|
||||
const COLOR_START = /\x1b\[\d+m/g; // Color
|
||||
const COLOR_END = /\x1b\[0?m/g; // Color
|
||||
|
||||
export function removeAnsiEscapeCodes(str: string): string {
|
||||
if (str) {
|
||||
str = str.replace(EL, '');
|
||||
str = str.replace(COLOR_START, '');
|
||||
str = str.replace(COLOR_END, '');
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
// -- UTF-8 BOM
|
||||
|
||||
export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM);
|
||||
|
||||
export function startsWithUTF8BOM(str: string): boolean {
|
||||
return (str && str.length > 0 && str.charCodeAt(0) === CharCode.UTF8_BOM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends two strings. If the appended result is longer than maxLength,
|
||||
* trims the start of the result and replaces it with '...'.
|
||||
*/
|
||||
export function appendWithLimit(first: string, second: string, maxLength: number): string {
|
||||
const newLength = first.length + second.length;
|
||||
if (newLength > maxLength) {
|
||||
first = '...' + first.substr(newLength - maxLength);
|
||||
}
|
||||
if (second.length > maxLength) {
|
||||
first += second.substr(second.length - maxLength);
|
||||
} else {
|
||||
first += second;
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
|
||||
export function safeBtoa(str: string): string {
|
||||
return btoa(encodeURIComponent(str)); // we use encodeURIComponent because btoa fails for non Latin 1 values
|
||||
}
|
||||
|
||||
export function repeat(s: string, count: number): string {
|
||||
let result = '';
|
||||
for (let i = 0; i < count; i++) {
|
||||
result += s;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
220
src/vs/base/common/types.ts
Normal file
220
src/vs/base/common/types.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
const _typeof = {
|
||||
number: 'number',
|
||||
string: 'string',
|
||||
undefined: 'undefined',
|
||||
object: 'object',
|
||||
function: 'function'
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is a JavaScript Array or not.
|
||||
*/
|
||||
export function isArray(array: any): array is any[] {
|
||||
if (Array.isArray) {
|
||||
return Array.isArray(array);
|
||||
}
|
||||
|
||||
if (array && typeof (array.length) === _typeof.number && array.constructor === Array) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is a JavaScript String or not.
|
||||
*/
|
||||
export function isString(str: any): str is string {
|
||||
if (typeof (str) === _typeof.string || str instanceof String) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is a JavaScript Array and each element in the array is a string.
|
||||
*/
|
||||
export function isStringArray(value: any): value is string[] {
|
||||
return isArray(value) && (<any[]>value).every(elem => isString(elem));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns whether the provided parameter is of type `object` but **not**
|
||||
* `null`, an `array`, a `regexp`, nor a `date`.
|
||||
*/
|
||||
export function isObject(obj: any): boolean {
|
||||
// The method can't do a type cast since there are type (like strings) which
|
||||
// are subclasses of any put not positvely matched by the function. Hence type
|
||||
// narrowing results in wrong results.
|
||||
return typeof obj === _typeof.object
|
||||
&& obj !== null
|
||||
&& !Array.isArray(obj)
|
||||
&& !(obj instanceof RegExp)
|
||||
&& !(obj instanceof Date);
|
||||
}
|
||||
|
||||
/**
|
||||
* In **contrast** to just checking `typeof` this will return `false` for `NaN`.
|
||||
* @returns whether the provided parameter is a JavaScript Number or not.
|
||||
*/
|
||||
export function isNumber(obj: any): obj is number {
|
||||
if ((typeof (obj) === _typeof.number || obj instanceof Number) && !isNaN(obj)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is a JavaScript Boolean or not.
|
||||
*/
|
||||
export function isBoolean(obj: any): obj is boolean {
|
||||
return obj === true || obj === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is undefined.
|
||||
*/
|
||||
export function isUndefined(obj: any): boolean {
|
||||
return typeof (obj) === _typeof.undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is undefined or null.
|
||||
*/
|
||||
export function isUndefinedOrNull(obj: any): boolean {
|
||||
return isUndefined(obj) || obj === null;
|
||||
}
|
||||
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is an empty JavaScript Object or not.
|
||||
*/
|
||||
export function isEmptyObject(obj: any): obj is any {
|
||||
if (!isObject(obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let key in obj) {
|
||||
if (hasOwnProperty.call(obj, key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is a JavaScript Function or not.
|
||||
*/
|
||||
export function isFunction(obj: any): obj is Function {
|
||||
return typeof obj === _typeof.function;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameters is are JavaScript Function or not.
|
||||
*/
|
||||
export function areFunctions(...objects: any[]): boolean {
|
||||
return objects && objects.length > 0 && objects.every(isFunction);
|
||||
}
|
||||
|
||||
export type TypeConstraint = string | Function;
|
||||
|
||||
export function validateConstraints(args: any[], constraints: TypeConstraint[]): void {
|
||||
const len = Math.min(args.length, constraints.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
validateConstraint(args[i], constraints[i]);
|
||||
}
|
||||
}
|
||||
|
||||
export function validateConstraint(arg: any, constraint: TypeConstraint): void {
|
||||
|
||||
if (isString(constraint)) {
|
||||
if (typeof arg !== constraint) {
|
||||
throw new Error(`argument does not match constraint: typeof ${constraint}`);
|
||||
}
|
||||
} else if (isFunction(constraint)) {
|
||||
if (arg instanceof constraint) {
|
||||
return;
|
||||
}
|
||||
if (arg && arg.constructor === constraint) {
|
||||
return;
|
||||
}
|
||||
if (constraint.length === 1 && constraint.call(undefined, arg) === true) {
|
||||
return;
|
||||
}
|
||||
throw new Error(`argument does not match one of these constraints: arg instanceof constraint, arg.constructor === constraint, nor constraint(arg) === true`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new object of the provided class and will call the constructor with
|
||||
* any additional argument supplied.
|
||||
*/
|
||||
export function create(ctor: Function, ...args: any[]): any {
|
||||
let obj = Object.create(ctor.prototype);
|
||||
ctor.apply(obj, args);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
export interface IFunction0<T> {
|
||||
(): T;
|
||||
}
|
||||
export interface IFunction1<A1, T> {
|
||||
(a1: A1): T;
|
||||
}
|
||||
export interface IFunction2<A1, A2, T> {
|
||||
(a1: A1, a2: A2): T;
|
||||
}
|
||||
export interface IFunction3<A1, A2, A3, T> {
|
||||
(a1: A1, a2: A2, a3: A3): T;
|
||||
}
|
||||
export interface IFunction4<A1, A2, A3, A4, T> {
|
||||
(a1: A1, a2: A2, a3: A3, a4: A4): T;
|
||||
}
|
||||
export interface IFunction5<A1, A2, A3, A4, A5, T> {
|
||||
(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5): T;
|
||||
}
|
||||
export interface IFunction6<A1, A2, A3, A4, A5, A6, T> {
|
||||
(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6): T;
|
||||
}
|
||||
export interface IFunction7<A1, A2, A3, A4, A5, A6, A7, T> {
|
||||
(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7): T;
|
||||
}
|
||||
export interface IFunction8<A1, A2, A3, A4, A5, A6, A7, A8, T> {
|
||||
(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): T;
|
||||
}
|
||||
|
||||
export interface IAction0 extends IFunction0<void> { }
|
||||
export interface IAction1<A1> extends IFunction1<A1, void> { }
|
||||
export interface IAction2<A1, A2> extends IFunction2<A1, A2, void> { }
|
||||
export interface IAction3<A1, A2, A3> extends IFunction3<A1, A2, A3, void> { }
|
||||
export interface IAction4<A1, A2, A3, A4> extends IFunction4<A1, A2, A3, A4, void> { }
|
||||
export interface IAction5<A1, A2, A3, A4, A5> extends IFunction5<A1, A2, A3, A4, A5, void> { }
|
||||
export interface IAction6<A1, A2, A3, A4, A5, A6> extends IFunction6<A1, A2, A3, A4, A5, A6, void> { }
|
||||
export interface IAction7<A1, A2, A3, A4, A5, A6, A7> extends IFunction7<A1, A2, A3, A4, A5, A6, A7, void> { }
|
||||
export interface IAction8<A1, A2, A3, A4, A5, A6, A7, A8> extends IFunction8<A1, A2, A3, A4, A5, A6, A7, A8, void> { }
|
||||
|
||||
export interface IAsyncFunction0<T> extends IFunction0<TPromise<T>> { }
|
||||
export interface IAsyncFunction1<A1, T> extends IFunction1<A1, TPromise<T>> { }
|
||||
export interface IAsyncFunction2<A1, A2, T> extends IFunction2<A1, A2, TPromise<T>> { }
|
||||
export interface IAsyncFunction3<A1, A2, A3, T> extends IFunction3<A1, A2, A3, TPromise<T>> { }
|
||||
export interface IAsyncFunction4<A1, A2, A3, A4, T> extends IFunction4<A1, A2, A3, A4, TPromise<T>> { }
|
||||
export interface IAsyncFunction5<A1, A2, A3, A4, A5, T> extends IFunction5<A1, A2, A3, A4, A5, TPromise<T>> { }
|
||||
export interface IAsyncFunction6<A1, A2, A3, A4, A5, A6, T> extends IFunction6<A1, A2, A3, A4, A5, A6, TPromise<T>> { }
|
||||
export interface IAsyncFunction7<A1, A2, A3, A4, A5, A6, A7, T> extends IFunction7<A1, A2, A3, A4, A5, A6, A7, TPromise<T>> { }
|
||||
export interface IAsyncFunction8<A1, A2, A3, A4, A5, A6, A7, A8, T> extends IFunction8<A1, A2, A3, A4, A5, A6, A7, A8, TPromise<T>> { }
|
||||
|
||||
404
src/vs/base/common/uri.ts
Normal file
404
src/vs/base/common/uri.ts
Normal file
@@ -0,0 +1,404 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
||||
|
||||
function _encode(ch: string): string {
|
||||
return '%' + ch.charCodeAt(0).toString(16).toUpperCase();
|
||||
}
|
||||
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
||||
function encodeURIComponent2(str: string): string {
|
||||
return encodeURIComponent(str).replace(/[!'()*]/g, _encode);
|
||||
}
|
||||
|
||||
function encodeNoop(str: string): string {
|
||||
return str.replace(/[#?]/, _encode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
|
||||
* This class is a simple parser which creates the basic component paths
|
||||
* (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
|
||||
* and encoding.
|
||||
*
|
||||
* foo://example.com:8042/over/there?name=ferret#nose
|
||||
* \_/ \______________/\_________/ \_________/ \__/
|
||||
* | | | | |
|
||||
* scheme authority path query fragment
|
||||
* | _____________________|__
|
||||
* / \ / \
|
||||
* urn:example:animal:ferret:nose
|
||||
*
|
||||
*
|
||||
*/
|
||||
export default class URI {
|
||||
|
||||
static isUri(thing: any): thing is URI {
|
||||
if (thing instanceof URI) {
|
||||
return true;
|
||||
}
|
||||
if (!thing) {
|
||||
return false;
|
||||
}
|
||||
return typeof (<URI>thing).authority === 'string'
|
||||
&& typeof (<URI>thing).fragment === 'string'
|
||||
&& typeof (<URI>thing).path === 'string'
|
||||
&& typeof (<URI>thing).query === 'string'
|
||||
&& typeof (<URI>thing).scheme === 'string';
|
||||
}
|
||||
|
||||
private static _empty = '';
|
||||
private static _slash = '/';
|
||||
private static _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
||||
private static _driveLetterPath = /^\/[a-zA-Z]:/;
|
||||
private static _upperCaseDrive = /^(\/)?([A-Z]:)/;
|
||||
|
||||
/**
|
||||
* scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
* The part before the first colon.
|
||||
*/
|
||||
readonly scheme: string;
|
||||
|
||||
/**
|
||||
* authority is the 'www.msft.com' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
* The part between the first double slashes and the next slash.
|
||||
*/
|
||||
readonly authority: string;
|
||||
|
||||
/**
|
||||
* path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
readonly path: string;
|
||||
|
||||
/**
|
||||
* query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
readonly query: string;
|
||||
|
||||
/**
|
||||
* fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
readonly fragment: string;
|
||||
|
||||
private _formatted: string = null;
|
||||
private _fsPath: string = null;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
private constructor(scheme: string, authority: string, path: string, query: string, fragment: string) {
|
||||
|
||||
this.scheme = scheme || URI._empty;
|
||||
this.authority = authority || URI._empty;
|
||||
this.path = path || URI._empty;
|
||||
this.query = query || URI._empty;
|
||||
this.fragment = fragment || URI._empty;
|
||||
|
||||
this._validate(this);
|
||||
}
|
||||
|
||||
// ---- filesystem path -----------------------
|
||||
|
||||
/**
|
||||
* Returns a string representing the corresponding file system path of this URI.
|
||||
* Will handle UNC paths and normalize windows drive letters to lower-case. Also
|
||||
* uses the platform specific path separator. Will *not* validate the path for
|
||||
* invalid characters and semantics. Will *not* look at the scheme of this URI.
|
||||
*/
|
||||
get fsPath(): string {
|
||||
if (!this._fsPath) {
|
||||
let value: string;
|
||||
if (this.authority && this.path && this.scheme === 'file') {
|
||||
// unc path: file://shares/c$/far/boo
|
||||
value = `//${this.authority}${this.path}`;
|
||||
} else if (URI._driveLetterPath.test(this.path)) {
|
||||
// windows drive letter: file:///c:/far/boo
|
||||
value = this.path[1].toLowerCase() + this.path.substr(2);
|
||||
} else {
|
||||
// other path
|
||||
value = this.path;
|
||||
}
|
||||
if (platform.isWindows) {
|
||||
value = value.replace(/\//g, '\\');
|
||||
}
|
||||
this._fsPath = value;
|
||||
}
|
||||
return this._fsPath;
|
||||
}
|
||||
|
||||
// ---- modify to new -------------------------
|
||||
|
||||
public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
|
||||
|
||||
if (!change) {
|
||||
return this;
|
||||
}
|
||||
|
||||
let { scheme, authority, path, query, fragment } = change;
|
||||
if (scheme === void 0) {
|
||||
scheme = this.scheme;
|
||||
} else if (scheme === null) {
|
||||
scheme = '';
|
||||
}
|
||||
if (authority === void 0) {
|
||||
authority = this.authority;
|
||||
} else if (authority === null) {
|
||||
authority = '';
|
||||
}
|
||||
if (path === void 0) {
|
||||
path = this.path;
|
||||
} else if (path === null) {
|
||||
path = '';
|
||||
}
|
||||
if (query === void 0) {
|
||||
query = this.query;
|
||||
} else if (query === null) {
|
||||
query = '';
|
||||
}
|
||||
if (fragment === void 0) {
|
||||
fragment = this.fragment;
|
||||
} else if (fragment === null) {
|
||||
fragment = '';
|
||||
}
|
||||
|
||||
if (scheme === this.scheme
|
||||
&& authority === this.authority
|
||||
&& path === this.path
|
||||
&& query === this.query
|
||||
&& fragment === this.fragment) {
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
return new URI(scheme, authority, path, query, fragment);
|
||||
}
|
||||
|
||||
// ---- parse & validate ------------------------
|
||||
|
||||
public static parse(value: string): URI {
|
||||
const match = URI._regexp.exec(value);
|
||||
if (!match) {
|
||||
return new URI(URI._empty, URI._empty, URI._empty, URI._empty, URI._empty);
|
||||
}
|
||||
return new URI(
|
||||
match[2] || URI._empty,
|
||||
decodeURIComponent(match[4] || URI._empty),
|
||||
decodeURIComponent(match[5] || URI._empty),
|
||||
decodeURIComponent(match[7] || URI._empty),
|
||||
decodeURIComponent(match[9] || URI._empty),
|
||||
);
|
||||
}
|
||||
|
||||
public static file(path: string): URI {
|
||||
|
||||
let authority = URI._empty;
|
||||
|
||||
// normalize to fwd-slashes on windows,
|
||||
// on other systems bwd-slashes are valid
|
||||
// filename character, eg /f\oo/ba\r.txt
|
||||
if (platform.isWindows) {
|
||||
path = path.replace(/\\/g, URI._slash);
|
||||
}
|
||||
|
||||
// check for authority as used in UNC shares
|
||||
// or use the path as given
|
||||
if (path[0] === URI._slash && path[0] === path[1]) {
|
||||
let idx = path.indexOf(URI._slash, 2);
|
||||
if (idx === -1) {
|
||||
authority = path.substring(2);
|
||||
path = URI._empty;
|
||||
} else {
|
||||
authority = path.substring(2, idx);
|
||||
path = path.substring(idx);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that path starts with a slash
|
||||
// or that it is at least a slash
|
||||
if (path[0] !== URI._slash) {
|
||||
path = URI._slash + path;
|
||||
}
|
||||
|
||||
return new URI('file', authority, path, URI._empty, URI._empty);
|
||||
}
|
||||
|
||||
public static from(components: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
|
||||
return new URI(
|
||||
components.scheme,
|
||||
components.authority,
|
||||
components.path,
|
||||
components.query,
|
||||
components.fragment,
|
||||
);
|
||||
}
|
||||
|
||||
private static _schemePattern = /^\w[\w\d+.-]*$/;
|
||||
private static _singleSlashStart = /^\//;
|
||||
private static _doubleSlashStart = /^\/\//;
|
||||
|
||||
private _validate(ret: URI): void {
|
||||
// scheme, https://tools.ietf.org/html/rfc3986#section-3.1
|
||||
// ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
||||
if (ret.scheme && !URI._schemePattern.test(ret.scheme)) {
|
||||
throw new Error('[UriError]: Scheme contains illegal characters.');
|
||||
}
|
||||
|
||||
// path, http://tools.ietf.org/html/rfc3986#section-3.3
|
||||
// If a URI contains an authority component, then the path component
|
||||
// must either be empty or begin with a slash ("/") character. If a URI
|
||||
// does not contain an authority component, then the path cannot begin
|
||||
// with two slash characters ("//").
|
||||
if (ret.path) {
|
||||
if (ret.authority) {
|
||||
if (!URI._singleSlashStart.test(ret.path)) {
|
||||
throw new Error('[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character');
|
||||
}
|
||||
} else {
|
||||
if (URI._doubleSlashStart.test(ret.path)) {
|
||||
throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- printing/externalize ---------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @param skipEncoding Do not encode the result, default is `false`
|
||||
*/
|
||||
public toString(skipEncoding: boolean = false): string {
|
||||
if (!skipEncoding) {
|
||||
if (!this._formatted) {
|
||||
this._formatted = URI._asFormatted(this, false);
|
||||
}
|
||||
return this._formatted;
|
||||
} else {
|
||||
// we don't cache that
|
||||
return URI._asFormatted(this, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static _asFormatted(uri: URI, skipEncoding: boolean): string {
|
||||
|
||||
const encoder = !skipEncoding
|
||||
? encodeURIComponent2
|
||||
: encodeNoop;
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
let { scheme, authority, path, query, fragment } = uri;
|
||||
if (scheme) {
|
||||
parts.push(scheme, ':');
|
||||
}
|
||||
if (authority || scheme === 'file') {
|
||||
parts.push('//');
|
||||
}
|
||||
if (authority) {
|
||||
authority = authority.toLowerCase();
|
||||
let idx = authority.indexOf(':');
|
||||
if (idx === -1) {
|
||||
parts.push(encoder(authority));
|
||||
} else {
|
||||
parts.push(encoder(authority.substr(0, idx)), authority.substr(idx));
|
||||
}
|
||||
}
|
||||
if (path) {
|
||||
// lower-case windows drive letters in /C:/fff or C:/fff
|
||||
const m = URI._upperCaseDrive.exec(path);
|
||||
if (m) {
|
||||
if (m[1]) {
|
||||
path = '/' + m[2].toLowerCase() + path.substr(3); // "/c:".length === 3
|
||||
} else {
|
||||
path = m[2].toLowerCase() + path.substr(2); // // "c:".length === 2
|
||||
}
|
||||
}
|
||||
|
||||
// encode every segement but not slashes
|
||||
// make sure that # and ? are always encoded
|
||||
// when occurring in paths - otherwise the result
|
||||
// cannot be parsed back again
|
||||
let lastIdx = 0;
|
||||
while (true) {
|
||||
let idx = path.indexOf(URI._slash, lastIdx);
|
||||
if (idx === -1) {
|
||||
parts.push(encoder(path.substring(lastIdx)));
|
||||
break;
|
||||
}
|
||||
parts.push(encoder(path.substring(lastIdx, idx)), URI._slash);
|
||||
lastIdx = idx + 1;
|
||||
};
|
||||
}
|
||||
if (query) {
|
||||
parts.push('?', encoder(query));
|
||||
}
|
||||
if (fragment) {
|
||||
parts.push('#', encoder(fragment));
|
||||
}
|
||||
|
||||
return parts.join(URI._empty);
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
const res = <UriState>{
|
||||
fsPath: this.fsPath,
|
||||
external: this.toString(),
|
||||
$mid: 1
|
||||
};
|
||||
|
||||
if (this.path) {
|
||||
res.path = this.path;
|
||||
}
|
||||
|
||||
if (this.scheme) {
|
||||
res.scheme = this.scheme;
|
||||
}
|
||||
|
||||
if (this.authority) {
|
||||
res.authority = this.authority;
|
||||
}
|
||||
|
||||
if (this.query) {
|
||||
res.query = this.query;
|
||||
}
|
||||
|
||||
if (this.fragment) {
|
||||
res.fragment = this.fragment;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static revive(data: any): URI {
|
||||
let result = new URI(
|
||||
(<UriState>data).scheme,
|
||||
(<UriState>data).authority,
|
||||
(<UriState>data).path,
|
||||
(<UriState>data).query,
|
||||
(<UriState>data).fragment
|
||||
);
|
||||
result._fsPath = (<UriState>data).fsPath;
|
||||
result._formatted = (<UriState>data).external;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
interface UriComponents {
|
||||
scheme: string;
|
||||
authority: string;
|
||||
path: string;
|
||||
query: string;
|
||||
fragment: string;
|
||||
}
|
||||
|
||||
interface UriState extends UriComponents {
|
||||
$mid: number;
|
||||
fsPath: string;
|
||||
external: string;
|
||||
}
|
||||
115
src/vs/base/common/uuid.ts
Normal file
115
src/vs/base/common/uuid.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Represents a UUID as defined by rfc4122.
|
||||
*/
|
||||
export interface UUID {
|
||||
|
||||
/**
|
||||
* @returns the canonical representation in sets of hexadecimal numbers separated by dashes.
|
||||
*/
|
||||
asHex(): string;
|
||||
|
||||
equals(other: UUID): boolean;
|
||||
}
|
||||
|
||||
class ValueUUID implements UUID {
|
||||
|
||||
constructor(public _value: string) {
|
||||
// empty
|
||||
}
|
||||
|
||||
public asHex(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public equals(other: UUID): boolean {
|
||||
return this.asHex() === other.asHex();
|
||||
}
|
||||
}
|
||||
|
||||
class V4UUID extends ValueUUID {
|
||||
|
||||
private static _chars = ['0', '1', '2', '3', '4', '5', '6', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
|
||||
|
||||
private static _timeHighBits = ['8', '9', 'a', 'b'];
|
||||
|
||||
private static _oneOf(array: string[]): string {
|
||||
return array[Math.floor(array.length * Math.random())];
|
||||
}
|
||||
|
||||
private static _randomHex(): string {
|
||||
return V4UUID._oneOf(V4UUID._chars);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super([
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
'-',
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
'-',
|
||||
'4',
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
'-',
|
||||
V4UUID._oneOf(V4UUID._timeHighBits),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
'-',
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
V4UUID._randomHex(),
|
||||
].join(''));
|
||||
}
|
||||
}
|
||||
|
||||
export function v4(): UUID {
|
||||
return new V4UUID();
|
||||
}
|
||||
|
||||
const _UUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
export function isUUID(value: string): boolean {
|
||||
return _UUIDPattern.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a UUID that is of the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
|
||||
* @param value A uuid string.
|
||||
*/
|
||||
export function parse(value: string): UUID {
|
||||
if (!isUUID(value)) {
|
||||
throw new Error('invalid uuid');
|
||||
}
|
||||
|
||||
return new ValueUUID(value);
|
||||
}
|
||||
|
||||
export function generateUuid(): string {
|
||||
return v4().asHex();
|
||||
}
|
||||
74
src/vs/base/common/winjs.base.d.ts
vendored
Normal file
74
src/vs/base/common/winjs.base.d.ts
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
/// Interfaces for WinJS
|
||||
|
||||
export type ErrorCallback = (error: any) => void;
|
||||
export type ProgressCallback<TProgress = any> = (progress: TProgress) => void;
|
||||
|
||||
export declare class Promise<T = any, TProgress = any> {
|
||||
constructor(
|
||||
executor: (
|
||||
resolve: (value: T | PromiseLike<T>) => void,
|
||||
reject: (reason: any) => void,
|
||||
progress: (progress: TProgress) => void) => void,
|
||||
oncancel?: () => void);
|
||||
|
||||
public then<TResult1 = T, TResult2 = never>(
|
||||
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
||||
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
|
||||
onprogress?: (progress: TProgress) => void): Promise<TResult1 | TResult2, TProgress>;
|
||||
|
||||
public done(
|
||||
onfulfilled?: (value: T) => void,
|
||||
onrejected?: (reason: any) => void,
|
||||
onprogress?: (progress: TProgress) => void): void;
|
||||
|
||||
public cancel(): void;
|
||||
|
||||
public static as(value: null): Promise<null>;
|
||||
public static as(value: undefined): Promise<undefined>;
|
||||
public static as<T, TPromise extends PromiseLike<T>>(value: TPromise): TPromise;
|
||||
public static as<T>(value: T): Promise<T>;
|
||||
|
||||
public static is(value: any): value is PromiseLike<any>;
|
||||
|
||||
public static timeout(delay: number): Promise<void>;
|
||||
|
||||
public static join<T1, T2>(promises: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
|
||||
public static join<T>(promises: (T | PromiseLike<T>)[]): Promise<T[]>;
|
||||
public static join<T>(promises: { [n: string]: T | PromiseLike<T> }): Promise<{ [n: string]: T }>;
|
||||
|
||||
public static any<T>(promises: (T | PromiseLike<T>)[]): Promise<{ key: string; value: Promise<T>; }>;
|
||||
|
||||
public static wrap<T>(value: T | PromiseLike<T>): Promise<T>;
|
||||
|
||||
public static wrapError<T = never>(error: Error): Promise<T>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static addEventListener(event: 'error', promiseErrorHandler: (e: IPromiseError) => void);
|
||||
}
|
||||
|
||||
export type TValueCallback<T = any> = (value: T | PromiseLike<T>) => void;
|
||||
|
||||
export {
|
||||
Promise as TPromise,
|
||||
Promise as PPromise,
|
||||
TValueCallback as ValueCallback,
|
||||
ProgressCallback as TProgressCallback
|
||||
};
|
||||
|
||||
export interface IPromiseErrorDetail {
|
||||
parent: Promise;
|
||||
error: any;
|
||||
id: number;
|
||||
handler: Function;
|
||||
exception: Error;
|
||||
}
|
||||
|
||||
export interface IPromiseError {
|
||||
detail: IPromiseErrorDetail;
|
||||
}
|
||||
13
src/vs/base/common/winjs.base.js
Normal file
13
src/vs/base/common/winjs.base.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
define(['./winjs.base.raw'], function (winjs) {
|
||||
'use strict';
|
||||
return {
|
||||
Promise: winjs.Promise,
|
||||
TPromise: winjs.Promise,
|
||||
PPromise: winjs.Promise
|
||||
};
|
||||
});
|
||||
2071
src/vs/base/common/winjs.base.raw.js
Normal file
2071
src/vs/base/common/winjs.base.raw.js
Normal file
File diff suppressed because it is too large
Load Diff
392
src/vs/base/common/worker/simpleWorker.ts
Normal file
392
src/vs/base/common/worker/simpleWorker.ts
Normal file
@@ -0,0 +1,392 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { transformErrorForSerialization } from 'vs/base/common/errors';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ErrorCallback, TPromise, ValueCallback } from 'vs/base/common/winjs.base';
|
||||
import { ShallowCancelThenPromise } from 'vs/base/common/async';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
|
||||
const INITIALIZE = '$initialize';
|
||||
|
||||
export interface IWorker {
|
||||
getId(): number;
|
||||
postMessage(message: string): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface IWorkerCallback {
|
||||
(message: string): void;
|
||||
}
|
||||
|
||||
export interface IWorkerFactory {
|
||||
create(moduleId: string, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker;
|
||||
}
|
||||
|
||||
let webWorkerWarningLogged = false;
|
||||
export function logOnceWebWorkerWarning(err: any): void {
|
||||
if (!isWeb) {
|
||||
// running tests
|
||||
return;
|
||||
}
|
||||
if (!webWorkerWarningLogged) {
|
||||
webWorkerWarningLogged = true;
|
||||
console.warn('Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/Microsoft/monaco-editor#faq');
|
||||
}
|
||||
console.warn(err.message);
|
||||
}
|
||||
|
||||
interface IMessage {
|
||||
vsWorker: number;
|
||||
req?: string;
|
||||
seq?: string;
|
||||
}
|
||||
|
||||
interface IRequestMessage extends IMessage {
|
||||
req: string;
|
||||
method: string;
|
||||
args: any[];
|
||||
}
|
||||
|
||||
interface IReplyMessage extends IMessage {
|
||||
seq: string;
|
||||
err: any;
|
||||
res: any;
|
||||
}
|
||||
|
||||
interface IMessageReply {
|
||||
c: ValueCallback;
|
||||
e: ErrorCallback;
|
||||
}
|
||||
|
||||
interface IMessageHandler {
|
||||
sendMessage(msg: string): void;
|
||||
handleMessage(method: string, args: any[]): TPromise<any>;
|
||||
}
|
||||
|
||||
class SimpleWorkerProtocol {
|
||||
|
||||
private _workerId: number;
|
||||
private _lastSentReq: number;
|
||||
private _pendingReplies: { [req: string]: IMessageReply; };
|
||||
private _handler: IMessageHandler;
|
||||
|
||||
constructor(handler: IMessageHandler) {
|
||||
this._workerId = -1;
|
||||
this._handler = handler;
|
||||
this._lastSentReq = 0;
|
||||
this._pendingReplies = Object.create(null);
|
||||
}
|
||||
|
||||
public setWorkerId(workerId: number): void {
|
||||
this._workerId = workerId;
|
||||
}
|
||||
|
||||
public sendMessage(method: string, args: any[]): TPromise<any> {
|
||||
let req = String(++this._lastSentReq);
|
||||
let reply: IMessageReply = {
|
||||
c: null,
|
||||
e: null
|
||||
};
|
||||
let result = new TPromise<any>((c, e, p) => {
|
||||
reply.c = c;
|
||||
reply.e = e;
|
||||
}, () => {
|
||||
// Cancel not supported
|
||||
});
|
||||
this._pendingReplies[req] = reply;
|
||||
|
||||
this._send({
|
||||
vsWorker: this._workerId,
|
||||
req: req,
|
||||
method: method,
|
||||
args: args
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public handleMessage(serializedMessage: string): void {
|
||||
let message: IMessage;
|
||||
try {
|
||||
message = JSON.parse(serializedMessage);
|
||||
} catch (e) {
|
||||
// nothing
|
||||
}
|
||||
if (!message.vsWorker) {
|
||||
return;
|
||||
}
|
||||
if (this._workerId !== -1 && message.vsWorker !== this._workerId) {
|
||||
return;
|
||||
}
|
||||
this._handleMessage(message);
|
||||
}
|
||||
|
||||
private _handleMessage(msg: IMessage): void {
|
||||
if (msg.seq) {
|
||||
let replyMessage = <IReplyMessage>msg;
|
||||
if (!this._pendingReplies[replyMessage.seq]) {
|
||||
console.warn('Got reply to unknown seq');
|
||||
return;
|
||||
}
|
||||
|
||||
let reply = this._pendingReplies[replyMessage.seq];
|
||||
delete this._pendingReplies[replyMessage.seq];
|
||||
|
||||
if (replyMessage.err) {
|
||||
let err = replyMessage.err;
|
||||
if (replyMessage.err.$isError) {
|
||||
err = new Error();
|
||||
err.name = replyMessage.err.name;
|
||||
err.message = replyMessage.err.message;
|
||||
err.stack = replyMessage.err.stack;
|
||||
}
|
||||
reply.e(err);
|
||||
return;
|
||||
}
|
||||
|
||||
reply.c(replyMessage.res);
|
||||
return;
|
||||
}
|
||||
|
||||
let requestMessage = <IRequestMessage>msg;
|
||||
let req = requestMessage.req;
|
||||
let result = this._handler.handleMessage(requestMessage.method, requestMessage.args);
|
||||
result.then((r) => {
|
||||
this._send({
|
||||
vsWorker: this._workerId,
|
||||
seq: req,
|
||||
res: r,
|
||||
err: undefined
|
||||
});
|
||||
}, (e) => {
|
||||
this._send({
|
||||
vsWorker: this._workerId,
|
||||
seq: req,
|
||||
res: undefined,
|
||||
err: transformErrorForSerialization(e)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _send(msg: IRequestMessage | IReplyMessage): void {
|
||||
let strMsg = JSON.stringify(msg);
|
||||
// console.log('SENDING: ' + strMsg);
|
||||
this._handler.sendMessage(strMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main thread side
|
||||
*/
|
||||
export class SimpleWorkerClient<T> extends Disposable {
|
||||
|
||||
private _worker: IWorker;
|
||||
private _onModuleLoaded: TPromise<string[]>;
|
||||
private _protocol: SimpleWorkerProtocol;
|
||||
private _lazyProxy: TPromise<T>;
|
||||
private _lastRequestTimestamp = -1;
|
||||
|
||||
constructor(workerFactory: IWorkerFactory, moduleId: string) {
|
||||
super();
|
||||
|
||||
let lazyProxyFulfill: (v: T) => void = null;
|
||||
let lazyProxyReject: (err: any) => void = null;
|
||||
|
||||
this._worker = this._register(workerFactory.create(
|
||||
'vs/base/common/worker/simpleWorker',
|
||||
(msg: string) => {
|
||||
this._protocol.handleMessage(msg);
|
||||
},
|
||||
(err: any) => {
|
||||
// in Firefox, web workers fail lazily :(
|
||||
// we will reject the proxy
|
||||
lazyProxyReject(err);
|
||||
}
|
||||
));
|
||||
|
||||
this._protocol = new SimpleWorkerProtocol({
|
||||
sendMessage: (msg: string): void => {
|
||||
this._worker.postMessage(msg);
|
||||
},
|
||||
handleMessage: (method: string, args: any[]): TPromise<any> => {
|
||||
// Intentionally not supporting worker -> main requests
|
||||
return TPromise.as(null);
|
||||
}
|
||||
});
|
||||
this._protocol.setWorkerId(this._worker.getId());
|
||||
|
||||
// Gather loader configuration
|
||||
let loaderConfiguration: any = null;
|
||||
let globalRequire = (<any>self).require;
|
||||
if (typeof globalRequire.getConfig === 'function') {
|
||||
// Get the configuration from the Monaco AMD Loader
|
||||
loaderConfiguration = globalRequire.getConfig();
|
||||
} else if (typeof (<any>self).requirejs !== 'undefined') {
|
||||
// Get the configuration from requirejs
|
||||
loaderConfiguration = (<any>self).requirejs.s.contexts._.config;
|
||||
}
|
||||
|
||||
this._lazyProxy = new TPromise<T>((c, e, p) => {
|
||||
lazyProxyFulfill = c;
|
||||
lazyProxyReject = e;
|
||||
}, () => { /* no cancel */ });
|
||||
|
||||
// Send initialize message
|
||||
this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [
|
||||
this._worker.getId(),
|
||||
moduleId,
|
||||
loaderConfiguration
|
||||
]);
|
||||
this._onModuleLoaded.then((availableMethods: string[]) => {
|
||||
let proxy = <T><any>{};
|
||||
for (let i = 0; i < availableMethods.length; i++) {
|
||||
proxy[availableMethods[i]] = createProxyMethod(availableMethods[i], proxyMethodRequest);
|
||||
}
|
||||
lazyProxyFulfill(proxy);
|
||||
}, (e) => {
|
||||
lazyProxyReject(e);
|
||||
this._onError('Worker failed to load ' + moduleId, e);
|
||||
});
|
||||
|
||||
// Create proxy to loaded code
|
||||
let proxyMethodRequest = (method: string, args: any[]): TPromise<any> => {
|
||||
return this._request(method, args);
|
||||
};
|
||||
|
||||
let createProxyMethod = (method: string, proxyMethodRequest: (method: string, args: any[]) => TPromise<any>): Function => {
|
||||
return function () {
|
||||
let args = Array.prototype.slice.call(arguments, 0);
|
||||
return proxyMethodRequest(method, args);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
public getProxyObject(): TPromise<T> {
|
||||
// Do not allow chaining promises to cancel the proxy creation
|
||||
return new ShallowCancelThenPromise(this._lazyProxy);
|
||||
}
|
||||
|
||||
public getLastRequestTimestamp(): number {
|
||||
return this._lastRequestTimestamp;
|
||||
}
|
||||
|
||||
private _request(method: string, args: any[]): TPromise<any> {
|
||||
return new TPromise<any>((c, e, p) => {
|
||||
this._onModuleLoaded.then(() => {
|
||||
this._lastRequestTimestamp = Date.now();
|
||||
this._protocol.sendMessage(method, args).then(c, e);
|
||||
}, e);
|
||||
}, () => {
|
||||
// Cancel intentionally not supported
|
||||
});
|
||||
}
|
||||
|
||||
private _onError(message: string, error?: any): void {
|
||||
console.error(message);
|
||||
console.info(error);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IRequestHandler {
|
||||
_requestHandlerTrait: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Worker side
|
||||
*/
|
||||
export class SimpleWorkerServer {
|
||||
|
||||
private _protocol: SimpleWorkerProtocol;
|
||||
private _requestHandler: IRequestHandler;
|
||||
|
||||
constructor(postSerializedMessage: (msg: string) => void) {
|
||||
this._protocol = new SimpleWorkerProtocol({
|
||||
sendMessage: (msg: string): void => {
|
||||
postSerializedMessage(msg);
|
||||
},
|
||||
handleMessage: (method: string, args: any[]): TPromise<any> => this._handleMessage(method, args)
|
||||
});
|
||||
}
|
||||
|
||||
public onmessage(msg: string): void {
|
||||
this._protocol.handleMessage(msg);
|
||||
}
|
||||
|
||||
private _handleMessage(method: string, args: any[]): TPromise<any> {
|
||||
if (method === INITIALIZE) {
|
||||
return this.initialize(<number>args[0], <string>args[1], <any>args[2]);
|
||||
}
|
||||
|
||||
if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') {
|
||||
return TPromise.wrapError(new Error('Missing requestHandler or method: ' + method));
|
||||
}
|
||||
|
||||
try {
|
||||
return TPromise.as(this._requestHandler[method].apply(this._requestHandler, args));
|
||||
} catch (e) {
|
||||
return TPromise.wrapError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private initialize(workerId: number, moduleId: string, loaderConfig: any): TPromise<any> {
|
||||
this._protocol.setWorkerId(workerId);
|
||||
|
||||
if (loaderConfig) {
|
||||
// Remove 'baseUrl', handling it is beyond scope for now
|
||||
if (typeof loaderConfig.baseUrl !== 'undefined') {
|
||||
delete loaderConfig['baseUrl'];
|
||||
}
|
||||
if (typeof loaderConfig.paths !== 'undefined') {
|
||||
if (typeof loaderConfig.paths.vs !== 'undefined') {
|
||||
delete loaderConfig.paths['vs'];
|
||||
}
|
||||
}
|
||||
let nlsConfig = loaderConfig['vs/nls'];
|
||||
// We need to have pseudo translation
|
||||
if (nlsConfig && nlsConfig.pseudo) {
|
||||
require(['vs/nls'], function (nlsPlugin) {
|
||||
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
|
||||
});
|
||||
}
|
||||
|
||||
// Since this is in a web worker, enable catching errors
|
||||
loaderConfig.catchError = true;
|
||||
(<any>self).require.config(loaderConfig);
|
||||
}
|
||||
|
||||
let cc: ValueCallback;
|
||||
let ee: ErrorCallback;
|
||||
let r = new TPromise<any>((c, e, p) => {
|
||||
cc = c;
|
||||
ee = e;
|
||||
});
|
||||
|
||||
// Use the global require to be sure to get the global config
|
||||
(<any>self).require([moduleId], (...result: any[]) => {
|
||||
let handlerModule = result[0];
|
||||
this._requestHandler = handlerModule.create();
|
||||
|
||||
let methods: string[] = [];
|
||||
for (let prop in this._requestHandler) {
|
||||
if (typeof this._requestHandler[prop] === 'function') {
|
||||
methods.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
cc(methods);
|
||||
}, ee);
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on the worker side
|
||||
*/
|
||||
export function create(postMessage: (msg: string) => void): SimpleWorkerServer {
|
||||
return new SimpleWorkerServer(postMessage);
|
||||
}
|
||||
Reference in New Issue
Block a user