Refresh master with initial release/0.24 snapshot (#332)

* Initial port of release/0.24 source code

* Fix additional headers

* Fix a typo in launch.json
This commit is contained in:
Karl Burtram
2017-12-15 15:38:57 -08:00
committed by GitHub
parent 271b3a0b82
commit 6ad0df0e3e
7118 changed files with 107999 additions and 56466 deletions

View File

@@ -1,30 +0,0 @@
// 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

@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
/**
* Returns the last element of an array.
* @param array The array.
@@ -111,7 +113,7 @@ function _divideAndMerge<T>(data: T[], compare: (a: T, b: T) => number): void {
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)) {
for (const element of mergeSort(data.slice(0), compare)) {
if (!currentGroup || compare(currentGroup[0], element) !== 0) {
currentGroup = [element];
result.push(currentGroup);
@@ -122,28 +124,43 @@ export function groupBy<T>(data: T[], compare: (a: T, b: T) => number): T[][] {
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) {
export interface Splice<T> {
start: number;
deleteCount: number;
inserted: T[];
}
const removed: T[] = [];
const added: T[] = [];
/**
* Diffs two *sorted* arrays and computes the splices which apply the diff.
*/
export function sortedDiff<T>(before: T[], after: T[], compare: (a: T, b: T) => number): Splice<T>[] {
const result: Splice<T>[] = [];
function pushSplice(start: number, deleteCount: number, inserted: T[]): void {
if (deleteCount === 0 && inserted.length === 0) {
return;
}
const latest = result[result.length - 1];
if (latest && latest.start + latest.deleteCount === start) {
latest.deleteCount += deleteCount;
latest.inserted.push(...inserted);
} else {
result.push({ start, deleteCount, inserted });
}
}
let beforeIdx = 0;
let afterIdx = 0;
while (true) {
if (beforeIdx === before.length) {
added.push(...after.slice(afterIdx));
pushSplice(beforeIdx, 0, after.slice(afterIdx));
break;
}
if (afterIdx === after.length) {
removed.push(...before.slice(beforeIdx));
pushSplice(beforeIdx, before.length - beforeIdx, []);
break;
}
@@ -156,15 +173,35 @@ export function delta<T>(before: T[], after: T[], compare: (a: T, b: T) => numbe
afterIdx += 1;
} else if (n < 0) {
// beforeElement is smaller -> before element removed
removed.push(beforeElement);
pushSplice(beforeIdx, 1, []);
beforeIdx += 1;
} else if (n > 0) {
// beforeElement is greater -> after element added
added.push(afterElement);
pushSplice(beforeIdx, 0, [afterElement]);
afterIdx += 1;
}
}
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): { removed: T[], added: T[] } {
const splices = sortedDiff(before, after, compare);
const removed: T[] = [];
const added: T[] = [];
for (const splice of splices) {
removed.push(...before.slice(splice.start, splice.start + splice.deleteCount));
added.push(...splice.inserted);
}
return { removed, added };
}
@@ -183,7 +220,51 @@ export function top<T>(array: T[], compare: (a: T, b: T) => number, n: number):
return [];
}
const result = array.slice(0, n).sort(compare);
for (let i = n, m = array.length; i < m; i++) {
topStep(array, compare, result, n, array.length);
return result;
}
/**
* Asynchronous variant of `top()` allowing for splitting up work in batches between which the event loop can run.
*
* 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.
* @param batch The number of elements to examine before yielding to the event loop.
* @return The first n elemnts from array when sorted with compare.
*/
export function topAsync<T>(array: T[], compare: (a: T, b: T) => number, n: number, batch: number): TPromise<T[]> {
if (n === 0) {
return TPromise.as([]);
}
let canceled = false;
return new TPromise((resolve, reject) => {
(async () => {
const o = array.length;
const result = array.slice(0, n).sort(compare);
for (let i = n, m = Math.min(n + batch, o); i < o; i = m, m = Math.min(m + batch, o)) {
if (i > n) {
await new Promise(resolve => setTimeout(resolve)); // nextTick() would starve I/O.
}
if (canceled) {
throw new Error('canceled');
}
topStep(array, compare, result, i, m);
}
return result;
})()
.then(resolve, reject);
}, () => {
canceled = true;
});
}
function topStep<T>(array: T[], compare: (a: T, b: T) => number, result: T[], i: number, m: number): void {
for (const n = result.length; i < m; i++) {
const element = array[i];
if (compare(element, result[n - 1]) < 0) {
result.pop();
@@ -191,7 +272,6 @@ export function top<T>(array: T[], compare: (a: T, b: T) => number, n: number):
result.splice(j, 0, element);
}
}
return result;
}
/**
@@ -287,14 +367,46 @@ export function commonPrefixLength<T>(one: T[], other: T[], equals: (a: T, b: T)
}
export function flatten<T>(arr: T[][]): T[] {
return arr.reduce((r, v) => r.concat(v), []);
return [].concat(...arr);
}
export function range(to: number, from = 0): number[] {
export function range(to: number): number[];
export function range(from: number, to: number): number[];
export function range(arg: number, to?: number): number[] {
let from = typeof to === 'number' ? arg : 0;
if (typeof to === 'number') {
from = arg;
} else {
from = 0;
to = arg;
}
const result: number[] = [];
for (let i = from; i < to; i++) {
result.push(i);
if (from <= to) {
for (let i = from; i < to; i++) {
result.push(i);
}
} else {
for (let i = from; i > to; i--) {
result.push(i);
}
}
return result;
}
export function weave<T>(a: T[], b: T[]): T[] {
const result: T[] = [];
let ai = 0, bi = 0;
for (let i = 0, length = a.length + b.length; i < length; i++) {
if ((i % 2 === 0 && ai < a.length) || bi >= b.length) {
result.push(a[ai++]);
} else {
result.push(b[bi++]);
}
}
return result;

View File

@@ -11,8 +11,9 @@ import { Promise, TPromise, ValueCallback, ErrorCallback, ProgressCallback } fro
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
function isThenable<T>(obj: any): obj is Thenable<T> {
export function isThenable<T>(obj: any): obj is Thenable<T> {
return obj && typeof (<Thenable<any>>obj).then === 'function';
}
@@ -451,6 +452,10 @@ export class Limiter<T> {
return this._onFinished.event;
}
public get size(): number {
return this.runningPromises + this.outstandingPromises.length;
}
queue(promiseFactory: ITask<Promise>): Promise;
queue(promiseFactory: ITask<TPromise<T>>): TPromise<T> {
return new TPromise<T>((c, e, p) => {
@@ -501,6 +506,33 @@ export class Queue<T> extends Limiter<T> {
}
}
/**
* A helper to organize queues per resource. The ResourceQueue makes sure to manage queues per resource
* by disposing them once the queue is empty.
*/
export class ResourceQueue<T> {
private queues: { [path: string]: Queue<void> };
constructor() {
this.queues = Object.create(null);
}
public queueFor(resource: URI): Queue<void> {
const key = resource.toString();
if (!this.queues[key]) {
const queue = new Queue<void>();
queue.onFinished(() => {
queue.dispose();
delete this.queues[key];
});
this.queues[key] = queue;
}
return this.queues[key];
}
}
export function setDisposableTimeout(handler: Function, timeout: number, ...args: any[]): IDisposable {
const handle = setTimeout(handler, timeout, ...args);
return { dispose() { clearTimeout(handle); } };

View File

@@ -6,47 +6,21 @@
import { IDisposable } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { LinkedList } from 'vs/base/common/linkedList';
export default class CallbackList {
private _callbacks: Function[];
private _contexts: any[];
private _callbacks: LinkedList<[Function, any]>;
public add(callback: Function, context: any = null, bucket?: IDisposable[]): void {
public add(callback: Function, context: any = null, bucket?: IDisposable[]): () => void {
if (!this._callbacks) {
this._callbacks = [];
this._contexts = [];
this._callbacks = new LinkedList<[Function, any]>();
}
this._callbacks.push(callback);
this._contexts.push(context);
const remove = this._callbacks.push([callback, 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');
bucket.push({ dispose: remove });
}
return remove;
}
public invoke(...args: any[]): any[] {
@@ -54,13 +28,12 @@ export default class CallbackList {
return undefined;
}
const ret: any[] = [],
callbacks = this._callbacks.slice(0),
contexts = this._contexts.slice(0);
const ret: any[] = [];
const elements = this._callbacks.toArray();
for (let i = 0, len = callbacks.length; i < len; i++) {
for (const [callback, context] of elements) {
try {
ret.push(callbacks[i].apply(contexts[i], args));
ret.push(callback.apply(context, args));
} catch (e) {
onUnexpectedError(e);
}
@@ -68,19 +41,20 @@ export default class CallbackList {
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]]);
return this._callbacks
? this._callbacks.toArray()
: [];
}
public isEmpty(): boolean {
return !this._callbacks || this._callbacks.isEmpty();
}
public dispose(): void {
this._callbacks = undefined;
this._contexts = undefined;
}
}

View File

@@ -6,6 +6,7 @@
'use strict';
import Event, { Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
export interface CancellationToken {
readonly isCancellationRequested: boolean;
@@ -16,10 +17,10 @@ export interface CancellationToken {
readonly onCancellationRequested: Event<any>;
}
const shortcutEvent: Event<any> = Object.freeze(function (callback, context?) {
const shortcutEvent = Object.freeze(function (callback, context?): IDisposable {
let handle = setTimeout(callback.bind(context), 0);
return { dispose() { clearTimeout(handle); } };
});
} as Event<any>);
export namespace CancellationToken {
@@ -83,8 +84,10 @@ export class CancellationTokenSource {
// cancelled token when cancellation happens
// before someone asks for the token
this._token = CancellationToken.Cancelled;
} else {
(<MutableToken>this._token).cancel();
} else if (this._token instanceof MutableToken) {
// actually cancel
this._token.cancel();
}
}

View File

@@ -29,7 +29,7 @@ const hasOwnProperty = Object.prototype.hasOwnProperty;
*/
export function values<T>(from: IStringDictionary<T> | INumberDictionary<T>): T[] {
const result: T[] = [];
for (var key in from) {
for (let key in from) {
if (hasOwnProperty.call(from, key)) {
result.push(from[key]);
}
@@ -39,7 +39,7 @@ export function values<T>(from: IStringDictionary<T> | INumberDictionary<T>): T[
export function size<T>(from: IStringDictionary<T> | INumberDictionary<T>): number {
let count = 0;
for (var key in from) {
for (let key in from) {
if (hasOwnProperty.call(from, key)) {
count += 1;
}

View File

@@ -4,8 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import scorer = require('vs/base/common/scorer');
import strings = require('vs/base/common/strings');
import * as strings from 'vs/base/common/strings';
import * as paths from 'vs/base/common/paths';
let intlFileNameCollator: Intl.Collator;
@@ -37,14 +36,8 @@ export function compareFileNames(one: string, other: string): number {
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] || '';
const [oneName, oneExtension] = extractNameAndExtension(one, true);
const [otherName, otherExtension] = extractNameAndExtension(other, true);
if (oneName !== otherName) {
return oneName < otherName ? -1 : 1;
@@ -59,14 +52,8 @@ export function noIntlCompareFileNames(one: string, other: string): number {
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] || '';
const [oneName, oneExtension] = extractNameAndExtension(one);
const [otherName, otherExtension] = extractNameAndExtension(other);
let result = intlFileNameCollator.compare(oneExtension, otherExtension);
@@ -92,14 +79,8 @@ export function compareFileExtensions(one: string, other: string): number {
}
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] || '';
const [oneName, oneExtension] = extractNameAndExtension(one, true);
const [otherName, otherExtension] = extractNameAndExtension(other, true);
if (oneExtension !== otherExtension) {
return oneExtension < otherExtension ? -1 : 1;
@@ -112,6 +93,12 @@ function noIntlCompareFileExtensions(one: string, other: string): number {
return oneName < otherName ? -1 : 1;
}
function extractNameAndExtension(str?: string, lowercase?: boolean): [string, string] {
const match = str ? FileNameMatch.exec(lowercase ? str.toLowerCase() : str) : [] as RegExpExecArray;
return [(match && match[1]) || '', (match && match[3]) || ''];
}
export function comparePaths(one: string, other: string): number {
const oneParts = one.split(paths.nativeSep);
const otherParts = other.split(paths.nativeSep);
@@ -186,56 +173,4 @@ export function compareByPrefix(one: string, other: string, lookFor: string): nu
}
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

@@ -5,7 +5,7 @@
'use strict';
export function createDecorator(mapFn: (fn: Function) => Function): Function {
export function createDecorator(mapFn: (fn: Function, key: string) => Function): Function {
return (target: any, key: string, descriptor: any) => {
let fnKey: string = null;
let fn: Function = null;
@@ -22,7 +22,7 @@ export function createDecorator(mapFn: (fn: Function) => Function): Function {
throw new Error('not supported');
}
descriptor[fnKey] = mapFn(fn);
descriptor[fnKey] = mapFn(fn, key);
};
}
@@ -56,4 +56,15 @@ export function memoize(target: any, key: string, descriptor: any) {
return this[memoizeKey];
};
}
export function debounce(delay: number): Function {
return createDecorator((fn, key) => {
const timerKey = `$debounce$${key}`;
return function (...args: any[]) {
clearTimeout(this[timerKey]);
this[timerKey] = setTimeout(() => fn.apply(this, args), delay);
};
});
}

View File

@@ -11,15 +11,15 @@ import * as Platform from 'vs/base/common/platform';
* Then trigger an action that will write to diagnostics to see all cached output from the past.
*/
var globals = Platform.globals;
const 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[] = [];
const switches = globals.Monaco.Diagnostics;
const map = new Map<string, Function[]>();
const data: any[] = [];
function fifo(array: any[], size: number) {
while (array.length > size) {
@@ -37,29 +37,29 @@ export function register(what: string, fn: Function): (...args: any[]) => void {
}
// register switch
var flag = switches[what] || false;
const flag = switches[what] || false;
switches[what] = flag;
// register function
var tracers = map.get(what) || [];
const tracers = map.get(what) || [];
tracers.push(fn);
map.set(what, tracers);
var result = function (...args: any[]) {
const result = function (...args: any[]) {
var idx: number;
let idx: number;
if (switches[what] === true) {
// replay back-in-time functions
var allArgs = [arguments];
const 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();
const doIt: () => void = function () {
const thisArguments = allArgs.shift();
fn.apply(fn, thisArguments);
if (allArgs.length > 0) {
Platform.setTimeout(doIt, 500);
@@ -71,10 +71,10 @@ export function register(what: string, fn: Function): (...args: any[]) => void {
// know where to store
idx = data.indexOf(fn);
idx = idx !== -1 ? idx : data.length;
var dataIdx = idx + 1;
const dataIdx = idx + 1;
// store arguments
var allargs = data[dataIdx] || [];
const allargs = data[dataIdx] || [];
allargs.push(arguments);
fifo(allargs, 50);

View File

@@ -9,6 +9,7 @@ import { DiffChange } from 'vs/base/common/diff/diffChange';
export interface ISequence {
getLength(): number;
getElementHash(index: number): string;
[index: number]: string;
}
export interface IDiffChange {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, toDisposable, combinedDisposable, empty as EmptyDisposable } 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';
@@ -82,7 +82,7 @@ export class Emitter<T> {
this._options.onFirstListenerAdd(this);
}
this._callbacks.add(listener, thisArgs);
const remove = this._callbacks.add(listener, thisArgs);
if (firstListener && this._options && this._options.onFirstListenerDidAdd) {
this._options.onFirstListenerDidAdd(this);
@@ -97,7 +97,7 @@ export class Emitter<T> {
dispose: () => {
result.dispose = Emitter._noop;
if (!this._disposed) {
this._callbacks.remove(listener, thisArgs);
remove();
if (this._options && this._options.onLastListenerRemove && this._callbacks.isEmpty()) {
this._options.onLastListenerRemove(this);
}
@@ -304,7 +304,7 @@ export function once<T>(event: Event<T>): Event<T> {
};
}
export function any<T>(...events: Event<T>[]): Event<T> {
export function anyEvent<T>(...events: Event<T>[]): Event<T> {
return (listener, thisArgs = null, disposables?) => combinedDisposable(events.map(event => event(e => listener.call(thisArgs, e), null, disposables)));
}
@@ -313,17 +313,17 @@ export function debounceEvent<I, O>(event: Event<I>, merger: (last: O, event: I)
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 output: O = undefined;
let handle: number = undefined;
let numDebouncedCalls = 0;
const emitter = new Emitter<O>({
onFirstListenerAdd() {
subscription = event(cur => {
numDebouncedCalls++;
output = merger(output, cur);
if (!handle && leading) {
if (leading && !handle) {
emitter.fire(output);
}
@@ -331,11 +331,11 @@ export function debounceEvent<I, O>(event: Event<I>, merger: (last: O, event: I)
handle = setTimeout(() => {
let _output = output;
output = undefined;
handle = undefined;
if (!leading || numDebouncedCalls > 1) {
emitter.fire(_output);
}
handle = null;
numDebouncedCalls = 0;
}, delay);
});
@@ -424,7 +424,7 @@ class ChainableEvent<T> implements IChainableEvent<T> {
return new ChainableEvent(filterEvent(this._event, fn));
}
on(listener, thisArgs, disposables: IDisposable[]) {
on(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) {
return this._event(listener, thisArgs, disposables);
}
}
@@ -514,10 +514,10 @@ export function echo<T>(event: Event<T>, nextTick = false, buffer: T[] = []): Ev
emitter.fire(e);
});
const flush = (listener, thisArgs?) => buffer.forEach(e => listener.call(thisArgs, e));
const flush = (listener: (e: T) => any, thisArgs?: any) => buffer.forEach(e => listener.call(thisArgs, e));
const emitter = new Emitter<T>({
onListenerDidAdd(emitter, listener, thisArgs?) {
onListenerDidAdd(emitter, listener: (e: T) => any, thisArgs?: any) {
if (nextTick) {
setTimeout(() => flush(listener, thisArgs));
} else {
@@ -528,3 +528,21 @@ export function echo<T>(event: Event<T>, nextTick = false, buffer: T[] = []): Ev
return emitter.event;
}
export class Relay<T> implements IDisposable {
private emitter = new Emitter<T>();
readonly output: Event<T> = this.emitter.event;
private disposable: IDisposable = EmptyDisposable;
set input(event: Event<T>) {
this.disposable.dispose();
this.disposable = event(this.emitter.fire, this.emitter);
}
dispose() {
this.disposable.dispose();
this.emitter.dispose();
}
}

View File

@@ -109,6 +109,7 @@ function _matchesSubString(word: string, wordToMatchAgainst: string, i: number,
if (result = _matchesSubString(word, wordToMatchAgainst, i + 1, j + 1)) {
return join({ start: j, end: j + 1 }, result);
}
return null;
}
return _matchesSubString(word, wordToMatchAgainst, i, j + 1);
@@ -121,7 +122,7 @@ function isLower(code: number): boolean {
return CharCode.a <= code && code <= CharCode.z;
}
function isUpper(code: number): boolean {
export function isUpper(code: number): boolean {
return CharCode.A <= code && code <= CharCode.Z;
}
@@ -242,7 +243,13 @@ function isCamelCasePattern(word: string): boolean {
}
export function matchesCamelCase(word: string, camelCaseWord: string): IMatch[] {
if (!camelCaseWord || camelCaseWord.length === 0) {
if (!camelCaseWord) {
return null;
}
camelCaseWord = camelCaseWord.trim();
if (camelCaseWord.length === 0) {
return null;
}
@@ -414,20 +421,40 @@ function printTable(table: number[][], pattern: string, patternLen: number, word
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;
function isSeparatorAtPos(value: string, index: number): boolean {
if (index < 0 || index >= value.length) {
return false;
}
const code = value.charCodeAt(index);
switch (code) {
case CharCode.Underline:
case CharCode.Dash:
case CharCode.Period:
case CharCode.Space:
case CharCode.Slash:
case CharCode.Backslash:
case CharCode.SingleQuote:
case CharCode.DoubleQuote:
case CharCode.Colon:
return true;
default:
return false;
}
}
const _ws: { [ch: string]: boolean } = Object.create(null);
_ws[' '] = true;
_ws['\t'] = true;
function isWhitespaceAtPos(value: string, index: number): boolean {
if (index < 0 || index >= value.length) {
return false;
}
const code = value.charCodeAt(index);
switch (code) {
case CharCode.Space:
case CharCode.Tab:
return true;
default:
return false;
}
}
const enum Arrow { Top = 0b1, Diag = 0b10, Left = 0b100 }
@@ -445,7 +472,7 @@ export function fuzzyScore(pattern: string, word: string, patternMaxWhitespaceIg
patternMaxWhitespaceIgnore = patternLen;
}
while (patternStartPos < patternMaxWhitespaceIgnore) {
if (_ws[pattern[patternStartPos]]) {
if (isWhitespaceAtPos(pattern, patternStartPos)) {
patternStartPos += 1;
} else {
break;
@@ -481,8 +508,6 @@ export function fuzzyScore(pattern: string, word: string, patternMaxWhitespaceIg
// 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;
@@ -502,7 +527,7 @@ export function fuzzyScore(pattern: string, word: string, patternMaxWhitespaceIg
} else {
score = 5;
}
} else if (_seps[lastLowWordChar]) {
} else if (isSeparatorAtPos(lowWord, wordPos - 2)) {
// post separator: `foo <-> bar_foo`
score = 5;
@@ -542,8 +567,6 @@ export function fuzzyScore(pattern: string, word: string, patternMaxWhitespaceIg
_arrows[patternPos][wordPos] = Arrow.Diag;
}
}
lastLowWordChar = lowWordChar;
}
}
@@ -556,25 +579,26 @@ export function fuzzyScore(pattern: string, word: string, patternMaxWhitespaceIg
// _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;
_matchesCount = 0;
_topScore = -100;
_patternStartPos = patternStartPos;
_findAllMatches(patternLen, wordLen, 0, new LazyArray(), false);
if (_bucket.length === 0) {
if (_matchesCount === 0) {
return undefined;
}
return [_topScore, _bucket[0].toArray()];
return [_topScore, _topMatch.toArray()];
}
let _bucket: LazyArray[] = [];
let _matchesCount: number = 0;
let _topMatch: 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) {
if (_matchesCount >= 10 || total < -25) {
// stop when having already 10 results, or
// when a potential alignment as already 5 gaps
return;
@@ -645,11 +669,10 @@ function _findAllMatches(patternPos: number, wordPos: number, total: number, mat
// dynamically keep track of the current top score
// and insert the current best score at head, the rest at tail
_matchesCount += 1;
if (total > _topScore) {
_topScore = total;
_bucket.unshift(matches);
} else {
_bucket.push(matches);
_topMatch = matches;
}
}

View File

@@ -16,6 +16,11 @@ export interface IExpression {
[pattern: string]: boolean | SiblingClause | any;
}
export interface IRelativePattern {
base: string;
pattern: string;
}
export function getEmptyExpression(): IExpression {
return Object.create(null);
}
@@ -28,6 +33,8 @@ export interface SiblingClause {
when: string;
}
const GLOBSTAR = '**';
const GLOB_SPLIT = '/';
const PATH_REGEX = '[/\\\\]'; // any slash or backslash
const NO_PATH_REGEX = '[^/\\\\]'; // any non-slash and non-backslash
const ALL_FORWARD_SLASHES = /\//g;
@@ -103,10 +110,10 @@ function parseRegExp(pattern: string): string {
let regEx = '';
// Split up into segments for each slash found
let segments = splitGlobAware(pattern, '/');
let segments = splitGlobAware(pattern, GLOB_SPLIT);
// Special case where we only have globstars
if (segments.every(s => s === '**')) {
if (segments.every(s => s === GLOBSTAR)) {
regEx = '.*';
}
@@ -116,7 +123,7 @@ function parseRegExp(pattern: string): string {
segments.forEach((segment, index) => {
// Globstar is special
if (segment === '**') {
if (segment === GLOBSTAR) {
// if we have more than one globstar after another, just ignore it
if (!previousSegmentWasGlobStar) {
@@ -207,7 +214,7 @@ function parseRegExp(pattern: string): string {
}
// 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] !== '**') {
if (index < segments.length - 1 && segments[index + 1] !== GLOBSTAR) {
regEx += PATH_REGEX;
}
@@ -264,11 +271,19 @@ const NULL = function (): string {
return null;
};
function parsePattern(pattern: string, options: IGlobOptions): ParsedStringPattern {
if (!pattern) {
function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): ParsedStringPattern {
if (!arg1) {
return NULL;
}
// Handle IRelativePattern
let pattern: string;
if (typeof arg1 !== 'string') {
pattern = arg1.pattern;
} else {
pattern = arg1;
}
// Whitespace trimming
pattern = pattern.trim();
@@ -276,7 +291,7 @@ function parsePattern(pattern: string, options: IGlobOptions): ParsedStringPatte
const patternKey = `${pattern}_${!!options.trimForExclusions}`;
let parsedPattern = CACHE.get(patternKey);
if (parsedPattern) {
return parsedPattern;
return wrapRelativePattern(parsedPattern, arg1);
}
// Check for Trivias
@@ -304,7 +319,21 @@ function parsePattern(pattern: string, options: IGlobOptions): ParsedStringPatte
// Cache
CACHE.set(patternKey, parsedPattern);
return parsedPattern;
return wrapRelativePattern(parsedPattern, arg1);
}
function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | IRelativePattern): ParsedStringPattern {
if (typeof arg2 === 'string') {
return parsedPattern;
}
return function (path, basename) {
if (!paths.isEqualOrParent(path, arg2.base)) {
return null;
}
return parsedPattern(paths.relative(arg2.base, path), basename);
};
}
function trimForExclusions(pattern: string, options: IGlobOptions): string {
@@ -395,9 +424,9 @@ function toRegExp(pattern: string): ParsedStringPattern {
* - simple brace expansion ({js,ts} => js or ts)
* - character ranges (using [...])
*/
export function match(pattern: string, path: string): boolean;
export function match(pattern: string | IRelativePattern, 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 {
export function match(arg1: string | IExpression | IRelativePattern, path: string, siblingsFn?: () => string[]): any {
if (!arg1 || !path) {
return false;
}
@@ -413,16 +442,16 @@ export function match(arg1: string | IExpression, path: string, siblingsFn?: ()
* - simple brace expansion ({js,ts} => js or ts)
* - character ranges (using [...])
*/
export function parse(pattern: string, options?: IGlobOptions): ParsedPattern;
export function parse(pattern: string | IRelativePattern, options?: IGlobOptions): ParsedPattern;
export function parse(expression: IExpression, options?: IGlobOptions): ParsedExpression;
export function parse(arg1: string | IExpression, options: IGlobOptions = {}): any {
export function parse(arg1: string | IExpression | IRelativePattern, options: IGlobOptions = {}): any {
if (!arg1) {
return FALSE;
}
// Glob with String
if (typeof arg1 === 'string') {
const parsedPattern = parsePattern(arg1, options);
if (typeof arg1 === 'string' || isRelativePattern(arg1)) {
const parsedPattern = parsePattern(arg1 as string | IRelativePattern, options);
if (parsedPattern === NULL) {
return FALSE;
}
@@ -442,6 +471,12 @@ export function parse(arg1: string | IExpression, options: IGlobOptions = {}): a
return parsedExpression(<IExpression>arg1, options);
}
function isRelativePattern(obj: any): obj is IRelativePattern {
const rp = obj as IRelativePattern;
return typeof rp.base === 'string' && typeof rp.pattern === 'string';
}
/**
* Same as `parse`, but the ParsedExpression is guaranteed to return a Promise
*/

View File

@@ -30,7 +30,7 @@ export class Graph<T> {
}
roots(): Node<T>[] {
var ret: Node<T>[] = [];
const ret: Node<T>[] = [];
forEach(this._nodes, entry => {
if (isEmptyObject(entry.value.outgoing)) {
ret.push(entry.value);
@@ -40,7 +40,7 @@ export class Graph<T> {
}
traverse(start: T, inwards: boolean, callback: (data: T) => void): void {
var startNode = this.lookup(start);
const startNode = this.lookup(start);
if (!startNode) {
return;
}
@@ -48,18 +48,18 @@ export class Graph<T> {
}
private _traverse(node: Node<T>, inwards: boolean, seen: { [key: string]: boolean }, callback: (data: T) => void): void {
var key = this._hashFn(node.data);
const key = this._hashFn(node.data);
if (seen[key]) {
return;
}
seen[key] = true;
callback(node.data);
var nodes = inwards ? node.outgoing : node.incoming;
const 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),
const fromNode = this.lookupOrInsertNode(from),
toNode = this.lookupOrInsertNode(to);
fromNode.outgoing[this._hashFn(to)] = toNode;
@@ -67,7 +67,7 @@ export class Graph<T> {
}
removeNode(data: T): void {
var key = this._hashFn(data);
const key = this._hashFn(data);
delete this._nodes[key];
forEach(this._nodes, (entry) => {
delete entry.value.outgoing[key];

View File

@@ -6,7 +6,6 @@
'use strict';
import { equals } from 'vs/base/common/arrays';
import { marked } from 'vs/base/common/marked/marked';
export interface IMarkdownString {
value: string;
@@ -56,7 +55,7 @@ export function isEmptyMarkdownString(oneOrMany: IMarkdownString | IMarkdownStri
export function isMarkdownString(thing: any): thing is IMarkdownString {
if (thing instanceof MarkdownString) {
return true;
} else if (typeof thing === 'object') {
} else if (thing && typeof thing === 'object') {
return typeof (<IMarkdownString>thing).value === 'string'
&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === void 0);
}
@@ -93,16 +92,3 @@ export function removeMarkdownEscapes(text: string): string {
}
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

@@ -5,11 +5,15 @@
'use strict';
export interface IIterator<T> {
export interface IIterator<E> {
next(): { done: boolean, value: E };
}
export interface INextIterator<T> {
next(): T;
}
export class ArrayIterator<T> implements IIterator<T> {
export class ArrayIterator<T> implements INextIterator<T> {
private items: T[];
protected start: number;
@@ -73,16 +77,16 @@ export class ArrayNavigator<T> extends ArrayIterator<T> implements INavigator<T>
}
export class MappedIterator<T, R> implements IIterator<R> {
export class MappedIterator<T, R> implements INextIterator<R> {
constructor(protected iterator: IIterator<T>, protected fn: (item: T) => R) {
constructor(protected iterator: INextIterator<T>, protected fn: (item: T) => R) {
// noop
}
next() { return this.fn(this.iterator.next()); }
}
export interface INavigator<T> extends IIterator<T> {
export interface INavigator<T> extends INextIterator<T> {
current(): T;
previous(): T;
parent(): T;

View File

@@ -837,7 +837,7 @@ export function parse(text: string, errors: ParseError[] = [], options?: ParseOp
currentParent = previousParents.pop();
},
onArrayBegin: () => {
let array = [];
let array: any[] = [];
onValue(array);
previousParents.push(currentParent);
currentParent = array;

View File

@@ -6,6 +6,7 @@
export interface IJSONSchema {
id?: string;
$id?: string;
$schema?: string;
type?: string | string[];
title?: string;
@@ -17,19 +18,19 @@ export interface IJSONSchema {
additionalProperties?: boolean | IJSONSchema;
minProperties?: number;
maxProperties?: number;
dependencies?: IJSONSchemaMap | string[];
dependencies?: IJSONSchemaMap | { [prop: string]: string[] };
items?: IJSONSchema | IJSONSchema[];
minItems?: number;
maxItems?: number;
uniqueItems?: boolean;
additionalItems?: boolean;
additionalItems?: boolean | IJSONSchema;
pattern?: string;
minLength?: number;
maxLength?: number;
minimum?: number;
maximum?: number;
exclusiveMinimum?: boolean;
exclusiveMaximum?: boolean;
exclusiveMinimum?: boolean | number;
exclusiveMaximum?: boolean | number;
multipleOf?: number;
required?: string[];
$ref?: string;
@@ -40,11 +41,20 @@ export interface IJSONSchema {
enum?: any[];
format?: string;
// schema draft 06
const?: any;
contains?: IJSONSchema;
propertyNames?: IJSONSchema;
// VSCode extensions
defaultSnippets?: IJSONSchemaSnippet[]; // VSCode extension
errorMessage?: string; // VSCode extension
patternErrorMessage?: string; // VSCode extension
deprecationMessage?: string; // VSCode extension
enumDescriptions?: string[]; // VSCode extension
markdownEnumDescriptions?: string[]; // VSCode extension
markdownDescription?: string; // VSCode extension
doNotSuggest?: boolean; // VSCode extension
}
export interface IJSONSchemaMap {

View File

@@ -17,10 +17,10 @@ export interface ILabelProvider {
getLabel(element: any): string;
}
export interface IRootProvider {
getRoot(resource: URI): URI;
export interface IWorkspaceFolderProvider {
getWorkspaceFolder(resource: URI): { uri: URI };
getWorkspace(): {
roots: URI[];
folders: { uri: URI }[];
};
}
@@ -28,7 +28,7 @@ export interface IUserHomeProvider {
userHome: string;
}
export function getPathLabel(resource: URI | string, rootProvider?: IRootProvider, userHomeProvider?: IUserHomeProvider): string {
export function getPathLabel(resource: URI | string, rootProvider?: IWorkspaceFolderProvider, userHomeProvider?: IUserHomeProvider): string {
if (!resource) {
return null;
}
@@ -37,20 +37,24 @@ export function getPathLabel(resource: URI | string, rootProvider?: IRootProvide
resource = URI.file(resource);
}
if (resource.scheme !== 'file' && resource.scheme !== 'untitled') {
return resource.authority + resource.path;
}
// return early if we can resolve a relative path label from the root
const baseResource = rootProvider ? rootProvider.getRoot(resource) : null;
const baseResource = rootProvider ? rootProvider.getWorkspaceFolder(resource) : null;
if (baseResource) {
const hasMultipleRoots = rootProvider.getWorkspace().roots.length > 1;
const hasMultipleRoots = rootProvider.getWorkspace().folders.length > 1;
let pathLabel: string;
if (isEqual(baseResource.fsPath, resource.fsPath, !platform.isLinux /* ignorecase */)) {
if (isEqual(baseResource.uri.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);
pathLabel = normalize(ltrim(resource.fsPath.substr(baseResource.uri.fsPath.length), nativeSep), true);
}
if (hasMultipleRoots) {
const rootName = basename(baseResource.fsPath);
const rootName = basename(baseResource.uri.fsPath);
pathLabel = pathLabel ? join(rootName, pathLabel) : rootName; // always show root basename if there are multiple
}
@@ -58,8 +62,8 @@ export function getPathLabel(resource: URI | string, rootProvider?: IRootProvide
}
// 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);
if (hasDriveLetter(resource.fsPath)) {
return normalize(normalizeDriveLetter(resource.fsPath), true);
}
// normalize and tildify (macOS, Linux only)
@@ -71,6 +75,18 @@ export function getPathLabel(resource: URI | string, rootProvider?: IRootProvide
return res;
}
function hasDriveLetter(path: string): boolean {
return platform.isWindows && path && path[1] === ':';
}
export function normalizeDriveLetter(path: string): string {
if (hasDriveLetter(path)) {
return path.charAt(0).toUpperCase() + path.slice(1);
}
return path;
}
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)}`;

View File

@@ -15,6 +15,11 @@ export interface IDisposable {
dispose(): void;
}
export function isDisposable<E extends object>(thing: E): thing is E & IDisposable {
return typeof (<IDisposable><any>thing).dispose === 'function'
&& (<IDisposable><any>thing).dispose.length === 0;
}
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[];
@@ -68,22 +73,6 @@ export abstract class Disposable implements IDisposable {
}
}
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;
}

View File

@@ -0,0 +1,122 @@
/*---------------------------------------------------------------------------------------------
* 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 { IIterator } from 'vs/base/common/iterator';
class Node<E> {
element: E;
next: Node<E>;
prev: Node<E>;
constructor(element: E) {
this.element = element;
}
}
export class LinkedList<E> {
private _first: Node<E>;
private _last: Node<E>;
isEmpty(): boolean {
return !this._first;
}
unshift(element: E) {
return this.insert(element, false);
}
push(element: E) {
return this.insert(element, true);
}
private insert(element: E, atTheEnd: boolean) {
const newNode = new Node(element);
if (!this._first) {
this._first = newNode;
this._last = newNode;
} else if (atTheEnd) {
// push
const oldLast = this._last;
this._last = newNode;
newNode.prev = oldLast;
oldLast.next = newNode;
} else {
// unshift
const oldFirst = this._first;
this._first = newNode;
newNode.next = oldFirst;
oldFirst.prev = newNode;
}
return () => {
for (let candidate = this._first; candidate instanceof Node; candidate = candidate.next) {
if (candidate !== newNode) {
continue;
}
if (candidate.prev && candidate.next) {
// middle
let anchor = candidate.prev;
anchor.next = candidate.next;
candidate.next.prev = anchor;
} else if (!candidate.prev && !candidate.next) {
// only node
this._first = undefined;
this._last = undefined;
} else if (!candidate.next) {
// last
this._last = this._last.prev;
this._last.next = undefined;
} else if (!candidate.prev) {
// first
this._first = this._first.next;
this._first.prev = undefined;
}
// done
break;
}
};
}
iterator(): IIterator<E> {
let _done: boolean;
let _value: E;
let element = {
get done() { return _done; },
get value() { return _value; }
};
let node = this._first;
return {
next(): { done: boolean; value: E } {
if (!node) {
_done = true;
_value = undefined;
} else {
_done = false;
_value = node.element;
node = node.next;
}
return element;
}
};
}
toArray(): E[] {
let result: E[] = [];
for (let node = this._first; node instanceof Node; node = node.next) {
result.push(node.element);
}
return result;
}
}

View File

@@ -220,110 +220,315 @@ export class BoundedMap<T> {
}
}
// --- trie'ish datastructure
export interface IKeyIterator {
reset(key: string): this;
next(): this;
join(parts: string[]): string;
class Node<E> {
element?: E;
readonly children = new Map<string, Node<E>>();
hasNext(): boolean;
cmp(a: string): number;
value(): string;
}
/**
* 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> {
export class StringIterator implements IKeyIterator {
static PathSplitter = (s: string) => s.split(/[\\/]/).filter(s => !!s);
private _value: string = '';
private _pos: number = 0;
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));
reset(key: string): this {
this._value = key;
this._pos = 0;
return this;
}
insert(path: string, element: E): void {
const parts = this._splitter(path);
let i = 0;
next(): this {
this._pos += 1;
return this;
}
// find insertion node
let node = this._root;
for (; i < parts.length; i++) {
let child = node.children.get(parts[i]);
if (child) {
node = child;
continue;
join(parts: string[]): string {
return parts.join('');
}
hasNext(): boolean {
return this._pos < this._value.length - 1;
}
cmp(a: string): number {
let aCode = a.charCodeAt(0);
let thisCode = this._value.charCodeAt(this._pos);
return aCode - thisCode;
}
value(): string {
return this._value[this._pos];
}
}
export class PathIterator implements IKeyIterator {
private static _fwd = '/'.charCodeAt(0);
private static _bwd = '\\'.charCodeAt(0);
private _value: string;
private _from: number;
private _to: number;
reset(key: string): this {
this._value = key.replace(/\\$|\/$/, '');
this._from = 0;
this._to = 0;
return this.next();
}
hasNext(): boolean {
return this._to < this._value.length;
}
join(parts: string[]): string {
return parts.join('/');
}
next(): this {
// this._data = key.split(/[\\/]/).filter(s => !!s);
this._from = this._to;
let justSeps = true;
for (; this._to < this._value.length; this._to++) {
const ch = this._value.charCodeAt(this._to);
if (ch === PathIterator._fwd || ch === PathIterator._bwd) {
if (justSeps) {
this._from++;
} else {
break;
}
} else {
justSeps = false;
}
break;
}
return this;
}
cmp(a: string): number {
let aPos = 0;
let aLen = a.length;
let thisPos = this._from;
while (aPos < aLen && thisPos < this._to) {
let cmp = a.charCodeAt(aPos) - this._value.charCodeAt(thisPos);
if (cmp !== 0) {
return cmp;
}
aPos += 1;
thisPos += 1;
}
// create new nodes
let newNode: Node<E>;
for (; i < parts.length; i++) {
newNode = new Node<E>();
node.children.set(parts[i], newNode);
node = newNode;
if (aLen === this._to - this._from) {
return 0;
} else if (aPos < aLen) {
return -1;
} else {
return 1;
}
}
value(): string {
return this._value.substring(this._from, this._to);
}
}
class TernarySearchTreeNode<E> {
str: string;
element: E;
left: TernarySearchTreeNode<E>;
mid: TernarySearchTreeNode<E>;
right: TernarySearchTreeNode<E>;
isEmpty(): boolean {
return !this.left && !this.mid && !this.right && !this.element;
}
}
export class TernarySearchTree<E> {
static forPaths<E>(): TernarySearchTree<E> {
return new TernarySearchTree<E>(new PathIterator());
}
static forStrings<E>(): TernarySearchTree<E> {
return new TernarySearchTree<E>(new StringIterator());
}
private _iter: IKeyIterator;
private _root: TernarySearchTreeNode<E>;
constructor(segments: IKeyIterator) {
this._iter = segments;
}
clear(): void {
this._root = undefined;
}
set(key: string, element: E): void {
let iter = this._iter.reset(key);
let node: TernarySearchTreeNode<E>;
if (!this._root) {
this._root = new TernarySearchTreeNode<E>();
this._root.str = iter.value();
}
node = this._root;
while (true) {
let val = iter.cmp(node.str);
if (val > 0) {
// left
if (!node.left) {
node.left = new TernarySearchTreeNode<E>();
node.left.str = iter.value();
}
node = node.left;
} else if (val < 0) {
// right
if (!node.right) {
node.right = new TernarySearchTreeNode<E>();
node.right.str = iter.value();
}
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
if (!node.mid) {
node.mid = new TernarySearchTreeNode<E>();
node.mid.str = iter.value();
}
node = node.mid;
} else {
break;
}
}
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) {
get(key: string): E {
let iter = this._iter.reset(key);
let node = this._root;
while (node) {
let val = iter.cmp(node.str);
if (val > 0) {
// left
node = node.left;
} else if (val < 0) {
// right
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
node = node.mid;
} else {
break;
}
if (node.element) {
lastNode = node;
}
children = node.children;
}
return node ? node.element : undefined;
}
delete(key: string): void {
this._delete(this._root, this._iter.reset(key));
}
private _delete(node: TernarySearchTreeNode<E>, iter: IKeyIterator): TernarySearchTreeNode<E> {
if (!node) {
return undefined;
}
const cmp = iter.cmp(node.str);
if (cmp > 0) {
// left
node.left = this._delete(node.left, iter);
} else if (cmp < 0) {
// right
node.right = this._delete(node.right, iter);
} else if (iter.hasNext()) {
// mid
node.mid = this._delete(node.mid, iter.next());
} else {
// remove element
node.element = undefined;
}
// return the last matching node
// that had an element
if (lastNode) {
return lastNode.element;
return node.isEmpty() ? undefined : node;
}
findSubstr(key: string): E {
let iter = this._iter.reset(key);
let node = this._root;
let candidate: E;
while (node) {
let val = iter.cmp(node.str);
if (val > 0) {
// left
node = node.left;
} else if (val < 0) {
// right
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
candidate = node.element || candidate;
node = node.mid;
} else {
break;
}
}
return node && node.element || candidate;
}
findSuperstr(key: string): TernarySearchTree<E> {
let iter = this._iter.reset(key);
let node = this._root;
while (node) {
let val = iter.cmp(node.str);
if (val > 0) {
// left
node = node.left;
} else if (val < 0) {
// right
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
node = node.mid;
} else {
// collect
if (!node.mid) {
return undefined;
}
let ret = new TernarySearchTree<E>(this._iter);
ret._root = node.mid;
return ret;
}
}
return undefined;
}
findSuperstr(path: string): TrieMap<E> {
const parts = this._splitter(path);
forEach(callback: (value: E, index: string) => any) {
this._forEach(this._root, [], callback);
}
let { children } = this._root;
let node: Node<E>;
for (const part of parts) {
node = children.get(part);
if (!node) {
return undefined;
}
children = node.children;
private _forEach(node: TernarySearchTreeNode<E>, parts: string[], callback: (value: E, index: string) => any) {
if (!node) {
return;
}
const result = new TrieMap<E>(this._splitter);
result._root = node;
return result;
this._forEach(node.left, parts, callback);
this._forEach(node.right, parts, callback);
let newParts = parts.slice();
newParts.push(node.str);
if (node.element) {
callback(node.element, this._iter.join(newParts));
}
this._forEach(node.mid, newParts, callback);
}
}

View File

@@ -38,5 +38,7 @@ export namespace Schemas {
export const file: string = 'file';
export const mailto: string = 'mailto';
export const untitled: string = 'untitled';
}

View File

@@ -12,7 +12,8 @@ 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;
let from: number;
let to: number;
if (types.isNumber(toOrCallback)) {
from = fromOrTo;
@@ -23,10 +24,10 @@ export function count(fromOrTo: number, toOrCallback?: NumberCallback | number,
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;
const op = from <= to ? (i: number) => i + 1 : (i: number) => i - 1;
const 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)) {
for (let i = from; cmp(i, to); i = op(i)) {
callback(i);
}
}
@@ -34,8 +35,8 @@ export function count(fromOrTo: number, toOrCallback?: NumberCallback | number,
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);
const result: number[] = [];
const fn = (i: number) => result.push(i);
if (types.isUndefined(to)) {
count(fromOrTo, fn);
@@ -45,3 +46,12 @@ export function countToArray(fromOrTo: number, to?: number): number[] {
return result;
}
export function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
export function rot(index: number, modulo: number): number {
return (modulo + (index % modulo)) % modulo;
}

View File

@@ -16,7 +16,7 @@ export function clone<T>(obj: T): T {
return obj as any;
}
const result = (Array.isArray(obj)) ? <any>[] : <any>{};
Object.keys(obj).forEach((key) => {
Object.keys(obj).forEach(key => {
if (obj[key] && typeof obj[key] === 'object') {
result[key] = clone(obj[key]);
} else {
@@ -31,7 +31,7 @@ export function deepClone<T>(obj: T): T {
return obj;
}
const result = (Array.isArray(obj)) ? <any>[] : <any>{};
Object.getOwnPropertyNames(obj).forEach((key) => {
Object.getOwnPropertyNames(obj).forEach(key => {
if (obj[key] && typeof obj[key] === 'object') {
result[key] = deepClone(obj[key]);
} else {
@@ -93,7 +93,7 @@ export function mixin(destination: any, source: any, overwrite: boolean = true):
}
if (isObject(source)) {
Object.keys(source).forEach((key) => {
Object.keys(source).forEach(key => {
if (key in destination) {
if (overwrite) {
if (isObject(destination[key]) && isObject(source[key])) {
@@ -111,7 +111,7 @@ export function mixin(destination: any, source: any, overwrite: boolean = true):
}
export function assign(destination: any, ...sources: any[]): any {
sources.forEach(source => Object.keys(source).forEach((key) => destination[key] = source[key]));
sources.forEach(source => Object.keys(source).forEach(key => destination[key] = source[key]));
return destination;
}

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 * as paths from 'vs/base/common/paths';
import uri from 'vs/base/common/uri';
import { equalsIgnoreCase } from 'vs/base/common/strings';
export function basenameOrAuthority(resource: uri): string {
return paths.basename(resource.fsPath) || resource.authority;
}
export function isEqualOrParent(first: uri, second: uri, ignoreCase?: boolean): boolean {
if (first.scheme === second.scheme && first.authority === second.authority) {
return paths.isEqualOrParent(first.fsPath, second.fsPath, ignoreCase);
}
return false;
}
export function isEqual(first: uri, second: uri, ignoreCase?: boolean): boolean {
const identityEquals = (first === second);
if (identityEquals) {
return true;
}
if (!first || !second) {
return false;
}
if (ignoreCase) {
return equalsIgnoreCase(first.toString(), second.toString());
}
return first.toString() === second.toString();
}
export function dirname(resource: uri): uri {
return resource.with({
path: paths.dirname(resource.path)
});
}

View File

@@ -1,134 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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

@@ -16,12 +16,12 @@ enum Severity {
namespace Severity {
var _error = 'error',
_warning = 'warning',
_warn = 'warn',
_info = 'info';
const _error = 'error';
const _warning = 'warning';
const _warn = 'warn';
const _info = 'info';
var _displayStrings: { [value: number]: string; } = Object.create(null);
const _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");

View File

@@ -221,7 +221,7 @@ export function createRegExp(searchString: string, isRegex: boolean, options: Re
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 === '$') {
if (regexp.source === '^' || regexp.source === '^$' || regexp.source === '$' || regexp.source === '^\\s*$') {
return false;
}
@@ -231,6 +231,10 @@ export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean {
return (match && <any>regexp.lastIndex === 0);
}
export function regExpContainsBackreference(regexpValue: string): boolean {
return !!regexpValue.match(/([^\\]|^)(\\\\)*\\\d+/);
}
/**
* The normalize() method returns the Unicode Normalization Form of a given string. The form will be
* the Normalization Form Canonical Composition.
@@ -238,9 +242,19 @@ export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean {
* @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
const nfcCache = new BoundedMap<string>(10000); // bounded to 10000 elements
export function normalizeNFC(str: string): string {
return normalize(str, 'NFC', nfcCache);
}
const nfdCache = new BoundedMap<string>(10000); // bounded to 10000 elements
export function normalizeNFD(str: string): string {
return normalize(str, 'NFD', nfdCache);
}
const nonAsciiCharactersPattern = /[^\u0000-\u0080]/;
function normalize(str: string, form: string, normalizedCache: BoundedMap<string>): string {
if (!canNormalize || !str) {
return str;
}
@@ -252,7 +266,7 @@ export function normalizeNFC(str: string): string {
let res: string;
if (nonAsciiCharactersPattern.test(str)) {
res = (<any>str).normalize('NFC');
res = (<any>str).normalize(form);
} else {
res = str;
}
@@ -705,6 +719,10 @@ export function startsWithUTF8BOM(str: string): boolean {
return (str && str.length > 0 && str.charCodeAt(0) === CharCode.UTF8_BOM);
}
export function stripUTF8BOM(str: string): string {
return startsWithUTF8BOM(str) ? str.substr(1) : str;
}
/**
* Appends two strings. If the appended result is longer than maxLength,
* trims the start of the result and replaces it with '...'.
@@ -735,3 +753,35 @@ export function repeat(s: string, count: number): string {
}
return result;
}
/**
* Checks if the characters of the provided query string are included in the
* target string. The characters do not have to be contiguous within the string.
*/
export function fuzzyContains(target: string, query: string): boolean {
if (!target || !query) {
return false; // return early if target or query are undefined
}
if (target.length < query.length) {
return false; // impossible for query to be contained in target
}
const queryLen = query.length;
const targetLower = target.toLowerCase();
let index = 0;
let lastIndexOf = -1;
while (index < queryLen) {
let indexOf = targetLower.indexOf(query[index], lastIndexOf + 1);
if (indexOf < 0) {
return false;
}
lastIndexOf = indexOf;
index++;
}
return true;
}

View File

@@ -21,6 +21,42 @@ function encodeNoop(str: string): string {
}
const _schemePattern = /^\w[\w\d+.-]*$/;
const _singleSlashStart = /^\//;
const _doubleSlashStart = /^\/\//;
function _validateUri(ret: URI): void {
// scheme, https://tools.ietf.org/html/rfc3986#section-3.1
// ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
if (ret.scheme && !_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 (!_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 (_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 ("//")');
}
}
}
}
const _empty = '';
const _slash = '/';
const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
const _driveLetterPath = /^\/[a-zA-Z]:/;
const _upperCaseDrive = /^(\/)?([A-Z]:)/;
const _driveLetter = /^[a-zA-Z]:/;
/**
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
* This class is a simple parser which creates the basic component paths
@@ -37,7 +73,7 @@ function encodeNoop(str: string): string {
*
*
*/
export default class URI {
export default class URI implements UriComponents {
static isUri(thing: any): thing is URI {
if (thing instanceof URI) {
@@ -53,12 +89,6 @@ export default class URI {
&& 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.
@@ -86,21 +116,38 @@ export default class URI {
*/
readonly fragment: string;
private _formatted: string = null;
private _fsPath: string = null;
/**
* @internal
*/
protected constructor(scheme: string, authority: string, path: string, query: string, fragment: string);
/**
* @internal
*/
private constructor(scheme: string, authority: string, path: string, query: string, fragment: string) {
protected constructor(components: UriComponents);
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;
/**
* @internal
*/
protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string) {
this._validate(this);
if (typeof schemeOrData === 'object') {
this.scheme = schemeOrData.scheme || _empty;
this.authority = schemeOrData.authority || _empty;
this.path = schemeOrData.path || _empty;
this.query = schemeOrData.query || _empty;
this.fragment = schemeOrData.fragment || _empty;
// no validation because it's this URI
// that creates uri components.
// _validateUri(this);
} else {
this.scheme = schemeOrData || _empty;
this.authority = authority || _empty;
this.path = path || _empty;
this.query = query || _empty;
this.fragment = fragment || _empty;
_validateUri(this);
}
}
// ---- filesystem path -----------------------
@@ -112,24 +159,7 @@ export default class URI {
* 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;
return _makeFsPath(this);
}
// ---- modify to new -------------------------
@@ -176,60 +206,66 @@ export default class URI {
return this;
}
return new URI(scheme, authority, path, query, fragment);
return new _URI(scheme, authority, path, query, fragment);
}
// ---- parse & validate ------------------------
public static parse(value: string): URI {
const match = URI._regexp.exec(value);
const match = _regexp.exec(value);
if (!match) {
return new URI(URI._empty, URI._empty, URI._empty, URI._empty, URI._empty);
return new _URI(_empty, _empty, _empty, _empty, _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),
return new _URI(
match[2] || _empty,
decodeURIComponent(match[4] || _empty),
decodeURIComponent(match[5] || _empty),
decodeURIComponent(match[7] || _empty),
decodeURIComponent(match[9] || _empty),
);
}
public static file(path: string): URI {
let authority = URI._empty;
let authority = _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);
path = path.replace(/\\/g, _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 (path[0] === _slash && path[1] === _slash) {
let idx = path.indexOf(_slash, 2);
if (idx === -1) {
authority = path.substring(2);
path = URI._empty;
path = _slash;
} else {
authority = path.substring(2, idx);
path = path.substring(idx);
path = path.substring(idx) || _slash;
}
}
// Ensure that path starts with a slash
// or that it is at least a slash
if (path[0] !== URI._slash) {
path = URI._slash + path;
if (_driveLetter.test(path)) {
path = _slash + path;
} else if (path[0] !== _slash) {
// tricky -> makes invalid paths
// but otherwise we have to stop
// allowing relative paths...
path = _slash + path;
}
return new URI('file', authority, path, URI._empty, URI._empty);
return new _URI('file', authority, path, _empty, _empty);
}
public static from(components: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
return new URI(
return new _URI(
components.scheme,
components.authority,
components.path,
@@ -238,35 +274,6 @@ export default class URI {
);
}
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 ---------------------------
/**
@@ -274,82 +281,14 @@ export default class URI {
* @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);
}
return _asFormatted(this, skipEncoding);
}
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 {
public toJSON(): object {
const res = <UriState>{
$mid: 1,
fsPath: this.fsPath,
external: this.toString(),
$mid: 1
};
if (this.path) {
@@ -375,21 +314,15 @@ export default class URI {
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
);
static revive(data: UriComponents | any): URI {
let result = new _URI(data);
result._fsPath = (<UriState>data).fsPath;
result._formatted = (<UriState>data).external;
return result;
}
}
interface UriComponents {
export interface UriComponents {
scheme: string;
authority: string;
path: string;
@@ -402,3 +335,129 @@ interface UriState extends UriComponents {
fsPath: string;
external: string;
}
// tslint:disable-next-line:class-name
class _URI extends URI {
_formatted: string = null;
_fsPath: string = null;
get fsPath(): string {
if (!this._fsPath) {
this._fsPath = _makeFsPath(this);
}
return this._fsPath;
}
public toString(skipEncoding: boolean = false): string {
if (!skipEncoding) {
if (!this._formatted) {
this._formatted = _asFormatted(this, false);
}
return this._formatted;
} else {
// we don't cache that
return _asFormatted(this, true);
}
}
}
/**
* Compute `fsPath` for the given uri
* @param uri
*/
function _makeFsPath(uri: URI): string {
let value: string;
if (uri.authority && uri.path && uri.scheme === 'file') {
// unc path: file://shares/c$/far/boo
value = `//${uri.authority}${uri.path}`;
} else if (_driveLetterPath.test(uri.path)) {
// windows drive letter: file:///c:/far/boo
value = uri.path[1].toLowerCase() + uri.path.substr(2);
} else {
// other path
value = uri.path;
}
if (platform.isWindows) {
value = value.replace(/\//g, '\\');
}
return value;
}
/**
* Create the external version of a uri
*/
function _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) {
let idx = authority.indexOf('@');
if (idx !== -1) {
const userinfo = authority.substr(0, idx);
authority = authority.substr(idx + 1);
idx = userinfo.indexOf(':');
if (idx === -1) {
parts.push(encoder(userinfo));
} else {
parts.push(encoder(userinfo.substr(0, idx)), ':', encoder(userinfo.substr(idx + 1)));
}
parts.push('@');
}
authority = authority.toLowerCase();
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 = _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(_slash, lastIdx);
if (idx === -1) {
parts.push(encoder(path.substring(lastIdx)));
break;
}
parts.push(encoder(path.substring(lastIdx, idx)), _slash);
lastIdx = idx + 1;
};
}
if (query) {
parts.push('?', encoder(query));
}
if (fragment) {
parts.push('#', encoder(fragment));
}
return parts.join(_empty);
}

View File

@@ -13,7 +13,7 @@ export declare class Promise<T = any, TProgress = any> {
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason: any) => void,
progress: (progress: TProgress) => void) => void,
oncancel?: () => void);
oncancel?: () => void);
public then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
@@ -29,7 +29,7 @@ export declare class Promise<T = any, TProgress = any> {
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, SomePromise extends PromiseLike<T>>(value: SomePromise): SomePromise;
public static as<T>(value: T): Promise<T>;
public static is(value: any): value is PromiseLike<any>;
@@ -49,7 +49,7 @@ export declare class Promise<T = any, TProgress = any> {
/**
* @internal
*/
public static addEventListener(event: 'error', promiseErrorHandler: (e: IPromiseError) => void);
public static addEventListener(event: 'error', promiseErrorHandler: (e: IPromiseError) => void): void;
}
export type TValueCallback<T = any> = (value: T | PromiseLike<T>) => void;

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Promise as WinJSPromise } from './winjs.base';
/**
* A polyfill for the native promises. The implementation is based on
* WinJS promises but tries to gap differences between winjs promises
* and native promises.
*/
export class PolyfillPromise<T = any> implements Promise<T> {
static all(thenables: Thenable<any>[]): PolyfillPromise {
return new PolyfillPromise(WinJSPromise.join(thenables).then(null, values => {
// WinJSPromise returns a sparse array whereas
// native promises return the *first* error
for (var key in values) {
if (values.hasOwnProperty(key)) {
return values[key];
}
}
}));
}
static race(thenables: Thenable<any>[]): PolyfillPromise {
// WinJSPromise returns `{ key: <index/key>, value: <promise> }`
// from the `any` call and Promise.race just wants the value
return new PolyfillPromise(WinJSPromise.any(thenables).then(entry => entry.value, err => err.value));
}
static resolve(value): PolyfillPromise {
return new PolyfillPromise(WinJSPromise.wrap(value));
}
static reject(value): PolyfillPromise {
return new PolyfillPromise(WinJSPromise.wrapError(value));
}
private _winjsPromise: WinJSPromise;
constructor(winjsPromise: WinJSPromise);
constructor(callback: (resolve: (value?: T) => void, reject: (err?: any) => void) => any);
constructor(callback: WinJSPromise | ((resolve: (value?: T) => void, reject: (err?: any) => void) => any)) {
if (WinJSPromise.is(callback)) {
this._winjsPromise = callback;
} else {
this._winjsPromise = new WinJSPromise((resolve, reject) => {
let initializing = true;
callback(function (value) {
if (!initializing) {
resolve(value);
} else {
setImmediate(resolve, value);
}
}, function (err) {
if (!initializing) {
reject(err);
} else {
setImmediate(reject, err);
}
});
initializing = false;
});
}
}
then(onFulfilled?: any, onRejected?: any): PolyfillPromise {
return new PolyfillPromise(this._winjsPromise.then(onFulfilled, onRejected));
}
catch(onRejected?: any): PolyfillPromise {
return new PolyfillPromise(this._winjsPromise.then(null, onRejected));
}
}

View File

@@ -242,7 +242,7 @@ export class SimpleWorkerClient<T> extends Disposable {
loaderConfiguration
]);
this._onModuleLoaded.then((availableMethods: string[]) => {
let proxy = <T><any>{};
let proxy = <T>{};
for (let i = 0; i < availableMethods.length; i++) {
proxy[availableMethods[i]] = createProxyMethod(availableMethods[i], proxyMethodRequest);
}