SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View 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."
]
}]

View 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());
}
}

View 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);
}

View 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
View 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);
}

View 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"
]
}

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'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;
}
}

View 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;
}
}

View 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();
}
}

View 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
}

View 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
View 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;
}
}
}
}

View 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);
}

View 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];
};
}

View 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;
}

File diff suppressed because it is too large Load Diff

View 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);
}
}
}
}

View 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;
}
}

View 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.");
}

View 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
View 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;
}

View 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);
}
}

View 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',
};

View 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;
}

View 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
View 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
View 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');
}
}

View 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);
}

View 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;
}
}

View 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;
}

View 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#');

View 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

File diff suppressed because it is too large Load Diff

View 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) }];
}

View 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 '';
}
}

View 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;
}

View 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)
}

View 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];
}

View 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;
}

View 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, '&&');
}

View 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
View 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;
}
}
}

View 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
View 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;

View 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
};
});

View 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.

File diff suppressed because it is too large Load Diff

View 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
View 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
}

View 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';
}

View 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;
}

View 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;
}

View 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]);
}
};
}

View 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
View 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;
}

View 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
}

View 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;
}
}

View 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
*/

View 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);
}

View 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;

View 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();
}
}

View 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 '&lt;';
case '>': return '&gt;';
case '&': return '&amp;';
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
View 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
View 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
View 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
View 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;
}

View 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
};
});

File diff suppressed because it is too large Load Diff

View 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);
}