Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,254 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
export interface ITelemetryData {
readonly from?: string;
readonly target?: string;
[key: string]: any;
}
export type WorkbenchActionExecutedClassification = {
id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
from: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
export type WorkbenchActionExecutedEvent = {
id: string;
from: string;
};
export interface IAction extends IDisposable {
readonly id: string;
label: string;
tooltip: string;
class: string | undefined;
enabled: boolean;
checked: boolean;
run(event?: any): Promise<any>;
}
export interface IActionRunner extends IDisposable {
run(action: IAction, context?: any): Promise<any>;
readonly onDidRun: Event<IRunEvent>;
readonly onDidBeforeRun: Event<IRunEvent>;
}
export interface IActionViewItem extends IDisposable {
actionRunner: IActionRunner;
setActionContext(context: any): void;
render(element: any /* HTMLElement */): void;
isEnabled(): boolean;
focus(fromRight?: boolean): void; // TODO@isidorn what is this?
blur(): void;
}
export interface IActionViewItemProvider {
(action: IAction): IActionViewItem | undefined;
}
export interface IActionChangeEvent {
readonly label?: string;
readonly tooltip?: string;
readonly class?: string;
readonly enabled?: boolean;
readonly checked?: boolean;
}
export class Action extends Disposable implements IAction {
protected _onDidChange = this._register(new Emitter<IActionChangeEvent>());
readonly onDidChange: Event<IActionChangeEvent> = this._onDidChange.event;
protected readonly _id: string;
protected _label: string;
protected _tooltip: string | undefined;
protected _cssClass: string | undefined;
protected _enabled: boolean = true;
protected _checked: boolean = false;
protected readonly _actionCallback?: (event?: any) => Promise<any>;
constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Promise<any>) {
super();
this._id = id;
this._label = label;
this._cssClass = cssClass;
this._enabled = enabled;
this._actionCallback = actionCallback;
}
get id(): string {
return this._id;
}
get label(): string {
return this._label;
}
set label(value: string) {
this._setLabel(value);
}
private _setLabel(value: string): void {
if (this._label !== value) {
this._label = value;
this._onDidChange.fire({ label: value });
}
}
get tooltip(): string {
return this._tooltip || '';
}
set tooltip(value: string) {
this._setTooltip(value);
}
protected _setTooltip(value: string): void {
if (this._tooltip !== value) {
this._tooltip = value;
this._onDidChange.fire({ tooltip: value });
}
}
get class(): string | undefined {
return this._cssClass;
}
set class(value: string | undefined) {
this._setClass(value);
}
protected _setClass(value: string | undefined): void {
if (this._cssClass !== value) {
this._cssClass = value;
this._onDidChange.fire({ class: value });
}
}
get enabled(): boolean {
return this._enabled;
}
set enabled(value: boolean) {
this._setEnabled(value);
}
protected _setEnabled(value: boolean): void {
if (this._enabled !== value) {
this._enabled = value;
this._onDidChange.fire({ enabled: value });
}
}
get checked(): boolean {
return this._checked;
}
set checked(value: boolean) {
this._setChecked(value);
}
protected _setChecked(value: boolean): void {
if (this._checked !== value) {
this._checked = value;
this._onDidChange.fire({ checked: value });
}
}
run(event?: any, _data?: ITelemetryData): Promise<any> {
if (this._actionCallback) {
return this._actionCallback(event);
}
return Promise.resolve(true);
}
}
export interface IRunEvent {
readonly action: IAction;
readonly result?: any;
readonly error?: any;
}
export class ActionRunner extends Disposable implements IActionRunner {
private _onDidBeforeRun = this._register(new Emitter<IRunEvent>());
readonly onDidBeforeRun: Event<IRunEvent> = this._onDidBeforeRun.event;
private _onDidRun = this._register(new Emitter<IRunEvent>());
readonly onDidRun: Event<IRunEvent> = this._onDidRun.event;
async run(action: IAction, context?: any): Promise<any> {
if (!action.enabled) {
return Promise.resolve(null);
}
this._onDidBeforeRun.fire({ action: action });
try {
const result = await this.runAction(action, context);
this._onDidRun.fire({ action: action, result: result });
} catch (error) {
this._onDidRun.fire({ action: action, error: error });
}
}
protected runAction(action: IAction, context?: any): Promise<any> {
const res = context ? action.run(context) : action.run();
return Promise.resolve(res);
}
}
export class RadioGroup extends Disposable {
constructor(readonly actions: Action[]) {
super();
for (const action of actions) {
this._register(action.onDidChange(e => {
if (e.checked && action.checked) {
for (const candidate of actions) {
if (candidate !== action) {
candidate.checked = false;
}
}
}
}));
}
}
}
export class Separator extends Action {
static readonly ID = 'vs.actions.separator';
constructor(label?: string) {
super(Separator.ID, label, label ? 'separator text' : 'separator');
this.checked = false;
this.enabled = false;
}
}
export class SubmenuAction extends Action {
get actions(): IAction[] {
return this._actions;
}
constructor(id: string, label: string, private _actions: IAction[], cssClass?: string) {
super(id, label, cssClass, true);
}
}
export class EmptySubmenuAction extends Action {
static readonly ID = 'vs.actions.empty';
constructor() {
super(EmptySubmenuAction.ID, nls.localize('submenu.empty', '(empty)'), undefined, false);
}
}

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
/**
* @deprecated use `FileAccess.asFileUri(relativePath, requireFn).fsPath`
*/
export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string {
return getUriFromAmdModule(requirefn, relativePath).fsPath;
}
/**
* @deprecated use `FileAccess.asFileUri()` for node.js contexts or `FileAccess.asBrowserUri` for browser contexts.
*/
export function getUriFromAmdModule(requirefn: typeof require, relativePath: string): URI {
return URI.parse(requirefn.toUrl(relativePath));
}

View File

@@ -0,0 +1,590 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
import { ISplice } from 'vs/base/common/sequence';
/**
* 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: ArrayLike<T>, n: number = 0): T {
return array[array.length - (1 + n)];
}
export function tail2<T>(arr: T[]): [T[], T] {
if (arr.length === 0) {
throw new Error('Invalid tail call');
}
return [arr.slice(0, arr.length - 1), arr[arr.length - 1]];
}
export function equals<T>(one: ReadonlyArray<T> | undefined, other: ReadonlyArray<T> | undefined, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
if (one === other) {
return true;
}
if (!one || !other) {
return false;
}
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: ReadonlyArray<T>, key: T, comparator: (op1: T, op2: T) => number): number {
let low = 0,
high = array.length - 1;
while (low <= high) {
const mid = ((low + high) / 2) | 0;
const 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 findFirstInSorted<T>(array: ReadonlyArray<T>, p: (x: T) => boolean): number {
let low = 0, high = array.length;
if (high === 0) {
return 0; // no children
}
while (low < high) {
const mid = Math.floor((low + high) / 2);
if (p(array[mid])) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
type Compare<T> = (a: T, b: T) => number;
export function quickSelect<T>(nth: number, data: T[], compare: Compare<T>): T {
nth = nth | 0;
if (nth >= data.length) {
throw new TypeError('invalid index');
}
let pivotValue = data[Math.floor(data.length * Math.random())];
let lower: T[] = [];
let higher: T[] = [];
let pivots: T[] = [];
for (let value of data) {
const val = compare(value, pivotValue);
if (val < 0) {
lower.push(value);
} else if (val > 0) {
higher.push(value);
} else {
pivots.push(value);
}
}
if (nth < lower.length) {
return quickSelect(nth, lower, compare);
} else if (nth < lower.length + pivots.length) {
return pivots[0];
} else {
return quickSelect(nth - (lower.length + pivots.length), higher, compare);
}
}
/**
* 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: Compare<T>): T[] {
_sort(data, compare, 0, data.length - 1, []);
return data;
}
function _merge<T>(a: T[], compare: Compare<T>, lo: number, mid: number, hi: number, aux: T[]): void {
let leftIdx = lo, rightIdx = mid + 1;
for (let i = lo; i <= hi; i++) {
aux[i] = a[i];
}
for (let i = lo; i <= hi; i++) {
if (leftIdx > mid) {
// left side consumed
a[i] = aux[rightIdx++];
} else if (rightIdx > hi) {
// right side consumed
a[i] = aux[leftIdx++];
} else if (compare(aux[rightIdx], aux[leftIdx]) < 0) {
// right element is less -> comes first
a[i] = aux[rightIdx++];
} else {
// left element comes first (less or equal)
a[i] = aux[leftIdx++];
}
}
}
function _sort<T>(a: T[], compare: Compare<T>, lo: number, hi: number, aux: T[]) {
if (hi <= lo) {
return;
}
const mid = lo + ((hi - lo) / 2) | 0;
_sort(a, compare, lo, mid, aux);
_sort(a, compare, mid + 1, hi, aux);
if (compare(a[mid], a[mid + 1]) <= 0) {
// left and right are sorted and if the last-left element is less
// or equals than the first-right element there is nothing else
// to do
return;
}
_merge(a, compare, lo, mid, hi, aux);
}
export function groupBy<T>(data: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[][] {
const result: T[][] = [];
let currentGroup: T[] | undefined = undefined;
for (const element of mergeSort(data.slice(0), compare)) {
if (!currentGroup || compare(currentGroup[0], element) !== 0) {
currentGroup = [element];
result.push(currentGroup);
} else {
currentGroup.push(element);
}
}
return result;
}
interface IMutableSplice<T> extends ISplice<T> {
deleteCount: number;
}
/**
* Diffs two *sorted* arrays and computes the splices which apply the diff.
*/
export function sortedDiff<T>(before: ReadonlyArray<T>, after: ReadonlyArray<T>, compare: (a: T, b: T) => number): ISplice<T>[] {
const result: IMutableSplice<T>[] = [];
function pushSplice(start: number, deleteCount: number, toInsert: T[]): void {
if (deleteCount === 0 && toInsert.length === 0) {
return;
}
const latest = result[result.length - 1];
if (latest && latest.start + latest.deleteCount === start) {
latest.deleteCount += deleteCount;
latest.toInsert.push(...toInsert);
} else {
result.push({ start, deleteCount, toInsert });
}
}
let beforeIdx = 0;
let afterIdx = 0;
while (true) {
if (beforeIdx === before.length) {
pushSplice(beforeIdx, 0, after.slice(afterIdx));
break;
}
if (afterIdx === after.length) {
pushSplice(beforeIdx, before.length - 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
pushSplice(beforeIdx, 1, []);
beforeIdx += 1;
} else if (n > 0) {
// beforeElement is greater -> after element added
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.
*/
export function delta<T>(before: ReadonlyArray<T>, after: ReadonlyArray<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.toInsert);
}
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: ReadonlyArray<T>, compare: (a: T, b: T) => number, n: number): T[] {
if (n === 0) {
return [];
}
const result = array.slice(0, n).sort(compare);
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, token?: CancellationToken): Promise<T[]> {
if (n === 0) {
return Promise.resolve([]);
}
return new Promise((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 (token && token.isCancellationRequested) {
throw canceled();
}
topStep(array, compare, result, i, m);
}
return result;
})()
.then(resolve, reject);
});
}
function topStep<T>(array: ReadonlyArray<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();
const j = findFirstInSorted(result, e => compare(element, e) < 0);
result.splice(j, 0, element);
}
}
}
/**
* @returns New array with all falsy values removed. The original array IS NOT modified.
*/
export function coalesce<T>(array: ReadonlyArray<T | undefined | null>): T[] {
return <T[]>array.filter(e => !!e);
}
/**
* Remove all falsey values from `array`. The original array IS modified.
*/
export function coalesceInPlace<T>(array: Array<T | undefined | null>): void {
let to = 0;
for (let i = 0; i < array.length; i++) {
if (!!array[i]) {
array[to] = array[i];
to += 1;
}
}
array.length = to;
}
/**
* 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) || obj.length === 0;
}
/**
* @returns True if the provided object is an array and has at least one element.
*/
export function isNonEmptyArray<T>(obj: T[] | undefined | null): obj is T[];
export function isNonEmptyArray<T>(obj: readonly T[] | undefined | null): obj is readonly T[];
export function isNonEmptyArray<T>(obj: T[] | readonly T[] | undefined | null): obj is T[] | readonly T[] {
return Array.isArray(obj) && 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: ReadonlyArray<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 distinctES6<T>(array: ReadonlyArray<T>): T[] {
const seen = new Set<T>();
return array.filter(element => {
if (seen.has(element)) {
return false;
}
seen.add(element);
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 lastIndex<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean): number {
for (let i = array.length - 1; i >= 0; i--) {
const element = array[i];
if (fn(element)) {
return i;
}
}
return -1;
}
export function firstOrDefault<T, NotFound = T>(array: ReadonlyArray<T>, notFoundValue: NotFound): T | NotFound;
export function firstOrDefault<T>(array: ReadonlyArray<T>): T | undefined;
export function firstOrDefault<T, NotFound = T>(array: ReadonlyArray<T>, notFoundValue?: NotFound): T | NotFound | undefined {
return array.length > 0 ? array[0] : notFoundValue;
}
export function commonPrefixLength<T>(one: ReadonlyArray<T>, other: ReadonlyArray<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 (<T[]>[]).concat(...arr);
}
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[] = [];
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 index<T>(array: ReadonlyArray<T>, indexer: (t: T) => string): { [key: string]: T; };
export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, mapper: (t: T) => R): { [key: string]: R; };
export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, mapper?: (t: T) => R): { [key: string]: R; } {
return array.reduce((r, t) => {
r[indexer(t)] = mapper ? mapper(t) : t;
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 () => remove(array, element);
}
/**
* Removes an element from an array if it can be found.
*/
export function remove<T>(array: T[], element: T): T | undefined {
const index = array.indexOf(element);
if (index > -1) {
array.splice(index, 1);
return element;
}
return undefined;
}
/**
* 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);
}
/**
* Uses Fisher-Yates shuffle to shuffle the given array
*/
export function shuffle<T>(array: T[], _seed?: number): void {
let rand: () => number;
if (typeof _seed === 'number') {
let seed = _seed;
// Seeded random number generator in JS. Modified from:
// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript
rand = () => {
const x = Math.sin(seed++) * 179426549; // throw away most significant digits and reduce any potential bias
return x - Math.floor(x);
};
} else {
rand = Math.random;
}
for (let i = array.length - 1; i > 0; i -= 1) {
const j = Math.floor(rand() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
/**
* Pushes an element to the start of the array, if found.
*/
export function pushToStart<T>(arr: T[], value: T): void {
const index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
arr.unshift(value);
}
}
/**
* Pushes an element to the end of the array, if found.
*/
export function pushToEnd<T>(arr: T[], value: T): void {
const index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
arr.push(value);
}
}
export function mapArrayOrNot<T, U>(items: T | T[], fn: (_: T) => U): U | U[] {
return Array.isArray(items) ?
items.map(fn) :
fn(items);
}
export function asArray<T>(x: T | T[]): T[];
export function asArray<T>(x: T | readonly T[]): readonly T[];
export function asArray<T>(x: T | T[]): T[] {
return Array.isArray(x) ? x : [x];
}
export function getRandomElement<T>(arr: T[]): T | undefined {
return arr[Math.floor(Math.random() * arr.length)];
}

View File

@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Throws an error with the provided message if the provided value does not evaluate to a true Javascript value.
*/
export function ok(value?: unknown, message?: string) {
if (!value) {
throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed');
}
}

View File

@@ -0,0 +1,976 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
export function isThenable<T>(obj: any): obj is Promise<T> {
return obj && typeof (<Promise<any>>obj).then === 'function';
}
export interface CancelablePromise<T> extends Promise<T> {
cancel(): void;
}
export function createCancelablePromise<T>(callback: (token: CancellationToken) => Promise<T>): CancelablePromise<T> {
const source = new CancellationTokenSource();
const thenable = callback(source.token);
const promise = new Promise<T>((resolve, reject) => {
source.token.onCancellationRequested(() => {
reject(errors.canceled());
});
Promise.resolve(thenable).then(value => {
source.dispose();
resolve(value);
}, err => {
source.dispose();
reject(err);
});
});
return <CancelablePromise<T>>new class {
cancel() {
source.cancel();
}
then<TResult1 = T, TResult2 = never>(resolve?: ((value: T) => TResult1 | Promise<TResult1>) | undefined | null, reject?: ((reason: any) => TResult2 | Promise<TResult2>) | undefined | null): Promise<TResult1 | TResult2> {
return promise.then(resolve, reject);
}
catch<TResult = never>(reject?: ((reason: any) => TResult | Promise<TResult>) | undefined | null): Promise<T | TResult> {
return this.then(undefined, reject);
}
finally(onfinally?: (() => void) | undefined | null): Promise<T> {
return promise.finally(onfinally);
}
};
}
export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken): Promise<T | undefined>;
export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken, defaultValue: T): Promise<T>;
export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken, defaultValue?: T): Promise<T | undefined> {
return Promise.race([promise, new Promise<T | undefined>(resolve => token.onCancellationRequested(() => resolve(defaultValue)))]);
}
/**
* Returns as soon as one of the promises is resolved and cancels remaining promises
*/
export async function raceCancellablePromises<T>(cancellablePromises: CancelablePromise<T>[]): Promise<T> {
let resolvedPromiseIndex = -1;
const promises = cancellablePromises.map((promise, index) => promise.then(result => { resolvedPromiseIndex = index; return result; }));
const result = await Promise.race(promises);
cancellablePromises.forEach((cancellablePromise, index) => {
if (index !== resolvedPromiseIndex) {
cancellablePromise.cancel();
}
});
return result;
}
export function raceTimeout<T>(promise: Promise<T>, timeout: number, onTimeout?: () => void): Promise<T | undefined> {
let promiseResolve: ((value: T | undefined) => void) | undefined = undefined;
const timer = setTimeout(() => {
promiseResolve?.(undefined);
onTimeout?.();
}, timeout);
return Promise.race([
promise.finally(() => clearTimeout(timer)),
new Promise<T | undefined>(resolve => promiseResolve = resolve)
]);
}
export function asPromise<T>(callback: () => T | Thenable<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
const item = callback();
if (isThenable<T>(item)) {
item.then(resolve, reject);
} else {
resolve(item);
}
});
}
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<any> | null;
private queuedPromise: Promise<any> | null;
private queuedPromiseFactory: ITask<Promise<any>> | null;
constructor() {
this.activePromise = null;
this.queuedPromise = null;
this.queuedPromiseFactory = null;
}
queue<T>(promiseFactory: ITask<Promise<T>>): Promise<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 Promise(c => {
this.activePromise!.then(onComplete, onComplete).then(c);
});
}
return new Promise((c, e) => {
this.queuedPromise!.then(c, e);
});
}
this.activePromise = promiseFactory();
return new Promise((resolve, reject) => {
this.activePromise!.then((result: any) => {
this.activePromise = null;
resolve(result);
}, (err: any) => {
this.activePromise = null;
reject(err);
});
});
}
}
export class Sequencer {
private current: Promise<any> = Promise.resolve(null);
queue<T>(promiseTask: ITask<Promise<T>>): Promise<T> {
return this.current = this.current.then(() => promiseTask());
}
}
export class SequencerByKey<TKey> {
private promiseMap = new Map<TKey, Promise<any>>();
queue<T>(key: TKey, promiseTask: ITask<Promise<T>>): Promise<T> {
const runningPromise = this.promiseMap.get(key) ?? Promise.resolve();
const newPromise = runningPromise
.catch(() => { })
.then(promiseTask)
.finally(() => {
if (this.promiseMap.get(key) === newPromise) {
this.promiseMap.delete(key);
}
});
this.promiseMap.set(key, newPromise);
return newPromise;
}
}
/**
* 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 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> implements IDisposable {
private timeout: any;
private completionPromise: Promise<any> | null;
private doResolve: ((value?: any | Promise<any>) => void) | null;
private doReject: ((err: any) => void) | null;
private task: ITask<T | Promise<T>> | null;
constructor(public defaultDelay: number) {
this.timeout = null;
this.completionPromise = null;
this.doResolve = null;
this.doReject = null;
this.task = null;
}
trigger(task: ITask<T | Promise<T>>, delay: number = this.defaultDelay): Promise<T> {
this.task = task;
this.cancelTimeout();
if (!this.completionPromise) {
this.completionPromise = new Promise((c, e) => {
this.doResolve = c;
this.doReject = e;
}).then(() => {
this.completionPromise = null;
this.doResolve = null;
if (this.task) {
const task = this.task;
this.task = null;
return task();
}
return undefined;
});
}
this.timeout = setTimeout(() => {
this.timeout = null;
if (this.doResolve) {
this.doResolve(null);
}
}, delay);
return this.completionPromise;
}
isTriggered(): boolean {
return this.timeout !== null;
}
cancel(): void {
this.cancelTimeout();
if (this.completionPromise) {
if (this.doReject) {
this.doReject(errors.canceled());
}
this.completionPromise = null;
}
}
private cancelTimeout(): void {
if (this.timeout !== null) {
clearTimeout(this.timeout);
this.timeout = null;
}
}
dispose(): void {
this.cancelTimeout();
}
}
/**
* A helper to delay execution of a task that is being requested often, while
* preventing accumulation of consecutive executions, while the task runs.
*
* The mail man is clever and waits for a certain amount of time, before going
* out to deliver letters. While the mail man is going out, more letters arrive
* and can only be delivered once he is back. Once he is back the mail man will
* do one more trip to deliver the letters that have accumulated while he was out.
*/
export class ThrottledDelayer<T> {
private delayer: Delayer<Promise<T>>;
private throttler: Throttler;
constructor(defaultDelay: number) {
this.delayer = new Delayer(defaultDelay);
this.throttler = new Throttler();
}
trigger(promiseFactory: ITask<Promise<T>>, delay?: number): Promise<T> {
return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as any as Promise<T>;
}
isTriggered(): boolean {
return this.delayer.isTriggered();
}
cancel(): void {
this.delayer.cancel();
}
dispose(): void {
this.delayer.dispose();
}
}
/**
* A barrier that is initially closed and then becomes opened permanently.
*/
export class Barrier {
private _isOpen: boolean;
private _promise: Promise<boolean>;
private _completePromise!: (v: boolean) => void;
constructor() {
this._isOpen = false;
this._promise = new Promise<boolean>((c, e) => {
this._completePromise = c;
});
}
isOpen(): boolean {
return this._isOpen;
}
open(): void {
this._isOpen = true;
this._completePromise(true);
}
wait(): Promise<boolean> {
return this._promise;
}
}
export function timeout(millis: number): CancelablePromise<void>;
export function timeout(millis: number, token: CancellationToken): Promise<void>;
export function timeout(millis: number, token?: CancellationToken): CancelablePromise<void> | Promise<void> {
if (!token) {
return createCancelablePromise(token => timeout(millis, token));
}
return new Promise((resolve, reject) => {
const handle = setTimeout(resolve, millis);
token.onCancellationRequested(() => {
clearTimeout(handle);
reject(errors.canceled());
});
});
}
export function disposableTimeout(handler: () => void, timeout = 0): IDisposable {
const timer = setTimeout(handler, timeout);
return toDisposable(() => clearTimeout(timer));
}
export function ignoreErrors<T>(promise: Promise<T>): Promise<T | undefined> {
return promise.then(undefined, _ => undefined);
}
/**
* 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<Promise<T>>[]): Promise<T[]> {
const results: T[] = [];
let index = 0;
const len = promiseFactories.length;
function next(): Promise<T> | null {
return index < len ? promiseFactories[index++]() : null;
}
function thenHandler(result: any): Promise<any> {
if (result !== undefined && result !== null) {
results.push(result);
}
const n = next();
if (n) {
return n.then(thenHandler);
}
return Promise.resolve(results);
}
return Promise.resolve(null).then(thenHandler);
}
export function first<T>(promiseFactories: ITask<Promise<T>>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T | null = null): Promise<T | null> {
let index = 0;
const len = promiseFactories.length;
const loop: () => Promise<T | null> = () => {
if (index >= len) {
return Promise.resolve(defaultValue);
}
const factory = promiseFactories[index++];
const promise = Promise.resolve(factory());
return promise.then(result => {
if (shouldStop(result)) {
return Promise.resolve(result);
}
return loop();
});
};
return loop();
}
interface ILimitedTaskFactory<T> {
factory: ITask<Promise<T>>;
c: (value: T | Promise<T>) => void;
e: (error?: any) => void;
}
/**
* 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 _size = 0;
private runningPromises: number;
private maxDegreeOfParalellism: number;
private outstandingPromises: ILimitedTaskFactory<T>[];
private readonly _onFinished: Emitter<void>;
constructor(maxDegreeOfParalellism: number) {
this.maxDegreeOfParalellism = maxDegreeOfParalellism;
this.outstandingPromises = [];
this.runningPromises = 0;
this._onFinished = new Emitter<void>();
}
get onFinished(): Event<void> {
return this._onFinished.event;
}
get size(): number {
return this._size;
// return this.runningPromises + this.outstandingPromises.length;
}
queue(factory: ITask<Promise<T>>): Promise<T> {
this._size++;
return new Promise<T>((c, e) => {
this.outstandingPromises.push({ factory, c, e });
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.then(iLimitedTask.c, iLimitedTask.e);
promise.then(() => this.consumed(), () => this.consumed());
}
}
private consumed(): void {
this._size--;
this.runningPromises--;
if (this.outstandingPromises.length > 0) {
this.consume();
} else {
this._onFinished.fire();
}
}
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);
}
}
/**
* 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 implements IDisposable {
private readonly queues = new Map<string, Queue<void>>();
queueFor(resource: URI): Queue<void> {
const key = resource.toString();
if (!this.queues.has(key)) {
const queue = new Queue<void>();
queue.onFinished(() => {
queue.dispose();
this.queues.delete(key);
});
this.queues.set(key, queue);
}
return this.queues.get(key)!;
}
dispose(): void {
this.queues.forEach(queue => queue.dispose());
this.queues.clear();
}
}
export class TimeoutTimer implements IDisposable {
private _token: any;
constructor();
constructor(runner: () => void, timeout: number);
constructor(runner?: () => void, timeout?: number) {
this._token = -1;
if (typeof runner === 'function' && typeof timeout === 'number') {
this.setIfNotSet(runner, timeout);
}
}
dispose(): void {
this.cancel();
}
cancel(): void {
if (this._token !== -1) {
clearTimeout(this._token);
this._token = -1;
}
}
cancelAndSet(runner: () => void, timeout: number): void {
this.cancel();
this._token = setTimeout(() => {
this._token = -1;
runner();
}, timeout);
}
setIfNotSet(runner: () => void, timeout: number): void {
if (this._token !== -1) {
// timer is already set
return;
}
this._token = setTimeout(() => {
this._token = -1;
runner();
}, timeout);
}
}
export class IntervalTimer implements IDisposable {
private _token: any;
constructor() {
this._token = -1;
}
dispose(): void {
this.cancel();
}
cancel(): void {
if (this._token !== -1) {
clearInterval(this._token);
this._token = -1;
}
}
cancelAndSet(runner: () => void, interval: number): void {
this.cancel();
this._token = setInterval(() => {
runner();
}, interval);
}
}
export class RunOnceScheduler {
protected runner: ((...args: any[]) => void) | null;
private timeoutToken: any;
private timeout: number;
private timeoutHandler: () => void;
constructor(runner: (...args: any[]) => void, delay: number) {
this.timeoutToken = -1;
this.runner = runner;
this.timeout = delay;
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()) {
clearTimeout(this.timeoutToken);
this.timeoutToken = -1;
}
}
/**
* Cancel previous runner (if any) & schedule a new runner.
*/
schedule(delay = this.timeout): void {
this.cancel();
this.timeoutToken = setTimeout(this.timeoutHandler, delay);
}
get delay(): number {
return this.timeout;
}
set delay(value: number) {
this.timeout = value;
}
/**
* Returns true if scheduled.
*/
isScheduled(): boolean {
return this.timeoutToken !== -1;
}
private onTimeout() {
this.timeoutToken = -1;
if (this.runner) {
this.doRun();
}
}
protected doRun(): void {
if (this.runner) {
this.runner();
}
}
}
export class RunOnceWorker<T> extends RunOnceScheduler {
private units: T[] = [];
constructor(runner: (units: T[]) => void, timeout: number) {
super(runner, timeout);
}
work(unit: T): void {
this.units.push(unit);
if (!this.isScheduled()) {
this.schedule();
}
}
protected doRun(): void {
const units = this.units;
this.units = [];
if (this.runner) {
this.runner(units);
}
}
dispose(): void {
this.units = [];
super.dispose();
}
}
//#region -- run on idle tricks ------------
export interface IdleDeadline {
readonly didTimeout: boolean;
timeRemaining(): number;
}
/**
* Execute the callback the next time the browser is idle
*/
export let runWhenIdle: (callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable;
declare function requestIdleCallback(callback: (args: IdleDeadline) => void, options?: { timeout: number }): number;
declare function cancelIdleCallback(handle: number): void;
(function () {
if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') {
const dummyIdle: IdleDeadline = Object.freeze({
didTimeout: true,
timeRemaining() { return 15; }
});
runWhenIdle = (runner) => {
const handle = setTimeout(() => runner(dummyIdle));
let disposed = false;
return {
dispose() {
if (disposed) {
return;
}
disposed = true;
clearTimeout(handle);
}
};
};
} else {
runWhenIdle = (runner, timeout?) => {
const handle: number = requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined);
let disposed = false;
return {
dispose() {
if (disposed) {
return;
}
disposed = true;
cancelIdleCallback(handle);
}
};
};
}
})();
/**
* An implementation of the "idle-until-urgent"-strategy as introduced
* here: https://philipwalton.com/articles/idle-until-urgent/
*/
export class IdleValue<T> {
private readonly _executor: () => void;
private readonly _handle: IDisposable;
private _didRun: boolean = false;
private _value?: T;
private _error: any;
constructor(executor: () => T) {
this._executor = () => {
try {
this._value = executor();
} catch (err) {
this._error = err;
} finally {
this._didRun = true;
}
};
this._handle = runWhenIdle(() => this._executor());
}
dispose(): void {
this._handle.dispose();
}
get value(): T {
if (!this._didRun) {
this._handle.dispose();
this._executor();
}
if (this._error) {
throw this._error;
}
return this._value!;
}
}
//#endregion
export async function retry<T>(task: ITask<Promise<T>>, delay: number, retries: number): Promise<T> {
let lastError: Error | undefined;
for (let i = 0; i < retries; i++) {
try {
return await task();
} catch (error) {
lastError = error;
await timeout(delay);
}
}
throw lastError;
}
//#region Task Sequentializer
interface IPendingTask {
taskId: number;
cancel: () => void;
promise: Promise<void>;
}
interface ISequentialTask {
promise: Promise<void>;
promiseResolve: () => void;
promiseReject: (error: Error) => void;
run: () => Promise<void>;
}
export interface ITaskSequentializerWithPendingTask {
readonly pending: Promise<void>;
}
export class TaskSequentializer {
private _pending?: IPendingTask;
private _next?: ISequentialTask;
hasPending(taskId?: number): this is ITaskSequentializerWithPendingTask {
if (!this._pending) {
return false;
}
if (typeof taskId === 'number') {
return this._pending.taskId === taskId;
}
return !!this._pending;
}
get pending(): Promise<void> | undefined {
return this._pending ? this._pending.promise : undefined;
}
cancelPending(): void {
this._pending?.cancel();
}
setPending(taskId: number, promise: Promise<void>, onCancel?: () => void,): Promise<void> {
this._pending = { taskId: taskId, cancel: () => onCancel?.(), promise };
promise.then(() => this.donePending(taskId), () => this.donePending(taskId));
return promise;
}
private donePending(taskId: number): void {
if (this._pending && taskId === this._pending.taskId) {
// only set pending to done if the promise finished that is associated with that taskId
this._pending = undefined;
// schedule the next task now that we are free if we have any
this.triggerNext();
}
}
private triggerNext(): void {
if (this._next) {
const next = this._next;
this._next = undefined;
// Run next task and complete on the associated promise
next.run().then(next.promiseResolve, next.promiseReject);
}
}
setNext(run: () => Promise<void>): Promise<void> {
// this is our first next task, so we create associated promise with it
// so that we can return a promise that completes when the task has
// completed.
if (!this._next) {
let promiseResolve: () => void;
let promiseReject: (error: Error) => void;
const promise = new Promise<void>((resolve, reject) => {
promiseResolve = resolve;
promiseReject = reject;
});
this._next = {
run,
promise,
promiseResolve: promiseResolve!,
promiseReject: promiseReject!
};
}
// we have a previous next task, just overwrite it
else {
this._next.run = run;
}
return this._next.promise;
}
}
//#endregion
//#region
/**
* The `IntervalCounter` allows to count the number
* of calls to `increment()` over a duration of
* `interval`. This utility can be used to conditionally
* throttle a frequent task when a certain threshold
* is reached.
*/
export class IntervalCounter {
private lastIncrementTime = 0;
private value = 0;
constructor(private readonly interval: number) { }
increment(): number {
const now = Date.now();
// We are outside of the range of `interval` and as such
// start counting from 0 and remember the time
if (now - this.lastIncrementTime > this.interval) {
this.lastIncrementTime = now;
this.value = 0;
}
this.value++;
return this.value;
}
}
//#endregion

View File

@@ -0,0 +1,236 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
import * as streams from 'vs/base/common/stream';
declare const Buffer: any;
const hasBuffer = (typeof Buffer !== 'undefined');
const hasTextEncoder = (typeof TextEncoder !== 'undefined');
const hasTextDecoder = (typeof TextDecoder !== 'undefined');
let textEncoder: TextEncoder | null;
let textDecoder: TextDecoder | null;
export class VSBuffer {
static alloc(byteLength: number): VSBuffer {
if (hasBuffer) {
return new VSBuffer(Buffer.allocUnsafe(byteLength));
} else {
return new VSBuffer(new Uint8Array(byteLength));
}
}
static wrap(actual: Uint8Array): VSBuffer {
if (hasBuffer && !(Buffer.isBuffer(actual))) {
// https://nodejs.org/dist/latest-v10.x/docs/api/buffer.html#buffer_class_method_buffer_from_arraybuffer_byteoffset_length
// Create a zero-copy Buffer wrapper around the ArrayBuffer pointed to by the Uint8Array
actual = Buffer.from(actual.buffer, actual.byteOffset, actual.byteLength);
}
return new VSBuffer(actual);
}
static fromString(source: string): VSBuffer {
if (hasBuffer) {
return new VSBuffer(Buffer.from(source));
} else if (hasTextEncoder) {
if (!textEncoder) {
textEncoder = new TextEncoder();
}
return new VSBuffer(textEncoder.encode(source));
} else {
return new VSBuffer(strings.encodeUTF8(source));
}
}
static concat(buffers: VSBuffer[], totalLength?: number): VSBuffer {
if (typeof totalLength === 'undefined') {
totalLength = 0;
for (let i = 0, len = buffers.length; i < len; i++) {
totalLength += buffers[i].byteLength;
}
}
const ret = VSBuffer.alloc(totalLength);
let offset = 0;
for (let i = 0, len = buffers.length; i < len; i++) {
const element = buffers[i];
ret.set(element, offset);
offset += element.byteLength;
}
return ret;
}
readonly buffer: Uint8Array;
readonly byteLength: number;
private constructor(buffer: Uint8Array) {
this.buffer = buffer;
this.byteLength = this.buffer.byteLength;
}
toString(): string {
if (hasBuffer) {
return this.buffer.toString();
} else if (hasTextDecoder) {
if (!textDecoder) {
textDecoder = new TextDecoder();
}
return textDecoder.decode(this.buffer);
} else {
return strings.decodeUTF8(this.buffer);
}
}
slice(start?: number, end?: number): VSBuffer {
// IMPORTANT: use subarray instead of slice because TypedArray#slice
// creates shallow copy and NodeBuffer#slice doesn't. The use of subarray
// ensures the same, performant, behaviour.
return new VSBuffer(this.buffer.subarray(start!/*bad lib.d.ts*/, end));
}
set(array: VSBuffer, offset?: number): void;
set(array: Uint8Array, offset?: number): void;
set(array: VSBuffer | Uint8Array, offset?: number): void {
if (array instanceof VSBuffer) {
this.buffer.set(array.buffer, offset);
} else {
this.buffer.set(array, offset);
}
}
readUInt32BE(offset: number): number {
return readUInt32BE(this.buffer, offset);
}
writeUInt32BE(value: number, offset: number): void {
writeUInt32BE(this.buffer, value, offset);
}
readUInt32LE(offset: number): number {
return readUInt32LE(this.buffer, offset);
}
writeUInt32LE(value: number, offset: number): void {
writeUInt32LE(this.buffer, value, offset);
}
readUInt8(offset: number): number {
return readUInt8(this.buffer, offset);
}
writeUInt8(value: number, offset: number): void {
writeUInt8(this.buffer, value, offset);
}
}
export function readUInt16LE(source: Uint8Array, offset: number): number {
return (
((source[offset + 0] << 0) >>> 0) |
((source[offset + 1] << 8) >>> 0)
);
}
export function writeUInt16LE(destination: Uint8Array, value: number, offset: number): void {
destination[offset + 0] = (value & 0b11111111);
value = value >>> 8;
destination[offset + 1] = (value & 0b11111111);
}
export function readUInt32BE(source: Uint8Array, offset: number): number {
return (
source[offset] * 2 ** 24
+ source[offset + 1] * 2 ** 16
+ source[offset + 2] * 2 ** 8
+ source[offset + 3]
);
}
export function writeUInt32BE(destination: Uint8Array, value: number, offset: number): void {
destination[offset + 3] = value;
value = value >>> 8;
destination[offset + 2] = value;
value = value >>> 8;
destination[offset + 1] = value;
value = value >>> 8;
destination[offset] = value;
}
export function readUInt32LE(source: Uint8Array, offset: number): number {
return (
((source[offset + 0] << 0) >>> 0) |
((source[offset + 1] << 8) >>> 0) |
((source[offset + 2] << 16) >>> 0) |
((source[offset + 3] << 24) >>> 0)
);
}
export function writeUInt32LE(destination: Uint8Array, value: number, offset: number): void {
destination[offset + 0] = (value & 0b11111111);
value = value >>> 8;
destination[offset + 1] = (value & 0b11111111);
value = value >>> 8;
destination[offset + 2] = (value & 0b11111111);
value = value >>> 8;
destination[offset + 3] = (value & 0b11111111);
}
export function readUInt8(source: Uint8Array, offset: number): number {
return source[offset];
}
export function writeUInt8(destination: Uint8Array, value: number, offset: number): void {
destination[offset] = value;
}
export interface VSBufferReadable extends streams.Readable<VSBuffer> { }
export interface VSBufferReadableStream extends streams.ReadableStream<VSBuffer> { }
export interface VSBufferWriteableStream extends streams.WriteableStream<VSBuffer> { }
export interface VSBufferReadableBufferedStream extends streams.ReadableBufferedStream<VSBuffer> { }
export function readableToBuffer(readable: VSBufferReadable): VSBuffer {
return streams.consumeReadable<VSBuffer>(readable, chunks => VSBuffer.concat(chunks));
}
export function bufferToReadable(buffer: VSBuffer): VSBufferReadable {
return streams.toReadable<VSBuffer>(buffer);
}
export function streamToBuffer(stream: streams.ReadableStream<VSBuffer>): Promise<VSBuffer> {
return streams.consumeStream<VSBuffer>(stream, chunks => VSBuffer.concat(chunks));
}
export async function bufferedStreamToBuffer(bufferedStream: streams.ReadableBufferedStream<VSBuffer>): Promise<VSBuffer> {
if (bufferedStream.ended) {
return VSBuffer.concat(bufferedStream.buffer);
}
return VSBuffer.concat([
// Include already read chunks...
...bufferedStream.buffer,
// ...and all additional chunks
await streamToBuffer(bufferedStream.stream)
]);
}
export function bufferToStream(buffer: VSBuffer): streams.ReadableStream<VSBuffer> {
return streams.toStream<VSBuffer>(buffer, chunks => VSBuffer.concat(chunks));
}
export function streamToBufferReadableStream(stream: streams.ReadableStreamEvents<Uint8Array | string>): streams.ReadableStream<VSBuffer> {
return streams.transform<Uint8Array | string, VSBuffer>(stream, { data: data => typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data) }, chunks => VSBuffer.concat(chunks));
}
export function newWriteableBufferStream(options?: streams.WriteableStreamOptions): streams.WriteableStream<VSBuffer> {
return streams.newWriteableStream<VSBuffer>(chunks => VSBuffer.concat(chunks), options);
}

View File

@@ -0,0 +1,16 @@
{
"name": "vs/base",
"dependencies": [
{
"name": "vs",
"internal": false
}
],
"libs": [
"lib.core.d.ts"
],
"sources": [
"**/*.ts"
],
"declares": []
}

View File

@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { IDisposable } from 'vs/base/common/lifecycle';
export interface CacheResult<T> extends IDisposable {
promise: Promise<T>;
}
export class Cache<T> {
private result: CacheResult<T> | null = null;
constructor(private task: (ct: CancellationToken) => Promise<T>) { }
get(): CacheResult<T> {
if (this.result) {
return this.result;
}
const cts = new CancellationTokenSource();
const promise = this.task(cts.token);
this.result = {
promise,
dispose: () => {
this.result = null;
cts.cancel();
cts.dispose();
}
};
return this.result;
}
}

View File

@@ -0,0 +1,144 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
export interface CancellationToken {
/**
* A flag signalling is cancellation has been requested.
*/
readonly isCancellationRequested: boolean;
/**
* An event which fires when cancellation is requested. This event
* only ever fires `once` as cancellation can only happen once. Listeners
* that are registered after cancellation will be called (next event loop run),
* but also only once.
*
* @event
*/
readonly onCancellationRequested: (listener: (e: any) => any, thisArgs?: any, disposables?: IDisposable[]) => IDisposable;
}
const shortcutEvent: Event<any> = Object.freeze(function (callback, context?): IDisposable {
const handle = setTimeout(callback.bind(context), 0);
return { dispose() { clearTimeout(handle); } };
});
export namespace CancellationToken {
export function isCancellationToken(thing: unknown): thing is CancellationToken {
if (thing === CancellationToken.None || thing === CancellationToken.Cancelled) {
return true;
}
if (thing instanceof MutableToken) {
return true;
}
if (!thing || typeof thing !== 'object') {
return false;
}
return typeof (thing as CancellationToken).isCancellationRequested === 'boolean'
&& typeof (thing as CancellationToken).onCancellationRequested === 'function';
}
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> | null = null;
public cancel() {
if (!this._isCancelled) {
this._isCancelled = true;
if (this._emitter) {
this._emitter.fire(undefined);
this.dispose();
}
}
}
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;
}
public dispose(): void {
if (this._emitter) {
this._emitter.dispose();
this._emitter = null;
}
}
}
export class CancellationTokenSource {
private _token?: CancellationToken = undefined;
private _parentListener?: IDisposable = undefined;
constructor(parent?: CancellationToken) {
this._parentListener = parent && parent.onCancellationRequested(this.cancel, this);
}
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 if (this._token instanceof MutableToken) {
// actually cancel
this._token.cancel();
}
}
dispose(cancel: boolean = false): void {
if (cancel) {
this.cancel();
}
if (this._parentListener) {
this._parentListener.dispose();
}
if (!this._token) {
// ensure to initialize with an empty token if we had none
this._token = CancellationToken.None;
} else if (this._token instanceof MutableToken) {
// actually dispose
this._token.dispose();
}
}
}

View File

@@ -0,0 +1,435 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// 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 `\b` character.
*/
Backspace = 8,
/**
* 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 = 0x2028,
/**
* Unicode Character 'PARAGRAPH SEPARATOR' (U+2029)
* http://www.fileformat.info/info/unicode/char/2029/index.htm
*/
PARAGRAPH_SEPARATOR = 0x2029,
/**
* Unicode Character 'NEXT LINE' (U+0085)
* http://www.fileformat.info/info/unicode/char/0085/index.htm
*/
NEXT_LINE = 0x0085,
// 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,135 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { matchesFuzzy, IMatch } from 'vs/base/common/filters';
import { ltrim } from 'vs/base/common/strings';
export const codiconStartMarker = '$(';
export interface IParsedCodicons {
readonly text: string;
readonly codiconOffsets?: readonly number[];
}
export function parseCodicons(text: string): IParsedCodicons {
const firstCodiconIndex = text.indexOf(codiconStartMarker);
if (firstCodiconIndex === -1) {
return { text }; // return early if the word does not include an codicon
}
return doParseCodicons(text, firstCodiconIndex);
}
function doParseCodicons(text: string, firstCodiconIndex: number): IParsedCodicons {
const codiconOffsets: number[] = [];
let textWithoutCodicons: string = '';
function appendChars(chars: string) {
if (chars) {
textWithoutCodicons += chars;
for (const _ of chars) {
codiconOffsets.push(codiconsOffset); // make sure to fill in codicon offsets
}
}
}
let currentCodiconStart = -1;
let currentCodiconValue: string = '';
let codiconsOffset = 0;
let char: string;
let nextChar: string;
let offset = firstCodiconIndex;
const length = text.length;
// Append all characters until the first codicon
appendChars(text.substr(0, firstCodiconIndex));
// example: $(file-symlink-file) my cool $(other-codicon) entry
while (offset < length) {
char = text[offset];
nextChar = text[offset + 1];
// beginning of codicon: some value $( <--
if (char === codiconStartMarker[0] && nextChar === codiconStartMarker[1]) {
currentCodiconStart = offset;
// if we had a previous potential codicon value without
// the closing ')', it was actually not an codicon and
// so we have to add it to the actual value
appendChars(currentCodiconValue);
currentCodiconValue = codiconStartMarker;
offset++; // jump over '('
}
// end of codicon: some value $(some-codicon) <--
else if (char === ')' && currentCodiconStart !== -1) {
const currentCodiconLength = offset - currentCodiconStart + 1; // +1 to include the closing ')'
codiconsOffset += currentCodiconLength;
currentCodiconStart = -1;
currentCodiconValue = '';
}
// within codicon
else if (currentCodiconStart !== -1) {
// Make sure this is a real codicon name
if (/^[a-z0-9\-]$/i.test(char)) {
currentCodiconValue += char;
} else {
// This is not a real codicon, treat it as text
appendChars(currentCodiconValue);
currentCodiconStart = -1;
currentCodiconValue = '';
}
}
// any value outside of codicons
else {
appendChars(char);
}
offset++;
}
// if we had a previous potential codicon value without
// the closing ')', it was actually not an codicon and
// so we have to add it to the actual value
appendChars(currentCodiconValue);
return { text: textWithoutCodicons, codiconOffsets };
}
export function matchesFuzzyCodiconAware(query: string, target: IParsedCodicons, enableSeparateSubstringMatching = false): IMatch[] | null {
const { text, codiconOffsets } = target;
// Return early if there are no codicon markers in the word to match against
if (!codiconOffsets || codiconOffsets.length === 0) {
return matchesFuzzy(query, text, enableSeparateSubstringMatching);
}
// Trim the word to match against because it could have leading
// whitespace now if the word started with an codicon
const wordToMatchAgainstWithoutCodiconsTrimmed = ltrim(text, ' ');
const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutCodiconsTrimmed.length;
// match on value without codicons
const matches = matchesFuzzy(query, wordToMatchAgainstWithoutCodiconsTrimmed, enableSeparateSubstringMatching);
// Map matches back to offsets with codicons and trimming
if (matches) {
for (const match of matches) {
const codiconOffset = codiconOffsets[match.start + leadingWhitespaceOffset] /* codicon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
match.start += codiconOffset;
match.end += codiconOffset;
}
}
return matches;
}

View File

@@ -0,0 +1,515 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { codiconStartMarker } from 'vs/base/common/codicon';
import { Emitter, Event } from 'vs/base/common/event';
export interface IIconRegistry {
readonly all: IterableIterator<Codicon>;
readonly onDidRegister: Event<Codicon>;
get(id: string): Codicon | undefined;
}
class Registry implements IIconRegistry {
private readonly _icons = new Map<string, Codicon>();
private readonly _onDidRegister = new Emitter<Codicon>();
public add(icon: Codicon) {
if (!this._icons.has(icon.id)) {
this._icons.set(icon.id, icon);
this._onDidRegister.fire(icon);
} else {
console.error(`Duplicate registration of codicon ${icon.id}`);
}
}
public get(id: string): Codicon | undefined {
return this._icons.get(id);
}
public get all(): IterableIterator<Codicon> {
return this._icons.values();
}
public get onDidRegister(): Event<Codicon> {
return this._onDidRegister.event;
}
}
const _registry = new Registry();
export const iconRegistry: IIconRegistry = _registry;
export function registerIcon(id: string, def: Codicon, description?: string) {
return new Codicon(id, def);
}
export class Codicon {
constructor(public readonly id: string, public readonly definition: Codicon | IconDefinition, public description?: string) {
_registry.add(this);
}
public get classNames() { return 'codicon codicon-' + this.id; }
// classNamesArray is useful for migrating to ES6 classlist
public get classNamesArray() { return ['codicon', 'codicon-' + this.id]; }
public get cssSelector() { return '.codicon.codicon-' + this.id; }
}
interface IconDefinition {
character: string;
}
export namespace Codicon {
// built-in icons, with image name
export const add = new Codicon('add', { character: '\\ea60' });
export const plus = new Codicon('plus', { character: '\\ea60' });
export const gistNew = new Codicon('gist-new', { character: '\\ea60' });
export const repoCreate = new Codicon('repo-create', { character: '\\ea60' });
export const lightbulb = new Codicon('lightbulb', { character: '\\ea61' });
export const lightBulb = new Codicon('light-bulb', { character: '\\ea61' });
export const repo = new Codicon('repo', { character: '\\ea62' });
export const repoDelete = new Codicon('repo-delete', { character: '\\ea62' });
export const gistFork = new Codicon('gist-fork', { character: '\\ea63' });
export const repoForked = new Codicon('repo-forked', { character: '\\ea63' });
export const gitPullRequest = new Codicon('git-pull-request', { character: '\\ea64' });
export const gitPullRequestAbandoned = new Codicon('git-pull-request-abandoned', { character: '\\ea64' });
export const recordKeys = new Codicon('record-keys', { character: '\\ea65' });
export const keyboard = new Codicon('keyboard', { character: '\\ea65' });
export const tag = new Codicon('tag', { character: '\\ea66' });
export const tagAdd = new Codicon('tag-add', { character: '\\ea66' });
export const tagRemove = new Codicon('tag-remove', { character: '\\ea66' });
export const person = new Codicon('person', { character: '\\ea67' });
export const personAdd = new Codicon('person-add', { character: '\\ea67' });
export const personFollow = new Codicon('person-follow', { character: '\\ea67' });
export const personOutline = new Codicon('person-outline', { character: '\\ea67' });
export const personFilled = new Codicon('person-filled', { character: '\\ea67' });
export const gitBranch = new Codicon('git-branch', { character: '\\ea68' });
export const gitBranchCreate = new Codicon('git-branch-create', { character: '\\ea68' });
export const gitBranchDelete = new Codicon('git-branch-delete', { character: '\\ea68' });
export const sourceControl = new Codicon('source-control', { character: '\\ea68' });
export const mirror = new Codicon('mirror', { character: '\\ea69' });
export const mirrorPublic = new Codicon('mirror-public', { character: '\\ea69' });
export const star = new Codicon('star', { character: '\\ea6a' });
export const starAdd = new Codicon('star-add', { character: '\\ea6a' });
export const starDelete = new Codicon('star-delete', { character: '\\ea6a' });
export const starEmpty = new Codicon('star-empty', { character: '\\ea6a' });
export const comment = new Codicon('comment', { character: '\\ea6b' });
export const commentAdd = new Codicon('comment-add', { character: '\\ea6b' });
export const alert = new Codicon('alert', { character: '\\ea6c' });
export const warning = new Codicon('warning', { character: '\\ea6c' });
export const search = new Codicon('search', { character: '\\ea6d' });
export const searchSave = new Codicon('search-save', { character: '\\ea6d' });
export const logOut = new Codicon('log-out', { character: '\\ea6e' });
export const signOut = new Codicon('sign-out', { character: '\\ea6e' });
export const logIn = new Codicon('log-in', { character: '\\ea6f' });
export const signIn = new Codicon('sign-in', { character: '\\ea6f' });
export const eye = new Codicon('eye', { character: '\\ea70' });
export const eyeUnwatch = new Codicon('eye-unwatch', { character: '\\ea70' });
export const eyeWatch = new Codicon('eye-watch', { character: '\\ea70' });
export const circleFilled = new Codicon('circle-filled', { character: '\\ea71' });
export const primitiveDot = new Codicon('primitive-dot', { character: '\\ea71' });
export const closeDirty = new Codicon('close-dirty', { character: '\\ea71' });
export const debugBreakpoint = new Codicon('debug-breakpoint', { character: '\\ea71' });
export const debugBreakpointDisabled = new Codicon('debug-breakpoint-disabled', { character: '\\ea71' });
export const debugHint = new Codicon('debug-hint', { character: '\\ea71' });
export const primitiveSquare = new Codicon('primitive-square', { character: '\\ea72' });
export const edit = new Codicon('edit', { character: '\\ea73' });
export const pencil = new Codicon('pencil', { character: '\\ea73' });
export const info = new Codicon('info', { character: '\\ea74' });
export const issueOpened = new Codicon('issue-opened', { character: '\\ea74' });
export const gistPrivate = new Codicon('gist-private', { character: '\\ea75' });
export const gitForkPrivate = new Codicon('git-fork-private', { character: '\\ea75' });
export const lock = new Codicon('lock', { character: '\\ea75' });
export const mirrorPrivate = new Codicon('mirror-private', { character: '\\ea75' });
export const close = new Codicon('close', { character: '\\ea76' });
export const removeClose = new Codicon('remove-close', { character: '\\ea76' });
export const x = new Codicon('x', { character: '\\ea76' });
export const repoSync = new Codicon('repo-sync', { character: '\\ea77' });
export const sync = new Codicon('sync', { character: '\\ea77' });
export const clone = new Codicon('clone', { character: '\\ea78' });
export const desktopDownload = new Codicon('desktop-download', { character: '\\ea78' });
export const beaker = new Codicon('beaker', { character: '\\ea79' });
export const microscope = new Codicon('microscope', { character: '\\ea79' });
export const vm = new Codicon('vm', { character: '\\ea7a' });
export const deviceDesktop = new Codicon('device-desktop', { character: '\\ea7a' });
export const file = new Codicon('file', { character: '\\ea7b' });
export const fileText = new Codicon('file-text', { character: '\\ea7b' });
export const more = new Codicon('more', { character: '\\ea7c' });
export const ellipsis = new Codicon('ellipsis', { character: '\\ea7c' });
export const kebabHorizontal = new Codicon('kebab-horizontal', { character: '\\ea7c' });
export const mailReply = new Codicon('mail-reply', { character: '\\ea7d' });
export const reply = new Codicon('reply', { character: '\\ea7d' });
export const organization = new Codicon('organization', { character: '\\ea7e' });
export const organizationFilled = new Codicon('organization-filled', { character: '\\ea7e' });
export const organizationOutline = new Codicon('organization-outline', { character: '\\ea7e' });
export const newFile = new Codicon('new-file', { character: '\\ea7f' });
export const fileAdd = new Codicon('file-add', { character: '\\ea7f' });
export const newFolder = new Codicon('new-folder', { character: '\\ea80' });
export const fileDirectoryCreate = new Codicon('file-directory-create', { character: '\\ea80' });
export const trash = new Codicon('trash', { character: '\\ea81' });
export const trashcan = new Codicon('trashcan', { character: '\\ea81' });
export const history = new Codicon('history', { character: '\\ea82' });
export const clock = new Codicon('clock', { character: '\\ea82' });
export const folder = new Codicon('folder', { character: '\\ea83' });
export const fileDirectory = new Codicon('file-directory', { character: '\\ea83' });
export const symbolFolder = new Codicon('symbol-folder', { character: '\\ea83' });
export const logoGithub = new Codicon('logo-github', { character: '\\ea84' });
export const markGithub = new Codicon('mark-github', { character: '\\ea84' });
export const github = new Codicon('github', { character: '\\ea84' });
export const terminal = new Codicon('terminal', { character: '\\ea85' });
export const console = new Codicon('console', { character: '\\ea85' });
export const repl = new Codicon('repl', { character: '\\ea85' });
export const zap = new Codicon('zap', { character: '\\ea86' });
export const symbolEvent = new Codicon('symbol-event', { character: '\\ea86' });
export const error = new Codicon('error', { character: '\\ea87' });
export const stop = new Codicon('stop', { character: '\\ea87' });
export const variable = new Codicon('variable', { character: '\\ea88' });
export const symbolVariable = new Codicon('symbol-variable', { character: '\\ea88' });
export const array = new Codicon('array', { character: '\\ea8a' });
export const symbolArray = new Codicon('symbol-array', { character: '\\ea8a' });
export const symbolModule = new Codicon('symbol-module', { character: '\\ea8b' });
export const symbolPackage = new Codicon('symbol-package', { character: '\\ea8b' });
export const symbolNamespace = new Codicon('symbol-namespace', { character: '\\ea8b' });
export const symbolObject = new Codicon('symbol-object', { character: '\\ea8b' });
export const symbolMethod = new Codicon('symbol-method', { character: '\\ea8c' });
export const symbolFunction = new Codicon('symbol-function', { character: '\\ea8c' });
export const symbolConstructor = new Codicon('symbol-constructor', { character: '\\ea8c' });
export const symbolBoolean = new Codicon('symbol-boolean', { character: '\\ea8f' });
export const symbolNull = new Codicon('symbol-null', { character: '\\ea8f' });
export const symbolNumeric = new Codicon('symbol-numeric', { character: '\\ea90' });
export const symbolNumber = new Codicon('symbol-number', { character: '\\ea90' });
export const symbolStructure = new Codicon('symbol-structure', { character: '\\ea91' });
export const symbolStruct = new Codicon('symbol-struct', { character: '\\ea91' });
export const symbolParameter = new Codicon('symbol-parameter', { character: '\\ea92' });
export const symbolTypeParameter = new Codicon('symbol-type-parameter', { character: '\\ea92' });
export const symbolKey = new Codicon('symbol-key', { character: '\\ea93' });
export const symbolText = new Codicon('symbol-text', { character: '\\ea93' });
export const symbolReference = new Codicon('symbol-reference', { character: '\\ea94' });
export const goToFile = new Codicon('go-to-file', { character: '\\ea94' });
export const symbolEnum = new Codicon('symbol-enum', { character: '\\ea95' });
export const symbolValue = new Codicon('symbol-value', { character: '\\ea95' });
export const symbolRuler = new Codicon('symbol-ruler', { character: '\\ea96' });
export const symbolUnit = new Codicon('symbol-unit', { character: '\\ea96' });
export const activateBreakpoints = new Codicon('activate-breakpoints', { character: '\\ea97' });
export const archive = new Codicon('archive', { character: '\\ea98' });
export const arrowBoth = new Codicon('arrow-both', { character: '\\ea99' });
export const arrowDown = new Codicon('arrow-down', { character: '\\ea9a' });
export const arrowLeft = new Codicon('arrow-left', { character: '\\ea9b' });
export const arrowRight = new Codicon('arrow-right', { character: '\\ea9c' });
export const arrowSmallDown = new Codicon('arrow-small-down', { character: '\\ea9d' });
export const arrowSmallLeft = new Codicon('arrow-small-left', { character: '\\ea9e' });
export const arrowSmallRight = new Codicon('arrow-small-right', { character: '\\ea9f' });
export const arrowSmallUp = new Codicon('arrow-small-up', { character: '\\eaa0' });
export const arrowUp = new Codicon('arrow-up', { character: '\\eaa1' });
export const bell = new Codicon('bell', { character: '\\eaa2' });
export const bold = new Codicon('bold', { character: '\\eaa3' });
export const book = new Codicon('book', { character: '\\eaa4' });
export const bookmark = new Codicon('bookmark', { character: '\\eaa5' });
export const debugBreakpointConditionalUnverified = new Codicon('debug-breakpoint-conditional-unverified', { character: '\\eaa6' });
export const debugBreakpointConditional = new Codicon('debug-breakpoint-conditional', { character: '\\eaa7' });
export const debugBreakpointConditionalDisabled = new Codicon('debug-breakpoint-conditional-disabled', { character: '\\eaa7' });
export const debugBreakpointDataUnverified = new Codicon('debug-breakpoint-data-unverified', { character: '\\eaa8' });
export const debugBreakpointData = new Codicon('debug-breakpoint-data', { character: '\\eaa9' });
export const debugBreakpointDataDisabled = new Codicon('debug-breakpoint-data-disabled', { character: '\\eaa9' });
export const debugBreakpointLogUnverified = new Codicon('debug-breakpoint-log-unverified', { character: '\\eaaa' });
export const debugBreakpointLog = new Codicon('debug-breakpoint-log', { character: '\\eaab' });
export const debugBreakpointLogDisabled = new Codicon('debug-breakpoint-log-disabled', { character: '\\eaab' });
export const briefcase = new Codicon('briefcase', { character: '\\eaac' });
export const broadcast = new Codicon('broadcast', { character: '\\eaad' });
export const browser = new Codicon('browser', { character: '\\eaae' });
export const bug = new Codicon('bug', { character: '\\eaaf' });
export const calendar = new Codicon('calendar', { character: '\\eab0' });
export const caseSensitive = new Codicon('case-sensitive', { character: '\\eab1' });
export const check = new Codicon('check', { character: '\\eab2' });
export const checklist = new Codicon('checklist', { character: '\\eab3' });
export const chevronDown = new Codicon('chevron-down', { character: '\\eab4' });
export const chevronLeft = new Codicon('chevron-left', { character: '\\eab5' });
export const chevronRight = new Codicon('chevron-right', { character: '\\eab6' });
export const chevronUp = new Codicon('chevron-up', { character: '\\eab7' });
export const chromeClose = new Codicon('chrome-close', { character: '\\eab8' });
export const chromeMaximize = new Codicon('chrome-maximize', { character: '\\eab9' });
export const chromeMinimize = new Codicon('chrome-minimize', { character: '\\eaba' });
export const chromeRestore = new Codicon('chrome-restore', { character: '\\eabb' });
export const circleOutline = new Codicon('circle-outline', { character: '\\eabc' });
export const debugBreakpointUnverified = new Codicon('debug-breakpoint-unverified', { character: '\\eabc' });
export const circleSlash = new Codicon('circle-slash', { character: '\\eabd' });
export const circuitBoard = new Codicon('circuit-board', { character: '\\eabe' });
export const clearAll = new Codicon('clear-all', { character: '\\eabf' });
export const clippy = new Codicon('clippy', { character: '\\eac0' });
export const closeAll = new Codicon('close-all', { character: '\\eac1' });
export const cloudDownload = new Codicon('cloud-download', { character: '\\eac2' });
export const cloudUpload = new Codicon('cloud-upload', { character: '\\eac3' });
export const code = new Codicon('code', { character: '\\eac4' });
export const collapseAll = new Codicon('collapse-all', { character: '\\eac5' });
export const colorMode = new Codicon('color-mode', { character: '\\eac6' });
export const commentDiscussion = new Codicon('comment-discussion', { character: '\\eac7' });
export const compareChanges = new Codicon('compare-changes', { character: '\\eafd' });
export const creditCard = new Codicon('credit-card', { character: '\\eac9' });
export const dash = new Codicon('dash', { character: '\\eacc' });
export const dashboard = new Codicon('dashboard', { character: '\\eacd' });
export const database = new Codicon('database', { character: '\\eace' });
export const debugContinue = new Codicon('debug-continue', { character: '\\eacf' });
export const debugDisconnect = new Codicon('debug-disconnect', { character: '\\ead0' });
export const debugPause = new Codicon('debug-pause', { character: '\\ead1' });
export const debugRestart = new Codicon('debug-restart', { character: '\\ead2' });
export const debugStart = new Codicon('debug-start', { character: '\\ead3' });
export const debugStepInto = new Codicon('debug-step-into', { character: '\\ead4' });
export const debugStepOut = new Codicon('debug-step-out', { character: '\\ead5' });
export const debugStepOver = new Codicon('debug-step-over', { character: '\\ead6' });
export const debugStop = new Codicon('debug-stop', { character: '\\ead7' });
export const debug = new Codicon('debug', { character: '\\ead8' });
export const deviceCameraVideo = new Codicon('device-camera-video', { character: '\\ead9' });
export const deviceCamera = new Codicon('device-camera', { character: '\\eada' });
export const deviceMobile = new Codicon('device-mobile', { character: '\\eadb' });
export const diffAdded = new Codicon('diff-added', { character: '\\eadc' });
export const diffIgnored = new Codicon('diff-ignored', { character: '\\eadd' });
export const diffModified = new Codicon('diff-modified', { character: '\\eade' });
export const diffRemoved = new Codicon('diff-removed', { character: '\\eadf' });
export const diffRenamed = new Codicon('diff-renamed', { character: '\\eae0' });
export const diff = new Codicon('diff', { character: '\\eae1' });
export const discard = new Codicon('discard', { character: '\\eae2' });
export const editorLayout = new Codicon('editor-layout', { character: '\\eae3' });
export const emptyWindow = new Codicon('empty-window', { character: '\\eae4' });
export const exclude = new Codicon('exclude', { character: '\\eae5' });
export const extensions = new Codicon('extensions', { character: '\\eae6' });
export const eyeClosed = new Codicon('eye-closed', { character: '\\eae7' });
export const fileBinary = new Codicon('file-binary', { character: '\\eae8' });
export const fileCode = new Codicon('file-code', { character: '\\eae9' });
export const fileMedia = new Codicon('file-media', { character: '\\eaea' });
export const filePdf = new Codicon('file-pdf', { character: '\\eaeb' });
export const fileSubmodule = new Codicon('file-submodule', { character: '\\eaec' });
export const fileSymlinkDirectory = new Codicon('file-symlink-directory', { character: '\\eaed' });
export const fileSymlinkFile = new Codicon('file-symlink-file', { character: '\\eaee' });
export const fileZip = new Codicon('file-zip', { character: '\\eaef' });
export const files = new Codicon('files', { character: '\\eaf0' });
export const filter = new Codicon('filter', { character: '\\eaf1' });
export const flame = new Codicon('flame', { character: '\\eaf2' });
export const foldDown = new Codicon('fold-down', { character: '\\eaf3' });
export const foldUp = new Codicon('fold-up', { character: '\\eaf4' });
export const fold = new Codicon('fold', { character: '\\eaf5' });
export const folderActive = new Codicon('folder-active', { character: '\\eaf6' });
export const folderOpened = new Codicon('folder-opened', { character: '\\eaf7' });
export const gear = new Codicon('gear', { character: '\\eaf8' });
export const gift = new Codicon('gift', { character: '\\eaf9' });
export const gistSecret = new Codicon('gist-secret', { character: '\\eafa' });
export const gist = new Codicon('gist', { character: '\\eafb' });
export const gitCommit = new Codicon('git-commit', { character: '\\eafc' });
export const gitCompare = new Codicon('git-compare', { character: '\\eafd' });
export const gitMerge = new Codicon('git-merge', { character: '\\eafe' });
export const githubAction = new Codicon('github-action', { character: '\\eaff' });
export const githubAlt = new Codicon('github-alt', { character: '\\eb00' });
export const globe = new Codicon('globe', { character: '\\eb01' });
export const grabber = new Codicon('grabber', { character: '\\eb02' });
export const graph = new Codicon('graph', { character: '\\eb03' });
export const gripper = new Codicon('gripper', { character: '\\eb04' });
export const heart = new Codicon('heart', { character: '\\eb05' });
export const home = new Codicon('home', { character: '\\eb06' });
export const horizontalRule = new Codicon('horizontal-rule', { character: '\\eb07' });
export const hubot = new Codicon('hubot', { character: '\\eb08' });
export const inbox = new Codicon('inbox', { character: '\\eb09' });
export const issueClosed = new Codicon('issue-closed', { character: '\\eb0a' });
export const issueReopened = new Codicon('issue-reopened', { character: '\\eb0b' });
export const issues = new Codicon('issues', { character: '\\eb0c' });
export const italic = new Codicon('italic', { character: '\\eb0d' });
export const jersey = new Codicon('jersey', { character: '\\eb0e' });
export const json = new Codicon('json', { character: '\\eb0f' });
export const kebabVertical = new Codicon('kebab-vertical', { character: '\\eb10' });
export const key = new Codicon('key', { character: '\\eb11' });
export const law = new Codicon('law', { character: '\\eb12' });
export const lightbulbAutofix = new Codicon('lightbulb-autofix', { character: '\\eb13' });
export const linkExternal = new Codicon('link-external', { character: '\\eb14' });
export const link = new Codicon('link', { character: '\\eb15' });
export const listOrdered = new Codicon('list-ordered', { character: '\\eb16' });
export const listUnordered = new Codicon('list-unordered', { character: '\\eb17' });
export const liveShare = new Codicon('live-share', { character: '\\eb18' });
export const loading = new Codicon('loading', { character: '\\eb19' });
export const location = new Codicon('location', { character: '\\eb1a' });
export const mailRead = new Codicon('mail-read', { character: '\\eb1b' });
export const mail = new Codicon('mail', { character: '\\eb1c' });
export const markdown = new Codicon('markdown', { character: '\\eb1d' });
export const megaphone = new Codicon('megaphone', { character: '\\eb1e' });
export const mention = new Codicon('mention', { character: '\\eb1f' });
export const milestone = new Codicon('milestone', { character: '\\eb20' });
export const mortarBoard = new Codicon('mortar-board', { character: '\\eb21' });
export const move = new Codicon('move', { character: '\\eb22' });
export const multipleWindows = new Codicon('multiple-windows', { character: '\\eb23' });
export const mute = new Codicon('mute', { character: '\\eb24' });
export const noNewline = new Codicon('no-newline', { character: '\\eb25' });
export const note = new Codicon('note', { character: '\\eb26' });
export const octoface = new Codicon('octoface', { character: '\\eb27' });
export const openPreview = new Codicon('open-preview', { character: '\\eb28' });
export const package_ = new Codicon('package', { character: '\\eb29' });
export const paintcan = new Codicon('paintcan', { character: '\\eb2a' });
export const pin = new Codicon('pin', { character: '\\eb2b' });
export const play = new Codicon('play', { character: '\\eb2c' });
export const run = new Codicon('run', { character: '\\eb2c' });
export const plug = new Codicon('plug', { character: '\\eb2d' });
export const preserveCase = new Codicon('preserve-case', { character: '\\eb2e' });
export const preview = new Codicon('preview', { character: '\\eb2f' });
export const project = new Codicon('project', { character: '\\eb30' });
export const pulse = new Codicon('pulse', { character: '\\eb31' });
export const question = new Codicon('question', { character: '\\eb32' });
export const quote = new Codicon('quote', { character: '\\eb33' });
export const radioTower = new Codicon('radio-tower', { character: '\\eb34' });
export const reactions = new Codicon('reactions', { character: '\\eb35' });
export const references = new Codicon('references', { character: '\\eb36' });
export const refresh = new Codicon('refresh', { character: '\\eb37' });
export const regex = new Codicon('regex', { character: '\\eb38' });
export const remoteExplorer = new Codicon('remote-explorer', { character: '\\eb39' });
export const remote = new Codicon('remote', { character: '\\eb3a' });
export const remove = new Codicon('remove', { character: '\\eb3b' });
export const replaceAll = new Codicon('replace-all', { character: '\\eb3c' });
export const replace = new Codicon('replace', { character: '\\eb3d' });
export const repoClone = new Codicon('repo-clone', { character: '\\eb3e' });
export const repoForcePush = new Codicon('repo-force-push', { character: '\\eb3f' });
export const repoPull = new Codicon('repo-pull', { character: '\\eb40' });
export const repoPush = new Codicon('repo-push', { character: '\\eb41' });
export const report = new Codicon('report', { character: '\\eb42' });
export const requestChanges = new Codicon('request-changes', { character: '\\eb43' });
export const rocket = new Codicon('rocket', { character: '\\eb44' });
export const rootFolderOpened = new Codicon('root-folder-opened', { character: '\\eb45' });
export const rootFolder = new Codicon('root-folder', { character: '\\eb46' });
export const rss = new Codicon('rss', { character: '\\eb47' });
export const ruby = new Codicon('ruby', { character: '\\eb48' });
export const saveAll = new Codicon('save-all', { character: '\\eb49' });
export const saveAs = new Codicon('save-as', { character: '\\eb4a' });
export const save = new Codicon('save', { character: '\\eb4b' });
export const screenFull = new Codicon('screen-full', { character: '\\eb4c' });
export const screenNormal = new Codicon('screen-normal', { character: '\\eb4d' });
export const searchStop = new Codicon('search-stop', { character: '\\eb4e' });
export const server = new Codicon('server', { character: '\\eb50' });
export const settingsGear = new Codicon('settings-gear', { character: '\\eb51' });
export const settings = new Codicon('settings', { character: '\\eb52' });
export const shield = new Codicon('shield', { character: '\\eb53' });
export const smiley = new Codicon('smiley', { character: '\\eb54' });
export const sortPrecedence = new Codicon('sort-precedence', { character: '\\eb55' });
export const splitHorizontal = new Codicon('split-horizontal', { character: '\\eb56' });
export const splitVertical = new Codicon('split-vertical', { character: '\\eb57' });
export const squirrel = new Codicon('squirrel', { character: '\\eb58' });
export const starFull = new Codicon('star-full', { character: '\\eb59' });
export const starHalf = new Codicon('star-half', { character: '\\eb5a' });
export const symbolClass = new Codicon('symbol-class', { character: '\\eb5b' });
export const symbolColor = new Codicon('symbol-color', { character: '\\eb5c' });
export const symbolConstant = new Codicon('symbol-constant', { character: '\\eb5d' });
export const symbolEnumMember = new Codicon('symbol-enum-member', { character: '\\eb5e' });
export const symbolField = new Codicon('symbol-field', { character: '\\eb5f' });
export const symbolFile = new Codicon('symbol-file', { character: '\\eb60' });
export const symbolInterface = new Codicon('symbol-interface', { character: '\\eb61' });
export const symbolKeyword = new Codicon('symbol-keyword', { character: '\\eb62' });
export const symbolMisc = new Codicon('symbol-misc', { character: '\\eb63' });
export const symbolOperator = new Codicon('symbol-operator', { character: '\\eb64' });
export const symbolProperty = new Codicon('symbol-property', { character: '\\eb65' });
export const wrench = new Codicon('wrench', { character: '\\eb65' });
export const wrenchSubaction = new Codicon('wrench-subaction', { character: '\\eb65' });
export const symbolSnippet = new Codicon('symbol-snippet', { character: '\\eb66' });
export const tasklist = new Codicon('tasklist', { character: '\\eb67' });
export const telescope = new Codicon('telescope', { character: '\\eb68' });
export const textSize = new Codicon('text-size', { character: '\\eb69' });
export const threeBars = new Codicon('three-bars', { character: '\\eb6a' });
export const thumbsdown = new Codicon('thumbsdown', { character: '\\eb6b' });
export const thumbsup = new Codicon('thumbsup', { character: '\\eb6c' });
export const tools = new Codicon('tools', { character: '\\eb6d' });
export const triangleDown = new Codicon('triangle-down', { character: '\\eb6e' });
export const triangleLeft = new Codicon('triangle-left', { character: '\\eb6f' });
export const triangleRight = new Codicon('triangle-right', { character: '\\eb70' });
export const triangleUp = new Codicon('triangle-up', { character: '\\eb71' });
export const twitter = new Codicon('twitter', { character: '\\eb72' });
export const unfold = new Codicon('unfold', { character: '\\eb73' });
export const unlock = new Codicon('unlock', { character: '\\eb74' });
export const unmute = new Codicon('unmute', { character: '\\eb75' });
export const unverified = new Codicon('unverified', { character: '\\eb76' });
export const verified = new Codicon('verified', { character: '\\eb77' });
export const versions = new Codicon('versions', { character: '\\eb78' });
export const vmActive = new Codicon('vm-active', { character: '\\eb79' });
export const vmOutline = new Codicon('vm-outline', { character: '\\eb7a' });
export const vmRunning = new Codicon('vm-running', { character: '\\eb7b' });
export const watch = new Codicon('watch', { character: '\\eb7c' });
export const whitespace = new Codicon('whitespace', { character: '\\eb7d' });
export const wholeWord = new Codicon('whole-word', { character: '\\eb7e' });
export const window = new Codicon('window', { character: '\\eb7f' });
export const wordWrap = new Codicon('word-wrap', { character: '\\eb80' });
export const zoomIn = new Codicon('zoom-in', { character: '\\eb81' });
export const zoomOut = new Codicon('zoom-out', { character: '\\eb82' });
export const listFilter = new Codicon('list-filter', { character: '\\eb83' });
export const listFlat = new Codicon('list-flat', { character: '\\eb84' });
export const listSelection = new Codicon('list-selection', { character: '\\eb85' });
export const selection = new Codicon('selection', { character: '\\eb85' });
export const listTree = new Codicon('list-tree', { character: '\\eb86' });
export const debugBreakpointFunctionUnverified = new Codicon('debug-breakpoint-function-unverified', { character: '\\eb87' });
export const debugBreakpointFunction = new Codicon('debug-breakpoint-function', { character: '\\eb88' });
export const debugBreakpointFunctionDisabled = new Codicon('debug-breakpoint-function-disabled', { character: '\\eb88' });
export const debugStackframeActive = new Codicon('debug-stackframe-active', { character: '\\eb89' });
export const debugStackframeDot = new Codicon('debug-stackframe-dot', { character: '\\eb8a' });
export const debugStackframe = new Codicon('debug-stackframe', { character: '\\eb8b' });
export const debugStackframeFocused = new Codicon('debug-stackframe-focused', { character: '\\eb8b' });
export const debugBreakpointUnsupported = new Codicon('debug-breakpoint-unsupported', { character: '\\eb8c' });
export const symbolString = new Codicon('symbol-string', { character: '\\eb8d' });
export const debugReverseContinue = new Codicon('debug-reverse-continue', { character: '\\eb8e' });
export const debugStepBack = new Codicon('debug-step-back', { character: '\\eb8f' });
export const debugRestartFrame = new Codicon('debug-restart-frame', { character: '\\eb90' });
export const callIncoming = new Codicon('call-incoming', { character: '\\eb92' });
export const callOutgoing = new Codicon('call-outgoing', { character: '\\eb93' });
export const menu = new Codicon('menu', { character: '\\eb94' });
export const expandAll = new Codicon('expand-all', { character: '\\eb95' });
export const feedback = new Codicon('feedback', { character: '\\eb96' });
export const groupByRefType = new Codicon('group-by-ref-type', { character: '\\eb97' });
export const ungroupByRefType = new Codicon('ungroup-by-ref-type', { character: '\\eb98' });
export const account = new Codicon('account', { character: '\\eb99' });
export const bellDot = new Codicon('bell-dot', { character: '\\eb9a' });
export const debugConsole = new Codicon('debug-console', { character: '\\eb9b' });
export const library = new Codicon('library', { character: '\\eb9c' });
export const output = new Codicon('output', { character: '\\eb9d' });
export const runAll = new Codicon('run-all', { character: '\\eb9e' });
export const syncIgnored = new Codicon('sync-ignored', { character: '\\eb9f' });
export const pinned = new Codicon('pinned', { character: '\\eba0' });
export const githubInverted = new Codicon('github-inverted', { character: '\\eba1' });
export const debugAlt = new Codicon('debug-alt', { character: '\\eb91' });
export const serverProcess = new Codicon('server-process', { character: '\\eba2' });
export const serverEnvironment = new Codicon('server-environment', { character: '\\eba3' });
export const pass = new Codicon('pass', { character: '\\eba4' });
export const stopCircle = new Codicon('stop-circle', { character: '\\eba5' });
export const playCircle = new Codicon('play-circle', { character: '\\eba6' });
export const record = new Codicon('record', { character: '\\eba7' });
export const debugAltSmall = new Codicon('debug-alt-small', { character: '\\eba8' });
export const vmConnect = new Codicon('vm-connect', { character: '\\eba9' });
export const cloud = new Codicon('cloud', { character: '\\ebaa' });
export const merge = new Codicon('merge', { character: '\\ebab' });
export const exportIcon = new Codicon('export', { character: '\\ebac' });
export const graphLeft = new Codicon('graph-left', { character: '\\ebad' });
export const magnet = new Codicon('magnet', { character: '\\ebae' });
export const notebook = new Codicon('notebook', { character: '\\ebaf' });
export const redo = new Codicon('redo', { character: '\\ebb0' });
export const checkAll = new Codicon('check-all', { character: '\\ebb1' });
export const pinnedDirty = new Codicon('pinned-dirty', { character: '\\ebb2' });
}
const escapeCodiconsRegex = /(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
export function escapeCodicons(text: string): string {
return text.replace(escapeCodiconsRegex, (match, escaped) => escaped ? match : `\\${match}`);
}
const markdownEscapedCodiconsRegex = /\\\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
export function markdownEscapeEscapedCodicons(text: string): string {
// Need to add an extra \ for escaping in markdown
return text.replace(markdownEscapedCodiconsRegex, match => `\\${match}`);
}
const markdownUnescapeCodiconsRegex = /(\\)?\$\\\(([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?)\\\)/gi;
export function markdownUnescapeCodicons(text: string): string {
return text.replace(markdownUnescapeCodiconsRegex, (match, escaped, codicon) => escaped ? match : `$(${codicon})`);
}
const stripCodiconsRegex = /(\s)?(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)(\s)?/gi;
export function stripCodicons(text: string): string {
if (text.indexOf(codiconStartMarker) === -1) {
return text;
}
return text.replace(stripCodiconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || '');
}

View File

@@ -0,0 +1,118 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* An interface for a JavaScript object that
* acts a dictionary. The keys are strings.
*/
export type IStringDictionary<V> = Record<string, V>;
/**
* An interface for a JavaScript object that
* acts a dictionary. The keys are numbers.
*/
export type INumberDictionary<V> = Record<number, V>;
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Returns an array which contains all values that reside
* in the given dictionary.
*/
export function values<T>(from: IStringDictionary<T> | INumberDictionary<T>): T[] {
const result: T[] = [];
for (let key in from) {
if (hasOwnProperty.call(from, key)) {
result.push((from as any)[key]);
}
}
return result;
}
/**
* Iterates over each entry in the provided dictionary. 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: () => void) => any): void {
for (let key in from) {
if (hasOwnProperty.call(from, key)) {
const result = callback({ key: key, value: (from as any)[key] }, function () {
delete (from as any)[key];
});
if (result === false) {
return;
}
}
}
}
/**
* 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;
}
export function fromMap<T>(original: Map<string, T>): IStringDictionary<T> {
const result: IStringDictionary<T> = Object.create(null);
if (original) {
original.forEach((value, key) => {
result[key] = value;
});
}
return result;
}
export class SetMap<K, V> {
private map = new Map<K, Set<V>>();
add(key: K, value: V): void {
let values = this.map.get(key);
if (!values) {
values = new Set<V>();
this.map.set(key, values);
}
values.add(value);
}
delete(key: K, value: V): void {
const values = this.map.get(key);
if (!values) {
return;
}
values.delete(value);
if (values.size === 0) {
this.map.delete(key);
}
}
forEach(key: K, fn: (value: V) => void): void {
const values = this.map.get(key);
if (!values) {
return;
}
values.forEach(fn);
}
}

View File

@@ -0,0 +1,619 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. 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): 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(Math.round(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 | null): 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;
const a = thisA + colorA * (1 - thisA);
if (a < 1e-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));
}
makeOpaque(opaqueBackground: Color): Color {
if (this.isOpaque() || opaqueBackground.rgba.a !== 1) {
// only allow to blend onto a non-opaque color onto a opaque color
return this;
}
const { r, g, b, a } = this.rgba;
// https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity
return new Color(new RGBA(
opaqueBackground.rgba.r - a * (opaqueBackground.rgba.r - r),
opaqueBackground.rgba.g - a * (opaqueBackground.rgba.g - g),
opaqueBackground.rgba.b - a * (opaqueBackground.rgba.b - b),
1
));
}
flatten(...backgrounds: Color[]): Color {
const background = backgrounds.reduceRight((accumulator, color) => {
return Color._flatten(color, accumulator);
});
return Color._flatten(this, background);
}
private static _flatten(foreground: Color, background: Color) {
const backgroundAlpha = 1 - foreground.rgba.a;
return new Color(new RGBA(
backgroundAlpha * background.rgba.r + foreground.rgba.a * foreground.rgba.r,
backgroundAlpha * background.rgba.g + foreground.rgba.a * foreground.rgba.g,
backgroundAlpha * background.rgba.b + foreground.rgba.a * foreground.rgba.b
));
}
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.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 {
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,256 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { sep } from 'vs/base/common/path';
import { IdleValue } from 'vs/base/common/async';
// When comparing large numbers of strings, such as in sorting large arrays, is better for
// performance to create an Intl.Collator object and use the function provided by its compare
// property than it is to use String.prototype.localeCompare()
// A collator with numeric sorting enabled, and no sensitivity to case or to accents
const intlFileNameCollatorBaseNumeric: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => {
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
return {
collator: collator,
collatorIsNumeric: collator.resolvedOptions().numeric
};
});
// A collator with numeric sorting enabled.
const intlFileNameCollatorNumeric: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
const collator = new Intl.Collator(undefined, { numeric: true });
return {
collator: collator
};
});
// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
const intlFileNameCollatorNumericCaseInsenstive: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' });
return {
collator: collator
};
});/** Compares filenames without distinguishing the name from the extension. Disambiguates by unicode comparison. */
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
const a = one || '';
const b = other || '';
const result = intlFileNameCollatorBaseNumeric.value.collator.compare(a, b);
// Using the numeric option in the collator will
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && a !== b) {
return a < b ? -1 : 1;
}
return result;
}
/** Compares filenames without distinguishing the name from the extension. Disambiguates by length, not unicode comparison. */
export function compareFileNamesDefault(one: string | null, other: string | null): number {
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
one = one || '';
other = other || '';
// Compare the entire filename - both name and extension - and disambiguate by length if needed
return compareAndDisambiguateByLength(collatorNumeric, one, other);
}
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
if (!caseSensitive) {
one = one && one.toLowerCase();
other = other && other.toLowerCase();
}
const [oneName, oneExtension] = extractNameAndExtension(one);
const [otherName, otherExtension] = extractNameAndExtension(other);
if (oneName !== otherName) {
return oneName < otherName ? -1 : 1;
}
if (oneExtension === otherExtension) {
return 0;
}
return oneExtension < otherExtension ? -1 : 1;
}
export function compareFileExtensions(one: string | null, other: string | null): number {
const [oneName, oneExtension] = extractNameAndExtension(one);
const [otherName, otherExtension] = extractNameAndExtension(other);
let result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneExtension, otherExtension);
if (result === 0) {
// Using the numeric option in the collator will
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && oneExtension !== otherExtension) {
return oneExtension < otherExtension ? -1 : 1;
}
// Extensions are equal, compare filenames
result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneName, otherName);
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && oneName !== otherName) {
return oneName < otherName ? -1 : 1;
}
}
return result;
}
/** Compares filenames by extenson, then by full filename */
export function compareFileExtensionsDefault(one: string | null, other: string | null): number {
one = one || '';
other = other || '';
const oneExtension = extractExtension(one);
const otherExtension = extractExtension(other);
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
let result;
// Check for extension differences, ignoring differences in case and comparing numbers numerically.
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
if (result !== 0) {
return result;
}
// Compare full filenames
return compareAndDisambiguateByLength(collatorNumeric, one, other);
}
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
/** Extracts the name and extension from a full filename, with optional special handling for dotfiles */
function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] {
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || ''];
// if the dotfilesAsNames option is selected, treat an empty filename with an extension,
// or a filename that starts with a dot, as a dotfile name
if (dotfilesAsNames && (!result[0] && result[1] || result[0] && result[0].charAt(0) === '.')) {
result = [result[0] + '.' + result[1], ''];
}
return result;
}
/** Extracts the extension from a full filename. Treats dotfiles as names, not extensions. */
function extractExtension(str?: string | null): string {
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
return (match && match[1] && match[1].charAt(0) !== '.' && match[3]) || '';
}
function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
// Check for differences
let result = collator.compare(one, other);
if (result !== 0) {
return result;
}
// In a numeric comparison, `foo1` and `foo01` will compare as equivalent.
// Disambiguate by sorting the shorter string first.
if (one.length !== other.length) {
return one.length < other.length ? -1 : 1;
}
return 0;
}
function comparePathComponents(one: string, other: string, caseSensitive = false): number {
if (!caseSensitive) {
one = one && one.toLowerCase();
other = other && other.toLowerCase();
}
if (one === other) {
return 0;
}
return one < other ? -1 : 1;
}
export function comparePaths(one: string, other: string, caseSensitive = false): number {
const oneParts = one.split(sep);
const otherParts = other.split(sep);
const lastOne = oneParts.length - 1;
const lastOther = otherParts.length - 1;
let endOne: boolean, endOther: boolean;
for (let i = 0; ; i++) {
endOne = lastOne === i;
endOther = lastOther === i;
if (endOne && endOther) {
return compareFileNames(oneParts[i], otherParts[i], caseSensitive);
} else if (endOne) {
return -1;
} else if (endOther) {
return 1;
}
const result = comparePathComponents(oneParts[i], otherParts[i], caseSensitive);
if (result !== 0) {
return result;
}
}
}
export function compareAnything(one: string, other: string, lookFor: string): number {
const elementAName = one.toLowerCase();
const 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
const elementASuffixMatch = elementAName.endsWith(lookFor);
const elementBSuffixMatch = elementBName.endsWith(lookFor);
if (elementASuffixMatch !== elementBSuffixMatch) {
return elementASuffixMatch ? -1 : 1;
}
// Understand file names
const 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 {
const elementAName = one.toLowerCase();
const elementBName = other.toLowerCase();
// Sort prefix matches over non prefix matches
const elementAPrefixMatch = elementAName.startsWith(lookFor);
const elementBPrefixMatch = elementBName.startsWith(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;
}

View File

@@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
export interface IRemoteConsoleLog {
type: string;
severity: string;
arguments: string;
}
interface IStackArgument {
__$stack: string;
}
export interface IStackFrame {
uri: URI;
line: number;
column: number;
}
export function isRemoteConsoleLog(obj: any): obj is IRemoteConsoleLog {
const entry = obj as IRemoteConsoleLog;
return entry && typeof entry.type === 'string' && typeof entry.severity === 'string';
}
export function parse(entry: IRemoteConsoleLog): { args: any[], stack?: string } {
const args: any[] = [];
let stack: string | undefined;
// Parse Entry
try {
const parsedArguments: any[] = JSON.parse(entry.arguments);
// Check for special stack entry as last entry
const stackArgument = parsedArguments[parsedArguments.length - 1] as IStackArgument;
if (stackArgument && stackArgument.__$stack) {
parsedArguments.pop(); // stack is handled specially
stack = stackArgument.__$stack;
}
args.push(...parsedArguments);
} catch (error) {
args.push('Unable to log remote console arguments', entry.arguments);
}
return { args, stack };
}
export function getFirstFrame(entry: IRemoteConsoleLog): IStackFrame | undefined;
export function getFirstFrame(stack: string | undefined): IStackFrame | undefined;
export function getFirstFrame(arg0: IRemoteConsoleLog | string | undefined): IStackFrame | undefined {
if (typeof arg0 !== 'string') {
return getFirstFrame(parse(arg0!).stack);
}
// Parse a source information out of the stack if we have one. Format can be:
// at vscode.commands.registerCommand (/Users/someone/Desktop/test-ts/out/src/extension.js:18:17)
// or
// at /Users/someone/Desktop/test-ts/out/src/extension.js:18:17
// or
// at c:\Users\someone\Desktop\end-js\extension.js:19:17
// or
// at e.$executeContributedCommand(c:\Users\someone\Desktop\end-js\extension.js:19:17)
const stack = arg0;
if (stack) {
const topFrame = findFirstFrame(stack);
// at [^\/]* => line starts with "at" followed by any character except '/' (to not capture unix paths too late)
// (?:(?:[a-zA-Z]+:)|(?:[\/])|(?:\\\\) => windows drive letter OR unix root OR unc root
// (?:.+) => simple pattern for the path, only works because of the line/col pattern after
// :(?:\d+):(?:\d+) => :line:column data
const matches = /at [^\/]*((?:(?:[a-zA-Z]+:)|(?:[\/])|(?:\\\\))(?:.+)):(\d+):(\d+)/.exec(topFrame || '');
if (matches && matches.length === 4) {
return {
uri: URI.file(matches[1]),
line: Number(matches[2]),
column: Number(matches[3])
};
}
}
return undefined;
}
function findFirstFrame(stack: string | undefined): string | undefined {
if (!stack) {
return stack;
}
const newlineIndex = stack.indexOf('\n');
if (newlineIndex === -1) {
return stack;
}
return stack.substring(0, newlineIndex);
}
export function log(entry: IRemoteConsoleLog, label: string): void {
const { args, stack } = parse(entry);
const isOneStringArg = typeof args[0] === 'string' && args.length === 1;
let topFrame = findFirstFrame(stack);
if (topFrame) {
topFrame = `(${topFrame.trim()})`;
}
let consoleArgs: string[] = [];
// First arg is a string
if (typeof args[0] === 'string') {
if (topFrame && isOneStringArg) {
consoleArgs = [`%c[${label}] %c${args[0]} %c${topFrame}`, color('blue'), color(''), color('grey')];
} else {
consoleArgs = [`%c[${label}] %c${args[0]}`, color('blue'), color(''), ...args.slice(1)];
}
}
// First arg is something else, just apply all
else {
consoleArgs = [`%c[${label}]%`, color('blue'), ...args];
}
// Stack: add to args unless already aded
if (topFrame && !isOneStringArg) {
consoleArgs.push(topFrame);
}
// Log it
if (typeof (console as any)[entry.severity] !== 'function') {
throw new Error('Unknown console method');
}
(console as any)[entry.severity].apply(console, consoleArgs);
}
function color(color: string): string {
return `color: ${color}`;
}

View File

@@ -0,0 +1,130 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
const minute = 60;
const hour = minute * 60;
const day = hour * 24;
const week = day * 7;
const month = day * 30;
const year = day * 365;
export function fromNow(date: number | Date, appendAgoLabel?: boolean): string {
if (typeof date !== 'number') {
date = date.getTime();
}
const seconds = Math.round((new Date().getTime() - date) / 1000);
if (seconds < -30) {
return localize('date.fromNow.in', 'in {0}', fromNow(new Date().getTime() + seconds * 1000, false));
}
if (seconds < 30) {
return localize('date.fromNow.now', 'now');
}
let value: number;
if (seconds < minute) {
value = seconds;
if (appendAgoLabel) {
return value === 1
? localize('date.fromNow.seconds.singular.ago', '{0} sec ago', value)
: localize('date.fromNow.seconds.plural.ago', '{0} secs ago', value);
} else {
return value === 1
? localize('date.fromNow.seconds.singular', '{0} sec', value)
: localize('date.fromNow.seconds.plural', '{0} secs', value);
}
}
if (seconds < hour) {
value = Math.floor(seconds / minute);
if (appendAgoLabel) {
return value === 1
? localize('date.fromNow.minutes.singular.ago', '{0} min ago', value)
: localize('date.fromNow.minutes.plural.ago', '{0} mins ago', value);
} else {
return value === 1
? localize('date.fromNow.minutes.singular', '{0} min', value)
: localize('date.fromNow.minutes.plural', '{0} mins', value);
}
}
if (seconds < day) {
value = Math.floor(seconds / hour);
if (appendAgoLabel) {
return value === 1
? localize('date.fromNow.hours.singular.ago', '{0} hr ago', value)
: localize('date.fromNow.hours.plural.ago', '{0} hrs ago', value);
} else {
return value === 1
? localize('date.fromNow.hours.singular', '{0} hr', value)
: localize('date.fromNow.hours.plural', '{0} hrs', value);
}
}
if (seconds < week) {
value = Math.floor(seconds / day);
if (appendAgoLabel) {
return value === 1
? localize('date.fromNow.days.singular.ago', '{0} day ago', value)
: localize('date.fromNow.days.plural.ago', '{0} days ago', value);
} else {
return value === 1
? localize('date.fromNow.days.singular', '{0} day', value)
: localize('date.fromNow.days.plural', '{0} days', value);
}
}
if (seconds < month) {
value = Math.floor(seconds / week);
if (appendAgoLabel) {
return value === 1
? localize('date.fromNow.weeks.singular.ago', '{0} wk ago', value)
: localize('date.fromNow.weeks.plural.ago', '{0} wks ago', value);
} else {
return value === 1
? localize('date.fromNow.weeks.singular', '{0} wk', value)
: localize('date.fromNow.weeks.plural', '{0} wks', value);
}
}
if (seconds < year) {
value = Math.floor(seconds / month);
if (appendAgoLabel) {
return value === 1
? localize('date.fromNow.months.singular.ago', '{0} mo ago', value)
: localize('date.fromNow.months.plural.ago', '{0} mos ago', value);
} else {
return value === 1
? localize('date.fromNow.months.singular', '{0} mo', value)
: localize('date.fromNow.months.plural', '{0} mos', value);
}
}
value = Math.floor(seconds / year);
if (appendAgoLabel) {
return value === 1
? localize('date.fromNow.years.singular.ago', '{0} yr ago', value)
: localize('date.fromNow.years.plural.ago', '{0} yrs ago', value);
} else {
return value === 1
? localize('date.fromNow.years.singular', '{0} yr', value)
: localize('date.fromNow.years.plural', '{0} yrs', value);
}
}
export function toLocalISOString(date: Date): string {
return date.getFullYear() +
'-' + String(date.getMonth() + 1).padStart(2, '0') +
'-' + String(date.getDate()).padStart(2, '0') +
'T' + String(date.getHours()).padStart(2, '0') +
':' + String(date.getMinutes()).padStart(2, '0') +
':' + String(date.getSeconds()).padStart(2, '0') +
'.' + (date.getMilliseconds() / 1000).toFixed(3).slice(2, 5) +
'Z';
}

View File

@@ -0,0 +1,155 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function createDecorator(mapFn: (fn: Function, key: string) => Function): Function {
return (target: any, key: string, descriptor: any) => {
let fnKey: string | null = null;
let fn: Function | null = 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, key);
};
}
let memoizeId = 0;
export function createMemoizer() {
const memoizeKeyPrefix = `$memoize${memoizeId++}`;
let self: any = undefined;
const result = function memoize(target: any, key: string, descriptor: any) {
let fnKey: string | null = null;
let fn: Function | null = null;
if (typeof descriptor.value === 'function') {
fnKey = 'value';
fn = descriptor.value;
if (fn!.length !== 0) {
console.warn('Memoize should only be used in functions with zero parameters');
}
} else if (typeof descriptor.get === 'function') {
fnKey = 'get';
fn = descriptor.get;
}
if (!fn) {
throw new Error('not supported');
}
const memoizeKey = `${memoizeKeyPrefix}:${key}`;
descriptor[fnKey!] = function (...args: any[]) {
self = this;
if (!this.hasOwnProperty(memoizeKey)) {
Object.defineProperty(this, memoizeKey, {
configurable: true,
enumerable: false,
writable: true,
value: fn!.apply(this, args)
});
}
return this[memoizeKey];
};
};
result.clear = () => {
if (typeof self === 'undefined') {
return;
}
Object.getOwnPropertyNames(self).forEach(property => {
if (property.indexOf(memoizeKeyPrefix) === 0) {
delete self[property];
}
});
};
return result;
}
export function memoize(target: any, key: string, descriptor: any) {
return createMemoizer()(target, key, descriptor);
}
export interface IDebounceReducer<T> {
(previousValue: T, ...args: any[]): T;
}
export function debounce<T>(delay: number, reducer?: IDebounceReducer<T>, initialValueProvider?: () => T): Function {
return createDecorator((fn, key) => {
const timerKey = `$debounce$${key}`;
const resultKey = `$debounce$result$${key}`;
return function (this: any, ...args: any[]) {
if (!this[resultKey]) {
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
}
clearTimeout(this[timerKey]);
if (reducer) {
this[resultKey] = reducer(this[resultKey], ...args);
args = [this[resultKey]];
}
this[timerKey] = setTimeout(() => {
fn.apply(this, args);
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
}, delay);
};
});
}
export function throttle<T>(delay: number, reducer?: IDebounceReducer<T>, initialValueProvider?: () => T): Function {
return createDecorator((fn, key) => {
const timerKey = `$throttle$timer$${key}`;
const resultKey = `$throttle$result$${key}`;
const lastRunKey = `$throttle$lastRun$${key}`;
const pendingKey = `$throttle$pending$${key}`;
return function (this: any, ...args: any[]) {
if (!this[resultKey]) {
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
}
if (this[lastRunKey] === null || this[lastRunKey] === undefined) {
this[lastRunKey] = -Number.MAX_VALUE;
}
if (reducer) {
this[resultKey] = reducer(this[resultKey], ...args);
}
if (this[pendingKey]) {
return;
}
const nextTime = this[lastRunKey] + delay;
if (nextTime <= Date.now()) {
this[lastRunKey] = Date.now();
fn.apply(this, [this[resultKey]]);
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
} else {
this[pendingKey] = true;
this[timerKey] = setTimeout(() => {
this[pendingKey] = false;
this[lastRunKey] = Date.now();
fn.apply(this, [this[resultKey]]);
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
}, nextTime - Date.now());
}
};
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* 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 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,83 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
import * as arrays from 'vs/base/common/arrays';
function exceptionToErrorMessage(exception: any, verbose: boolean): string {
if (verbose && (exception.stack || exception.stacktrace)) {
return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), stackToString(exception.stack) || stackToString(exception.stacktrace));
}
return detectSystemErrorMessage(exception);
}
function stackToString(stack: string[] | string | undefined): string | undefined {
if (Array.isArray(stack)) {
return stack.join('\n');
}
return stack;
}
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 || nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
}
/**
* 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)) {
const errors: any[] = arrays.coalesce(error);
const 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 (error.detail) {
const detail = error.detail;
if (detail.error) {
return exceptionToErrorMessage(detail.error, verbose);
}
if (detail.exception) {
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,214 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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) {
setTimeout(() => {
if (e.stack) {
throw new Error(e.message + '\n\n' + e.stack);
}
throw e;
}, 0);
};
}
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);
}
setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
this.unexpectedErrorHandler = newUnexpectedErrorHandler;
}
getUnexpectedErrorHandler(): (e: any) => void {
return this.unexpectedErrorHandler;
}
onUnexpectedError(e: any): void {
this.unexpectedErrorHandler(e);
this.emit(e);
}
// For external errors, we don't want the listeners to be called
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 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;
const 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 {
const error = new Error(canceledName);
error.name = error.message;
return error;
}
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 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);
}
export class NotImplementedError extends Error {
constructor(message?: string) {
super('NotImplemented');
if (message) {
this.message = message;
}
}
}
export class NotSupportedError extends Error {
constructor(message?: string) {
super('NotSupported');
if (message) {
this.message = message;
}
}
}

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAction } from 'vs/base/common/actions';
export interface IErrorOptions {
actions?: ReadonlyArray<IAction>;
}
export interface IErrorWithActions {
actions?: ReadonlyArray<IAction>;
}
export function isErrorWithActions(obj: unknown): obj is IErrorWithActions {
return obj instanceof Error && Array.isArray((obj as IErrorWithActions).actions);
}
export function createErrorWithActions(message: string, options: IErrorOptions = Object.create(null)): Error & IErrorWithActions {
const result = new Error(message);
if (options.actions) {
(<IErrorWithActions>result).actions = options.actions;
}
return result;
}

View File

@@ -0,0 +1,864 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { onUnexpectedError } from 'vs/base/common/errors';
import { once as onceFn } from 'vs/base/common/functional';
import { Disposable, IDisposable, toDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { CancellationToken } from 'vs/base/common/cancellation';
/**
* To an event a function with one or zero parameters
* can be subscribed. The event is the subscriber function itself.
*/
export interface Event<T> {
(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable;
}
export namespace Event {
export const None: Event<any> = () => Disposable.None;
/**
* Given an event, returns another event which only fires once.
*/
export function once<T>(event: Event<T>): Event<T> {
return (listener, thisArgs = null, disposables?) => {
// we need this, in case the event fires during the listener call
let didFire = false;
let result: IDisposable;
result = event(e => {
if (didFire) {
return;
} else if (result) {
result.dispose();
} else {
didFire = true;
}
return listener.call(thisArgs, e);
}, null, disposables);
if (didFire) {
result.dispose();
}
return result;
};
}
/**
* Given an event and a `map` function, returns another event which maps each element
* through the mapping function.
*/
export function map<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
return snapshot((listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables));
}
/**
* Given an event and an `each` function, returns another identical event and calls
* the `each` function per each element.
*/
export function forEach<I>(event: Event<I>, each: (i: I) => void): Event<I> {
return snapshot((listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables));
}
/**
* Given an event and a `filter` function, returns another event which emits those
* elements for which the `filter` function returns `true`.
*/
export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T>;
export function filter<T, R>(event: Event<T | R>, filter: (e: T | R) => e is R): Event<R>;
export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T> {
return snapshot((listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables));
}
/**
* Given an event, returns the same event but typed as `Event<void>`.
*/
export function signal<T>(event: Event<T>): Event<void> {
return event as Event<any> as Event<void>;
}
/**
* Given a collection of events, returns a single event which emits
* whenever any of the provided events emit.
*/
export function any<T>(...events: Event<T>[]): Event<T>;
export function any(...events: Event<any>[]): Event<void>;
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)));
}
/**
* Given an event and a `merge` function, returns another event which maps each element
* and the cumulative result through the `merge` function. Similar to `map`, but with memory.
*/
export function reduce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, initial?: O): Event<O> {
let output: O | undefined = initial;
return map<I, O>(event, e => {
output = merge(output, e);
return output;
});
}
/**
* Given a chain of event processing functions (filter, map, etc), each
* function will be invoked per event & per listener. Snapshotting an event
* chain allows each function to be invoked just once per event.
*/
export function snapshot<T>(event: Event<T>): Event<T> {
let listener: IDisposable;
const emitter = new Emitter<T>({
onFirstListenerAdd() {
listener = event(emitter.fire, emitter);
},
onLastListenerRemove() {
listener.dispose();
}
});
return emitter.event;
}
/**
* Debounces the provided event, given a `merge` function.
*
* @param event The input event.
* @param merge The reducing function.
* @param delay The debouncing delay in millis.
* @param leading Whether the event should fire in the leading phase of the timeout.
* @param leakWarningThreshold The leak warning threshold override.
*/
export function debounce<T>(event: Event<T>, merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<T>;
export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<O>;
export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number = 100, leading = false, leakWarningThreshold?: number): Event<O> {
let subscription: IDisposable;
let output: O | undefined = undefined;
let handle: any = undefined;
let numDebouncedCalls = 0;
const emitter = new Emitter<O>({
leakWarningThreshold,
onFirstListenerAdd() {
subscription = event(cur => {
numDebouncedCalls++;
output = merge(output, cur);
if (leading && !handle) {
emitter.fire(output);
output = undefined;
}
clearTimeout(handle);
handle = setTimeout(() => {
const _output = output;
output = undefined;
handle = undefined;
if (!leading || numDebouncedCalls > 1) {
emitter.fire(_output!);
}
numDebouncedCalls = 0;
}, delay);
});
},
onLastListenerRemove() {
subscription.dispose();
}
});
return emitter.event;
}
/**
* Given an event, it returns another event which fires only once and as soon as
* the input event emits. The event data is the number of millis it took for the
* event to fire.
*/
export function stopwatch<T>(event: Event<T>): Event<number> {
const start = new Date().getTime();
return map(once(event), _ => new Date().getTime() - start);
}
/**
* Given an event, it returns another event which fires only when the event
* element changes.
*/
export function latch<T>(event: Event<T>): Event<T> {
let firstCall = true;
let cache: T;
return filter(event, value => {
const shouldEmit = firstCall || value !== cache;
firstCall = false;
cache = value;
return shouldEmit;
});
}
/**
* 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> {
let buffer: T[] | null = _buffer.slice();
let listener: IDisposable | null = event(e => {
if (buffer) {
buffer.push(e);
} else {
emitter.fire(e);
}
});
const flush = () => {
if (buffer) {
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() {
if (listener) {
listener.dispose();
}
listener = null;
}
});
return emitter.event;
}
export interface IChainableEvent<T> {
event: Event<T>;
map<O>(fn: (i: T) => O): IChainableEvent<O>;
forEach(fn: (i: T) => void): IChainableEvent<T>;
filter(fn: (e: T) => boolean): IChainableEvent<T>;
filter<R>(fn: (e: T | R) => e is R): IChainableEvent<R>;
reduce<R>(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent<R>;
latch(): IChainableEvent<T>;
debounce(merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<T>;
debounce<R>(merge: (last: R | undefined, event: T) => R, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<R>;
on(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable;
once(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
}
class ChainableEvent<T> implements IChainableEvent<T> {
constructor(readonly event: Event<T>) { }
map<O>(fn: (i: T) => O): IChainableEvent<O> {
return new ChainableEvent(map(this.event, fn));
}
forEach(fn: (i: T) => void): IChainableEvent<T> {
return new ChainableEvent(forEach(this.event, fn));
}
filter(fn: (e: T) => boolean): IChainableEvent<T>;
filter<R>(fn: (e: T | R) => e is R): IChainableEvent<R>;
filter(fn: (e: T) => boolean): IChainableEvent<T> {
return new ChainableEvent(filter(this.event, fn));
}
reduce<R>(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent<R> {
return new ChainableEvent(reduce(this.event, merge, initial));
}
latch(): IChainableEvent<T> {
return new ChainableEvent(latch(this.event));
}
debounce(merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<T>;
debounce<R>(merge: (last: R | undefined, event: T) => R, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<R>;
debounce<R>(merge: (last: R | undefined, event: T) => R, delay: number = 100, leading = false, leakWarningThreshold?: number): IChainableEvent<R> {
return new ChainableEvent(debounce(this.event, merge, delay, leading, leakWarningThreshold));
}
on(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[] | DisposableStore) {
return this.event(listener, thisArgs, disposables);
}
once(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) {
return once(this.event)(listener, thisArgs, disposables);
}
}
export function chain<T>(event: Event<T>): IChainableEvent<T> {
return new ChainableEvent(event);
}
export interface NodeEventEmitter {
on(event: string | symbol, listener: Function): unknown;
removeListener(event: string | symbol, listener: Function): unknown;
}
export function fromNodeEventEmitter<T>(emitter: NodeEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {
const fn = (...args: any[]) => result.fire(map(...args));
const onFirstListenerAdd = () => emitter.on(eventName, fn);
const onLastListenerRemove = () => emitter.removeListener(eventName, fn);
const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove });
return result.event;
}
export interface DOMEventEmitter {
addEventListener(event: string | symbol, listener: Function): void;
removeEventListener(event: string | symbol, listener: Function): void;
}
export function fromDOMEventEmitter<T>(emitter: DOMEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {
const fn = (...args: any[]) => result.fire(map(...args));
const onFirstListenerAdd = () => emitter.addEventListener(eventName, fn);
const onLastListenerRemove = () => emitter.removeEventListener(eventName, fn);
const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove });
return result.event;
}
export function fromPromise<T = any>(promise: Promise<T>): Event<undefined> {
const emitter = new Emitter<undefined>();
let shouldEmit = false;
promise
.then(undefined, () => null)
.then(() => {
if (!shouldEmit) {
setTimeout(() => emitter.fire(undefined), 0);
} else {
emitter.fire(undefined);
}
});
shouldEmit = true;
return emitter.event;
}
export function toPromise<T>(event: Event<T>): Promise<T> {
return new Promise(c => once(event)(c));
}
}
type Listener<T> = [(e: T) => void, any] | ((e: T) => void);
export interface EmitterOptions {
onFirstListenerAdd?: Function;
onFirstListenerDidAdd?: Function;
onListenerDidAdd?: Function;
onLastListenerRemove?: Function;
leakWarningThreshold?: number;
}
let _globalLeakWarningThreshold = -1;
export function setGlobalLeakWarningThreshold(n: number): IDisposable {
const oldValue = _globalLeakWarningThreshold;
_globalLeakWarningThreshold = n;
return {
dispose() {
_globalLeakWarningThreshold = oldValue;
}
};
}
class LeakageMonitor {
private _stacks: Map<string, number> | undefined;
private _warnCountdown: number = 0;
constructor(
readonly customThreshold?: number,
readonly name: string = Math.random().toString(18).slice(2, 5),
) { }
dispose(): void {
if (this._stacks) {
this._stacks.clear();
}
}
check(listenerCount: number): undefined | (() => void) {
let threshold = _globalLeakWarningThreshold;
if (typeof this.customThreshold === 'number') {
threshold = this.customThreshold;
}
if (threshold <= 0 || listenerCount < threshold) {
return undefined;
}
if (!this._stacks) {
this._stacks = new Map();
}
const stack = new Error().stack!.split('\n').slice(3).join('\n');
const count = (this._stacks.get(stack) || 0);
this._stacks.set(stack, count + 1);
this._warnCountdown -= 1;
if (this._warnCountdown <= 0) {
// only warn on first exceed and then every time the limit
// is exceeded by 50% again
this._warnCountdown = threshold * 0.5;
// find most frequent listener and print warning
let topStack: string | undefined;
let topCount: number = 0;
for (const [stack, count] of this._stacks) {
if (!topStack || topCount < count) {
topStack = stack;
topCount = count;
}
}
console.warn(`[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`);
console.warn(topStack!);
}
return () => {
const count = (this._stacks!.get(stack) || 0);
this._stacks!.set(stack, count - 1);
};
}
}
/**
* The Emitter can be used to expose an Event to the public
* to fire it from the insides.
* Sample:
class Document {
private readonly _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 readonly _noop = function () { };
private readonly _options?: EmitterOptions;
private readonly _leakageMon?: LeakageMonitor;
private _disposed: boolean = false;
private _event?: Event<T>;
private _deliveryQueue?: LinkedList<[Listener<T>, T]>;
protected _listeners?: LinkedList<Listener<T>>;
constructor(options?: EmitterOptions) {
this._options = options;
this._leakageMon = _globalLeakWarningThreshold > 0
? new LeakageMonitor(this._options && this._options.leakWarningThreshold)
: undefined;
}
/**
* 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[] | DisposableStore) => {
if (!this._listeners) {
this._listeners = new LinkedList();
}
const firstListener = this._listeners.isEmpty();
if (firstListener && this._options && this._options.onFirstListenerAdd) {
this._options.onFirstListenerAdd(this);
}
const remove = this._listeners.push(!thisArgs ? listener : [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);
}
// check and record this emitter for potential leakage
let removeMonitor: (() => void) | undefined;
if (this._leakageMon) {
removeMonitor = this._leakageMon.check(this._listeners.size);
}
let result: IDisposable;
result = {
dispose: () => {
if (removeMonitor) {
removeMonitor();
}
result.dispose = Emitter._noop;
if (!this._disposed) {
remove();
if (this._options && this._options.onLastListenerRemove) {
const hasListeners = (this._listeners && !this._listeners.isEmpty());
if (!hasListeners) {
this._options.onLastListenerRemove(this);
}
}
}
}
};
if (disposables instanceof DisposableStore) {
disposables.add(result);
} else 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): void {
if (this._listeners) {
// put all [listener,event]-pairs into delivery queue
// then emit all event. an inner/nested event might be
// the driver of this
if (!this._deliveryQueue) {
this._deliveryQueue = new LinkedList();
}
for (let listener of this._listeners) {
this._deliveryQueue.push([listener, event]);
}
while (this._deliveryQueue.size > 0) {
const [listener, event] = this._deliveryQueue.shift()!;
try {
if (typeof listener === 'function') {
listener.call(undefined, event);
} else {
listener[0].call(listener[1], event);
}
} catch (e) {
onUnexpectedError(e);
}
}
}
}
dispose() {
if (this._listeners) {
this._listeners.clear();
}
if (this._deliveryQueue) {
this._deliveryQueue.clear();
}
if (this._leakageMon) {
this._leakageMon.dispose();
}
this._disposed = true;
}
}
export class PauseableEmitter<T> extends Emitter<T> {
private _isPaused = 0;
private _eventQueue = new LinkedList<T>();
private _mergeFn?: (input: T[]) => T;
constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) {
super(options);
this._mergeFn = options && options.merge;
}
pause(): void {
this._isPaused++;
}
resume(): void {
if (this._isPaused !== 0 && --this._isPaused === 0) {
if (this._mergeFn) {
// use the merge function to create a single composite
// event. make a copy in case firing pauses this emitter
const events = this._eventQueue.toArray();
this._eventQueue.clear();
super.fire(this._mergeFn(events));
} else {
// no merging, fire each event individually and test
// that this emitter isn't paused halfway through
while (!this._isPaused && this._eventQueue.size !== 0) {
super.fire(this._eventQueue.shift()!);
}
}
}
}
fire(event: T): void {
if (this._listeners) {
if (this._isPaused !== 0) {
this._eventQueue.push(event);
} else {
super.fire(event);
}
}
}
}
export interface IWaitUntil {
waitUntil(thenable: Promise<any>): void;
}
export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
private _asyncDeliveryQueue?: LinkedList<[Listener<T>, Omit<T, 'waitUntil'>]>;
async fireAsync(data: Omit<T, 'waitUntil'>, token: CancellationToken, promiseJoin?: (p: Promise<any>, listener: Function) => Promise<any>): Promise<void> {
if (!this._listeners) {
return;
}
if (!this._asyncDeliveryQueue) {
this._asyncDeliveryQueue = new LinkedList();
}
for (const listener of this._listeners) {
this._asyncDeliveryQueue.push([listener, data]);
}
while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) {
const [listener, data] = this._asyncDeliveryQueue.shift()!;
const thenables: Promise<any>[] = [];
const event = <T>{
...data,
waitUntil: (p: Promise<any>): void => {
if (Object.isFrozen(thenables)) {
throw new Error('waitUntil can NOT be called asynchronous');
}
if (promiseJoin) {
p = promiseJoin(p, typeof listener === 'function' ? listener : listener[0]);
}
thenables.push(p);
}
};
try {
if (typeof listener === 'function') {
listener.call(undefined, event);
} else {
listener[0].call(listener[1], event);
}
} catch (e) {
onUnexpectedError(e);
continue;
}
// freeze thenables-collection to enforce sync-calls to
// wait until and then wait for all thenables to resolve
Object.freeze(thenables);
await Promise.all(thenables).catch(e => onUnexpectedError(e));
}
}
}
export class EventMultiplexer<T> implements IDisposable {
private readonly emitter: Emitter<T>;
private hasListeners = false;
private events: { event: Event<T>; listener: IDisposable | null; }[] = [];
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 | null; }): void {
e.listener = e.event(r => this.emitter.fire(r));
}
private unhook(e: { event: Event<T>; listener: IDisposable | null; }): void {
if (e.listener) {
e.listener.dispose();
}
e.listener = null;
}
dispose(): void {
this.emitter.dispose();
}
}
/**
* The EventBufferer 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);
}
}, undefined, disposables);
};
}
bufferEvents<R = void>(fn: () => R): R {
const buffer: Array<() => R> = [];
this.buffers.push(buffer);
const r = fn();
this.buffers.pop();
buffer.forEach(flush => flush());
return r;
}
}
/**
* A Relay is an event forwarder which functions as a replugabble event pipe.
* Once created, you can connect an input event to it and it will simply forward
* events from that input event through its own `event` property. The `input`
* can be changed at any point in time.
*/
export class Relay<T> implements IDisposable {
private listening = false;
private inputEvent: Event<T> = Event.None;
private inputEventListener: IDisposable = Disposable.None;
private readonly emitter = new Emitter<T>({
onFirstListenerDidAdd: () => {
this.listening = true;
this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter);
},
onLastListenerRemove: () => {
this.listening = false;
this.inputEventListener.dispose();
}
});
readonly event: Event<T> = this.emitter.event;
set input(event: Event<T>) {
this.inputEvent = event;
if (this.listening) {
this.inputEventListener.dispose();
this.inputEventListener = event(this.emitter.fire, this.emitter);
}
}
dispose() {
this.inputEventListener.dispose();
this.emitter.dispose();
}
}

View File

@@ -0,0 +1,338 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isWindows } from 'vs/base/common/platform';
import { startsWithIgnoreCase, equalsIgnoreCase, rtrim } from 'vs/base/common/strings';
import { CharCode } from 'vs/base/common/charCode';
import { sep, posix, isAbsolute, join, normalize } from 'vs/base/common/path';
import { isNumber } from 'vs/base/common/types';
export function isPathSeparator(code: number) {
return code === CharCode.Slash || code === CharCode.Backslash;
}
/**
* Takes a Windows OS path and changes backward slashes to forward slashes.
* This should only be done for OS paths from Windows (or user provided paths potentially from Windows).
* Using it on a Linux or MaxOS path might change it.
*/
export function toSlashes(osPath: string) {
return osPath.replace(/[\\/]/g, posix.sep);
}
/**
* 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 = posix.sep): string {
if (!path) {
return '';
}
const len = path.length;
const firstLetter = path.charCodeAt(0);
if (isPathSeparator(firstLetter)) {
if (isPathSeparator(path.charCodeAt(1))) {
// UNC candidate \\localhost\shares\ddd
// ^^^^^^^^^^^^^^^^^^^
if (!isPathSeparator(path.charCodeAt(2))) {
let pos = 3;
const start = pos;
for (; pos < len; pos++) {
if (isPathSeparator(path.charCodeAt(pos))) {
break;
}
}
if (start !== pos && !isPathSeparator(path.charCodeAt(pos + 1))) {
pos += 1;
for (; pos < len; pos++) {
if (isPathSeparator(path.charCodeAt(pos))) {
return path.slice(0, pos + 1) // consume this separator
.replace(/[\\/]/g, sep);
}
}
}
}
}
// /user/far
// ^
return sep;
} else if (isWindowsDriveLetter(firstLetter)) {
// check for windows drive letter c:\ or c:
if (path.charCodeAt(1) === CharCode.Colon) {
if (isPathSeparator(path.charCodeAt(2))) {
// 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++) {
if (isPathSeparator(path.charCodeAt(pos))) {
return path.slice(0, pos + 1); // consume this separator
}
}
}
return '';
}
/**
* 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;
const 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 WINDOWS_INVALID_FILE_CHARS = /[\\/:\*\?"<>\|]/g;
const UNIX_INVALID_FILE_CHARS = /[\\/]/g;
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])(\.(.*?))?$/i;
export function isValidBasename(name: string | null | undefined, isWindowsOS: boolean = isWindows): boolean {
const invalidFileChars = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS;
if (!name || name.length === 0 || /^\s+$/.test(name)) {
return false; // require a name that is not just whitespace
}
invalidFileChars.lastIndex = 0; // the holy grail of software development
if (invalidFileChars.test(name)) {
return false; // check for certain invalid file characters
}
if (isWindowsOS && WINDOWS_FORBIDDEN_NAMES.test(name)) {
return false; // check for certain invalid file names
}
if (name === '.' || name === '..') {
return false; // check for reserved values
}
if (isWindowsOS && name[name.length - 1] === '.') {
return false; // Windows: file cannot end with a "."
}
if (isWindowsOS && name.length !== name.trim().length) {
return false; // Windows: file cannot end with a whitespace
}
if (name.length > 255) {
return false; // most file systems do not allow files > 255 length
}
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(base: string, parentCandidate: string, ignoreCase?: boolean, separator = sep): boolean {
if (base === parentCandidate) {
return true;
}
if (!base || !parentCandidate) {
return false;
}
if (parentCandidate.length > base.length) {
return false;
}
if (ignoreCase) {
const beginsWith = startsWithIgnoreCase(base, parentCandidate);
if (!beginsWith) {
return false;
}
if (parentCandidate.length === base.length) {
return true; // same path, different casing
}
let sepOffset = parentCandidate.length;
if (parentCandidate.charAt(parentCandidate.length - 1) === separator) {
sepOffset--; // adjust the expected sep offset in case our candidate already ends in separator character
}
return base.charAt(sepOffset) === separator;
}
if (parentCandidate.charAt(parentCandidate.length - 1) !== separator) {
parentCandidate += separator;
}
return base.indexOf(parentCandidate) === 0;
}
export function isWindowsDriveLetter(char0: number): boolean {
return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z;
}
export function sanitizeFilePath(candidate: string, cwd: string): string {
// Special case: allow to open a drive letter without trailing backslash
if (isWindows && candidate.endsWith(':')) {
candidate += sep;
}
// Ensure absolute
if (!isAbsolute(candidate)) {
candidate = join(cwd, candidate);
}
// Ensure normalized
candidate = normalize(candidate);
// Ensure no trailing slash/backslash
if (isWindows) {
candidate = rtrim(candidate, sep);
// Special case: allow to open drive root ('C:\')
if (candidate.endsWith(':')) {
candidate += sep;
}
} else {
candidate = rtrim(candidate, sep);
// Special case: allow to open root ('/')
if (!candidate) {
candidate = sep;
}
}
return candidate;
}
export function isRootOrDriveLetter(path: string): boolean {
const pathNormalized = normalize(path);
if (isWindows) {
if (path.length > 3) {
return false;
}
return isWindowsDriveLetter(pathNormalized.charCodeAt(0))
&& pathNormalized.charCodeAt(1) === CharCode.Colon
&& (path.length === 2 || pathNormalized.charCodeAt(2) === CharCode.Backslash);
}
return pathNormalized === posix.sep;
}
export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number {
if (candidate.length > path.length) {
return -1;
}
if (path === candidate) {
return 0;
}
if (ignoreCase) {
path = path.toLowerCase();
candidate = candidate.toLowerCase();
}
return path.indexOf(candidate);
}
export interface IPathWithLineAndColumn {
path: string;
line?: number;
column?: number;
}
export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
let path: string | undefined = undefined;
let line: number | undefined = undefined;
let column: number | undefined = undefined;
segments.forEach(segment => {
const segmentAsNumber = Number(segment);
if (!isNumber(segmentAsNumber)) {
path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
} else if (line === undefined) {
line = segmentAsNumber;
} else if (column === undefined) {
column = segmentAsNumber;
}
});
if (!path) {
throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`');
}
return {
path,
line: line !== undefined ? line : undefined,
column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set
};
}

View File

@@ -0,0 +1,818 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CharCode } from 'vs/base/common/charCode';
import { LRUCache } from 'vs/base/common/map';
import * as strings from 'vs/base/common/strings';
export interface IFilter {
// Returns null if word doesn't match.
(word: string, wordToMatchAgainst: string): IMatch[] | null;
}
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[] | null {
for (let i = 0, len = filter.length; i < len; i++) {
const match = filter[i](word, wordToMatchAgainst);
if (match) {
return match;
}
}
return null;
};
}
// 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[] | null {
if (!wordToMatchAgainst || wordToMatchAgainst.length < word.length) {
return null;
}
let matches: boolean;
if (ignoreCase) {
matches = strings.startsWithIgnoreCase(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[] | null {
const 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[] | null {
return _matchesSubString(word.toLowerCase(), wordToMatchAgainst.toLowerCase(), 0, 0);
}
function _matchesSubString(word: string, wordToMatchAgainst: string, i: number, j: number): IMatch[] | null {
if (i === word.length) {
return [];
} else if (j === wordToMatchAgainst.length) {
return null;
} else {
if (word[i] === wordToMatchAgainst[j]) {
let result: IMatch[] | null = null;
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);
}
}
// CamelCase
function isLower(code: number): boolean {
return CharCode.a <= code && code <= CharCode.z;
}
export 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
);
}
const wordSeparators = new Set<number>();
'`~!@#$%^&*()-=+[{]}\\|;:\'",.<>/?'
.split('')
.forEach(s => wordSeparators.add(s.charCodeAt(0)));
function isWordSeparator(code: number): boolean {
return isWhitespace(code) || wordSeparators.has(code);
}
function charactersMatch(codeA: number, codeB: number): boolean {
return (codeA === codeB) || (isWordSeparator(codeA) && isWordSeparator(codeB));
}
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++) {
const 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[] | null {
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 = 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++; }
}
const upperPercent = upper / word.length;
const lowerPercent = lower / word.length;
const alphaPercent = alpha / word.length;
const 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[] | null {
if (!camelCaseWord) {
return null;
}
camelCaseWord = camelCaseWord.trim();
if (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 = null;
let i = 0;
word = word.toLowerCase();
while (i < camelCaseWord.length && (result = _matchesCamelCase(word, 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[] | null {
if (!target || target.length === 0) {
return null;
}
let result: IMatch[] | null = null;
let i = 0;
word = word.toLowerCase();
target = target.toLowerCase();
while (i < target.length && (result = _matchesWords(word, 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[] | null {
if (i === word.length) {
return [];
} else if (j === target.length) {
return null;
} else if (!charactersMatch(word.charCodeAt(i), target.charCodeAt(j))) {
return null;
} else {
let result: IMatch[] | null = 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++) {
if (isWordSeparator(word.charCodeAt(i)) ||
(i > 0 && isWordSeparator(word.charCodeAt(i - 1)))) {
return i;
}
}
return word.length;
}
// Fuzzy
const fuzzyContiguousFilter = or(matchesPrefix, matchesCamelCase, matchesContiguousSubString);
const fuzzySeparateFilter = or(matchesPrefix, matchesCamelCase, matchesSubString);
const fuzzyRegExpCache = new LRUCache<string, RegExp>(10000); // bounded to 10000 elements
export function matchesFuzzy(word: string, wordToMatchAgainst: string, enableSeparateSubstringMatching = false): IMatch[] | null {
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
const match = 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);
}
/**
* Match pattern againt word in a fuzzy way. As in IntelliSense and faster and more
* powerfull than `matchesFuzzy`
*/
export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null {
const score = fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, true);
return score ? createMatches(score) : null;
}
export function anyScore(pattern: string, lowPattern: string, _patternPos: number, word: string, lowWord: string, _wordPos: number): FuzzyScore {
const result = fuzzyScore(pattern, lowPattern, 0, word, lowWord, 0, true);
if (result) {
return result;
}
let matches = 0;
let score = 0;
let idx = _wordPos;
for (let patternPos = 0; patternPos < lowPattern.length && patternPos < _maxLen; ++patternPos) {
const wordPos = lowWord.indexOf(lowPattern.charAt(patternPos), idx);
if (wordPos >= 0) {
score += 1;
matches += 2 ** wordPos;
idx = wordPos + 1;
} else if (matches !== 0) {
// once we have started matching things
// we need to match the remaining pattern
// characters
break;
}
}
return [score, matches, _wordPos];
}
//#region --- fuzzyScore ---
export function createMatches(score: undefined | FuzzyScore): IMatch[] {
if (typeof score === 'undefined') {
return [];
}
const matches = score[1].toString(2);
const wordStart = score[2];
const res: IMatch[] = [];
for (let pos = wordStart; pos < _maxLen; pos++) {
if (matches[matches.length - (pos + 1)] === '1') {
const last = res[res.length - 1];
if (last && last.end === pos) {
last.end = pos + 1;
} else {
res.push({ start: pos, end: pos + 1 });
}
}
}
return res;
}
const _maxLen = 128;
function initTable() {
const table: number[][] = [];
const row: number[] = [0];
for (let i = 1; i <= _maxLen; i++) {
row.push(-i);
}
for (let i = 0; i <= _maxLen; i++) {
const 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;
}
function printTables(pattern: string, patternStart: number, word: string, wordStart: number): void {
pattern = pattern.substr(patternStart);
word = word.substr(wordStart);
console.log(printTable(_table, pattern, pattern.length, word, word.length));
console.log(printTable(_arrows, pattern, pattern.length, word, word.length));
console.log(printTable(_scores, pattern, pattern.length, word, word.length));
}
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:
case CharCode.DollarSign:
return true;
default:
return false;
}
}
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;
}
}
function isUpperCaseAtPos(pos: number, word: string, wordLow: string): boolean {
return word[pos] !== wordLow[pos];
}
export function isPatternInWord(patternLow: string, patternPos: number, patternLen: number, wordLow: string, wordPos: number, wordLen: number): boolean {
while (patternPos < patternLen && wordPos < wordLen) {
if (patternLow[patternPos] === wordLow[wordPos]) {
patternPos += 1;
}
wordPos += 1;
}
return patternPos === patternLen; // pattern must be exhausted
}
const enum Arrow { Top = 0b1, Diag = 0b10, Left = 0b100 }
/**
* A tuple of three values.
* 0. the score
* 1. the matches encoded as bitmask (2^53)
* 2. the offset at which matching started
*/
export type FuzzyScore = [number, number, number];
export namespace FuzzyScore {
/**
* No matches and value `-100`
*/
export const Default: [-100, 0, 0] = <[-100, 0, 0]>Object.freeze([-100, 0, 0]);
export function isDefault(score?: FuzzyScore): score is [-100, 0, 0] {
return !score || (score[0] === -100 && score[1] === 0 && score[2] === 0);
}
}
export interface FuzzyScorer {
(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined;
}
export function fuzzyScore(pattern: string, patternLow: string, patternStart: number, word: string, wordLow: string, wordStart: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
const wordLen = word.length > _maxLen ? _maxLen : word.length;
if (patternStart >= patternLen || wordStart >= wordLen || (patternLen - patternStart) > (wordLen - wordStart)) {
return undefined;
}
// 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
if (!isPatternInWord(patternLow, patternStart, patternLen, wordLow, wordStart, wordLen)) {
return undefined;
}
let row: number = 1;
let column: number = 1;
let patternPos = patternStart;
let wordPos = wordStart;
let hasStrongFirstMatch = false;
// There will be a match, fill in tables
for (row = 1, patternPos = patternStart; patternPos < patternLen; row++, patternPos++) {
for (column = 1, wordPos = wordStart; wordPos < wordLen; column++, wordPos++) {
const score = _doScore(pattern, patternLow, patternPos, patternStart, word, wordLow, wordPos);
if (patternPos === patternStart && score > 1) {
hasStrongFirstMatch = true;
}
_scores[row][column] = score;
const diag = _table[row - 1][column - 1] + (score > 1 ? 1 : score);
const top = _table[row - 1][column] + -1;
const left = _table[row][column - 1] + -1;
if (left >= top) {
// left or diag
if (left > diag) {
_table[row][column] = left;
_arrows[row][column] = Arrow.Left;
} else if (left === diag) {
_table[row][column] = left;
_arrows[row][column] = Arrow.Left | Arrow.Diag;
} else {
_table[row][column] = diag;
_arrows[row][column] = Arrow.Diag;
}
} else {
// top or diag
if (top > diag) {
_table[row][column] = top;
_arrows[row][column] = Arrow.Top;
} else if (top === diag) {
_table[row][column] = top;
_arrows[row][column] = Arrow.Top | Arrow.Diag;
} else {
_table[row][column] = diag;
_arrows[row][column] = Arrow.Diag;
}
}
}
}
if (_debug) {
printTables(pattern, patternStart, word, wordStart);
}
if (!hasStrongFirstMatch && !firstMatchCanBeWeak) {
return undefined;
}
_matchesCount = 0;
_topScore = -100;
_wordStart = wordStart;
_firstMatchCanBeWeak = firstMatchCanBeWeak;
_findAllMatches2(row - 1, column - 1, patternLen === wordLen ? 1 : 0, 0, false);
if (_matchesCount === 0) {
return undefined;
}
return [_topScore, _topMatch2, wordStart];
}
function _doScore(pattern: string, patternLow: string, patternPos: number, patternStart: number, word: string, wordLow: string, wordPos: number) {
if (patternLow[patternPos] !== wordLow[wordPos]) {
return -1;
}
if (wordPos === (patternPos - patternStart)) {
// common prefix: `foobar <-> foobaz`
// ^^^^^
if (pattern[patternPos] === word[wordPos]) {
return 7;
} else {
return 5;
}
} else if (isUpperCaseAtPos(wordPos, word, wordLow) && (wordPos === 0 || !isUpperCaseAtPos(wordPos - 1, word, wordLow))) {
// hitting upper-case: `foo <-> forOthers`
// ^^ ^
if (pattern[patternPos] === word[wordPos]) {
return 7;
} else {
return 5;
}
} else if (isSeparatorAtPos(wordLow, wordPos) && (wordPos === 0 || !isSeparatorAtPos(wordLow, wordPos - 1))) {
// hitting a separator: `. <-> foo.bar`
// ^
return 5;
} else if (isSeparatorAtPos(wordLow, wordPos - 1) || isWhitespaceAtPos(wordLow, wordPos - 1)) {
// post separator: `foo <-> bar_foo`
// ^^^
return 5;
} else {
return 1;
}
}
let _matchesCount: number = 0;
let _topMatch2: number = 0;
let _topScore: number = 0;
let _wordStart: number = 0;
let _firstMatchCanBeWeak: boolean = false;
function _findAllMatches2(row: number, column: number, total: number, matches: number, lastMatched: boolean): void {
if (_matchesCount >= 10 || total < -25) {
// stop when having already 10 results, or
// when a potential alignment as already 5 gaps
return;
}
let simpleMatchCount = 0;
while (row > 0 && column > 0) {
const score = _scores[row][column];
const arrow = _arrows[row][column];
if (arrow === Arrow.Left) {
// left -> no match, skip a word character
column -= 1;
if (lastMatched) {
total -= 5; // new gap penalty
} else if (matches !== 0) {
total -= 1; // gap penalty after first match
}
lastMatched = false;
simpleMatchCount = 0;
} else if (arrow & Arrow.Diag) {
if (arrow & Arrow.Left) {
// left
_findAllMatches2(
row,
column - 1,
matches !== 0 ? total - 1 : total, // gap penalty after first match
matches,
lastMatched
);
}
// diag
total += score;
row -= 1;
column -= 1;
lastMatched = true;
// match -> set a 1 at the word pos
matches += 2 ** (column + _wordStart);
// count simple matches and boost a row of
// simple matches when they yield in a
// strong match.
if (score === 1) {
simpleMatchCount += 1;
if (row === 0 && !_firstMatchCanBeWeak) {
// 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 -= column >= 3 ? 9 : column * 3; // late start penalty
// 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;
_topMatch2 = matches;
}
}
//#endregion
//#region --- graceful ---
export function fuzzyScoreGracefulAggressive(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
return fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, true, firstMatchCanBeWeak);
}
export function fuzzyScoreGraceful(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
return fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, false, firstMatchCanBeWeak);
}
function fuzzyScoreWithPermutations(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, aggressive: boolean, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
let top = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, firstMatchCanBeWeak);
if (top && !aggressive) {
// when using the original pattern yield a result we`
// return it unless we are aggressive and try to find
// a better alignment, e.g. `cno` -> `^co^ns^ole` or `^c^o^nsole`.
return top;
}
if (pattern.length >= 3) {
// When the pattern is long enough then try a few (max 7)
// permutations of the pattern to find a better match. The
// permutations only swap neighbouring characters, e.g
// `cnoso` becomes `conso`, `cnsoo`, `cnoos`.
const tries = Math.min(7, pattern.length - 1);
for (let movingPatternPos = patternPos + 1; movingPatternPos < tries; movingPatternPos++) {
const newPattern = nextTypoPermutation(pattern, movingPatternPos);
if (newPattern) {
const candidate = fuzzyScore(newPattern, newPattern.toLowerCase(), patternPos, word, lowWord, wordPos, firstMatchCanBeWeak);
if (candidate) {
candidate[0] -= 3; // permutation penalty
if (!top || candidate[0] > top[0]) {
top = candidate;
}
}
}
}
}
return top;
}
function nextTypoPermutation(pattern: string, patternPos: number): string | undefined {
if (patternPos + 1 >= pattern.length) {
return undefined;
}
const swap1 = pattern[patternPos];
const swap2 = pattern[patternPos + 1];
if (swap1 === swap2) {
return undefined;
}
return pattern.slice(0, patternPos)
+ swap2
+ swap1
+ pattern.slice(patternPos + 2);
}
//#endregion

View File

@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function once<T extends Function>(this: unknown, fn: T): T {
const _this = this;
let didCall = false;
let result: unknown;
return function () {
if (didCall) {
return result;
}
didCall = true;
result = fn.apply(_this, arguments);
return result;
} as unknown as T;
}

View File

@@ -0,0 +1,882 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { compareAnything } from 'vs/base/common/comparers';
import { matchesPrefix, IMatch, isUpper, fuzzyScore, createMatches as createFuzzyMatches } from 'vs/base/common/filters';
import { sep } from 'vs/base/common/path';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings';
import { CharCode } from 'vs/base/common/charCode';
//#region Fuzzy scorer
export type FuzzyScore = [number /* score */, number[] /* match positions */];
export type FuzzyScorerCache = { [key: string]: IItemScore };
const NO_MATCH = 0;
const NO_SCORE: FuzzyScore = [NO_MATCH, []];
// const DEBUG = false;
// const DEBUG_MATRIX = false;
export function scoreFuzzy(target: string, query: string, queryLower: string, fuzzy: boolean): FuzzyScore {
if (!target || !query) {
return NO_SCORE; // return early if target or query are undefined
}
const targetLength = target.length;
const queryLength = query.length;
if (targetLength < queryLength) {
return NO_SCORE; // impossible for query to be contained in target
}
// if (DEBUG) {
// console.group(`Target: ${target}, Query: ${query}`);
// }
const targetLower = target.toLowerCase();
// When not searching fuzzy, we require the query to be contained fully
// in the target string contiguously.
if (!fuzzy) {
if (!targetLower.includes(queryLower)) {
// if (DEBUG) {
// console.log(`Characters not matching consecutively ${queryLower} within ${targetLower}`);
// }
return NO_SCORE;
}
}
const res = doScoreFuzzy(query, queryLower, queryLength, target, targetLower, targetLength);
// if (DEBUG) {
// console.log(`%cFinal Score: ${res[0]}`, 'font-weight: bold');
// console.groupEnd();
// }
return res;
}
function doScoreFuzzy(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number): FuzzyScore {
const scores: number[] = [];
const matches: number[] = [];
//
// Build Scorer Matrix:
//
// The matrix is composed of query q and target t. For each index we score
// q[i] with t[i] and compare that with the previous score. If the score is
// equal or larger, we keep the match. In addition to the score, we also keep
// the length of the consecutive matches to use as boost for the score.
//
// t a r g e t
// q
// u
// e
// r
// y
//
for (let queryIndex = 0; queryIndex < queryLength; queryIndex++) {
const queryIndexOffset = queryIndex * targetLength;
const queryIndexPreviousOffset = queryIndexOffset - targetLength;
const queryIndexGtNull = queryIndex > 0;
const queryCharAtIndex = query[queryIndex];
const queryLowerCharAtIndex = queryLower[queryIndex];
for (let targetIndex = 0; targetIndex < targetLength; targetIndex++) {
const targetIndexGtNull = targetIndex > 0;
const currentIndex = queryIndexOffset + targetIndex;
const leftIndex = currentIndex - 1;
const diagIndex = queryIndexPreviousOffset + targetIndex - 1;
const leftScore = targetIndexGtNull ? scores[leftIndex] : 0;
const diagScore = queryIndexGtNull && targetIndexGtNull ? scores[diagIndex] : 0;
const matchesSequenceLength = queryIndexGtNull && targetIndexGtNull ? matches[diagIndex] : 0;
// If we are not matching on the first query character any more, we only produce a
// score if we had a score previously for the last query index (by looking at the diagScore).
// This makes sure that the query always matches in sequence on the target. For example
// given a target of "ede" and a query of "de", we would otherwise produce a wrong high score
// for query[1] ("e") matching on target[0] ("e") because of the "beginning of word" boost.
let score: number;
if (!diagScore && queryIndexGtNull) {
score = 0;
} else {
score = computeCharScore(queryCharAtIndex, queryLowerCharAtIndex, target, targetLower, targetIndex, matchesSequenceLength);
}
// We have a score and its equal or larger than the left score
// Match: sequence continues growing from previous diag value
// Score: increases by diag score value
if (score && diagScore + score >= leftScore) {
matches[currentIndex] = matchesSequenceLength + 1;
scores[currentIndex] = diagScore + score;
}
// We either have no score or the score is lower than the left score
// Match: reset to 0
// Score: pick up from left hand side
else {
matches[currentIndex] = NO_MATCH;
scores[currentIndex] = leftScore;
}
}
}
// Restore Positions (starting from bottom right of matrix)
const positions: number[] = [];
let queryIndex = queryLength - 1;
let targetIndex = targetLength - 1;
while (queryIndex >= 0 && targetIndex >= 0) {
const currentIndex = queryIndex * targetLength + targetIndex;
const match = matches[currentIndex];
if (match === NO_MATCH) {
targetIndex--; // go left
} else {
positions.push(targetIndex);
// go up and left
queryIndex--;
targetIndex--;
}
}
// Print matrix
// if (DEBUG_MATRIX) {
// printMatrix(query, target, matches, scores);
// }
return [scores[queryLength * targetLength - 1], positions.reverse()];
}
function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: string, target: string, targetLower: string, targetIndex: number, matchesSequenceLength: number): number {
let score = 0;
if (!considerAsEqual(queryLowerCharAtIndex, targetLower[targetIndex])) {
return score; // no match of characters
}
// Character match bonus
score += 1;
// if (DEBUG) {
// console.groupCollapsed(`%cCharacter match bonus: +1 (char: ${queryLowerCharAtIndex} at index ${targetIndex}, total score: ${score})`, 'font-weight: normal');
// }
// Consecutive match bonus
if (matchesSequenceLength > 0) {
score += (matchesSequenceLength * 5);
// if (DEBUG) {
// console.log(`Consecutive match bonus: +${matchesSequenceLength * 5}`);
// }
}
// Same case bonus
if (queryCharAtIndex === target[targetIndex]) {
score += 1;
// if (DEBUG) {
// console.log('Same case bonus: +1');
// }
}
// Start of word bonus
if (targetIndex === 0) {
score += 8;
// if (DEBUG) {
// console.log('Start of word bonus: +8');
// }
}
else {
// After separator bonus
const separatorBonus = scoreSeparatorAtPos(target.charCodeAt(targetIndex - 1));
if (separatorBonus) {
score += separatorBonus;
// if (DEBUG) {
// console.log(`After separtor bonus: +${separatorBonus}`);
// }
}
// Inside word upper case bonus (camel case)
else if (isUpper(target.charCodeAt(targetIndex))) {
score += 2;
// if (DEBUG) {
// console.log('Inside word upper case bonus: +2');
// }
}
}
// if (DEBUG) {
// console.groupEnd();
// }
return score;
}
function considerAsEqual(a: string, b: string): boolean {
if (a === b) {
return true;
}
// Special case path spearators: ignore platform differences
if (a === '/' || a === '\\') {
return b === '/' || b === '\\';
}
return false;
}
function scoreSeparatorAtPos(charCode: number): number {
switch (charCode) {
case CharCode.Slash:
case CharCode.Backslash:
return 5; // prefer path separators...
case CharCode.Underline:
case CharCode.Dash:
case CharCode.Period:
case CharCode.Space:
case CharCode.SingleQuote:
case CharCode.DoubleQuote:
case CharCode.Colon:
return 4; // ...over other separators
default:
return 0;
}
}
// function printMatrix(query: string, target: string, matches: number[], scores: number[]): void {
// console.log('\t' + target.split('').join('\t'));
// for (let queryIndex = 0; queryIndex < query.length; queryIndex++) {
// let line = query[queryIndex] + '\t';
// for (let targetIndex = 0; targetIndex < target.length; targetIndex++) {
// const currentIndex = queryIndex * target.length + targetIndex;
// line = line + 'M' + matches[currentIndex] + '/' + 'S' + scores[currentIndex] + '\t';
// }
// console.log(line);
// }
// }
//#endregion
//#region Alternate fuzzy scorer implementation that is e.g. used for symbols
export type FuzzyScore2 = [number | undefined /* score */, IMatch[]];
const NO_SCORE2: FuzzyScore2 = [undefined, []];
export function scoreFuzzy2(target: string, query: IPreparedQuery | IPreparedQueryPiece, patternStart = 0, wordStart = 0): FuzzyScore2 {
// Score: multiple inputs
const preparedQuery = query as IPreparedQuery;
if (preparedQuery.values && preparedQuery.values.length > 1) {
return doScoreFuzzy2Multiple(target, preparedQuery.values, patternStart, wordStart);
}
// Score: single input
return doScoreFuzzy2Single(target, query, patternStart, wordStart);
}
function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], patternStart: number, wordStart: number): FuzzyScore2 {
let totalScore = 0;
const totalMatches: IMatch[] = [];
for (const queryPiece of query) {
const [score, matches] = doScoreFuzzy2Single(target, queryPiece, patternStart, wordStart);
if (typeof score !== 'number') {
// if a single query value does not match, return with
// no score entirely, we require all queries to match
return NO_SCORE2;
}
totalScore += score;
totalMatches.push(...matches);
}
// if we have a score, ensure that the positions are
// sorted in ascending order and distinct
return [totalScore, normalizeMatches(totalMatches)];
}
function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, wordStart: number): FuzzyScore2 {
const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), wordStart, true);
if (!score) {
return NO_SCORE2;
}
return [score[0], createFuzzyMatches(score)];
}
//#endregion
//#region Item (label, description, path) scorer
/**
* Scoring on structural items that have a label and optional description.
*/
export interface IItemScore {
/**
* Overall score.
*/
score: number;
/**
* Matches within the label.
*/
labelMatch?: IMatch[];
/**
* Matches within the description.
*/
descriptionMatch?: IMatch[];
}
const NO_ITEM_SCORE: IItemScore = Object.freeze({ score: 0 });
export interface IItemAccessor<T> {
/**
* Just the label of the item to score on.
*/
getItemLabel(item: T): string | undefined;
/**
* The optional description of the item to score on.
*/
getItemDescription(item: T): string | undefined;
/**
* If the item is a file, the path of the file to score on.
*/
getItemPath(file: T): string | undefined;
}
const PATH_IDENTITY_SCORE = 1 << 18;
const LABEL_PREFIX_SCORE_THRESHOLD = 1 << 17;
const LABEL_SCORE_THRESHOLD = 1 << 16;
export function scoreItemFuzzy<T>(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: FuzzyScorerCache): IItemScore {
if (!item || !query.normalized) {
return NO_ITEM_SCORE; // we need an item and query to score on at least
}
const label = accessor.getItemLabel(item);
if (!label) {
return NO_ITEM_SCORE; // we need a label at least
}
const description = accessor.getItemDescription(item);
// in order to speed up scoring, we cache the score with a unique hash based on:
// - label
// - description (if provided)
// - query (normalized)
// - number of query pieces (i.e. 'hello world' and 'helloworld' are different)
// - wether fuzzy matching is enabled or not
let cacheHash: string;
if (description) {
cacheHash = `${label}${description}${query.normalized}${Array.isArray(query.values) ? query.values.length : ''}${fuzzy}`;
} else {
cacheHash = `${label}${query.normalized}${Array.isArray(query.values) ? query.values.length : ''}${fuzzy}`;
}
const cached = cache[cacheHash];
if (cached) {
return cached;
}
const itemScore = doScoreItemFuzzy(label, description, accessor.getItemPath(item), query, fuzzy);
cache[cacheHash] = itemScore;
return itemScore;
}
function doScoreItemFuzzy(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore {
const preferLabelMatches = !path || !query.containsPathSeparator;
// Treat identity matches on full path highest
if (path && (isLinux ? query.pathNormalized === path : equalsIgnoreCase(query.pathNormalized, path))) {
return { score: PATH_IDENTITY_SCORE, labelMatch: [{ start: 0, end: label.length }], descriptionMatch: description ? [{ start: 0, end: description.length }] : undefined };
}
// Score: multiple inputs
if (query.values && query.values.length > 1) {
return doScoreItemFuzzyMultiple(label, description, path, query.values, preferLabelMatches, fuzzy);
}
// Score: single input
return doScoreItemFuzzySingle(label, description, path, query, preferLabelMatches, fuzzy);
}
function doScoreItemFuzzyMultiple(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece[], preferLabelMatches: boolean, fuzzy: boolean): IItemScore {
let totalScore = 0;
const totalLabelMatches: IMatch[] = [];
const totalDescriptionMatches: IMatch[] = [];
for (const queryPiece of query) {
const { score, labelMatch, descriptionMatch } = doScoreItemFuzzySingle(label, description, path, queryPiece, preferLabelMatches, fuzzy);
if (score === NO_MATCH) {
// if a single query value does not match, return with
// no score entirely, we require all queries to match
return NO_ITEM_SCORE;
}
totalScore += score;
if (labelMatch) {
totalLabelMatches.push(...labelMatch);
}
if (descriptionMatch) {
totalDescriptionMatches.push(...descriptionMatch);
}
}
// if we have a score, ensure that the positions are
// sorted in ascending order and distinct
return {
score: totalScore,
labelMatch: normalizeMatches(totalLabelMatches),
descriptionMatch: normalizeMatches(totalDescriptionMatches)
};
}
function doScoreItemFuzzySingle(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece, preferLabelMatches: boolean, fuzzy: boolean): IItemScore {
// Prefer label matches if told so or we have no description
if (preferLabelMatches || !description) {
const [labelScore, labelPositions] = scoreFuzzy(label, query.normalized, query.normalizedLowercase, fuzzy);
if (labelScore) {
// If we have a prefix match on the label, we give a much
// higher baseScore to elevate these matches over others
// This ensures that typing a file name wins over results
// that are present somewhere in the label, but not the
// beginning.
const labelPrefixMatch = matchesPrefix(query.normalized, label);
let baseScore: number;
if (labelPrefixMatch) {
baseScore = LABEL_PREFIX_SCORE_THRESHOLD;
// We give another boost to labels that are short, e.g. given
// files "window.ts" and "windowActions.ts" and a query of
// "window", we want "window.ts" to receive a higher score.
// As such we compute the percentage the query has within the
// label and add that to the baseScore.
const prefixLengthBoost = Math.round((query.normalized.length / label.length) * 100);
baseScore += prefixLengthBoost;
} else {
baseScore = LABEL_SCORE_THRESHOLD;
}
return { score: baseScore + labelScore, labelMatch: labelPrefixMatch || createMatches(labelPositions) };
}
}
// Finally compute description + label scores if we have a description
if (description) {
let descriptionPrefix = description;
if (!!path) {
descriptionPrefix = `${description}${sep}`; // assume this is a file path
}
const descriptionPrefixLength = descriptionPrefix.length;
const descriptionAndLabel = `${descriptionPrefix}${label}`;
const [labelDescriptionScore, labelDescriptionPositions] = scoreFuzzy(descriptionAndLabel, query.normalized, query.normalizedLowercase, fuzzy);
if (labelDescriptionScore) {
const labelDescriptionMatches = createMatches(labelDescriptionPositions);
const labelMatch: IMatch[] = [];
const descriptionMatch: IMatch[] = [];
// We have to split the matches back onto the label and description portions
labelDescriptionMatches.forEach(h => {
// Match overlaps label and description part, we need to split it up
if (h.start < descriptionPrefixLength && h.end > descriptionPrefixLength) {
labelMatch.push({ start: 0, end: h.end - descriptionPrefixLength });
descriptionMatch.push({ start: h.start, end: descriptionPrefixLength });
}
// Match on label part
else if (h.start >= descriptionPrefixLength) {
labelMatch.push({ start: h.start - descriptionPrefixLength, end: h.end - descriptionPrefixLength });
}
// Match on description part
else {
descriptionMatch.push(h);
}
});
return { score: labelDescriptionScore, labelMatch, descriptionMatch };
}
}
return NO_ITEM_SCORE;
}
function createMatches(offsets: number[] | undefined): IMatch[] {
const ret: IMatch[] = [];
if (!offsets) {
return ret;
}
let last: IMatch | undefined;
for (const pos of offsets) {
if (last && last.end === pos) {
last.end += 1;
} else {
last = { start: pos, end: pos + 1 };
ret.push(last);
}
}
return ret;
}
function normalizeMatches(matches: IMatch[]): IMatch[] {
// sort matches by start to be able to normalize
const sortedMatches = matches.sort((matchA, matchB) => {
return matchA.start - matchB.start;
});
// merge matches that overlap
const normalizedMatches: IMatch[] = [];
let currentMatch: IMatch | undefined = undefined;
for (const match of sortedMatches) {
// if we have no current match or the matches
// do not overlap, we take it as is and remember
// it for future merging
if (!currentMatch || !matchOverlaps(currentMatch, match)) {
currentMatch = match;
normalizedMatches.push(match);
}
// otherwise we merge the matches
else {
currentMatch.start = Math.min(currentMatch.start, match.start);
currentMatch.end = Math.max(currentMatch.end, match.end);
}
}
return normalizedMatches;
}
function matchOverlaps(matchA: IMatch, matchB: IMatch): boolean {
if (matchA.end < matchB.start) {
return false; // A ends before B starts
}
if (matchB.end < matchA.start) {
return false; // B ends before A starts
}
return true;
}
//#endregion
//#region Comparers
export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: FuzzyScorerCache): number {
const itemScoreA = scoreItemFuzzy(itemA, query, fuzzy, accessor, cache);
const itemScoreB = scoreItemFuzzy(itemB, query, fuzzy, accessor, cache);
const scoreA = itemScoreA.score;
const scoreB = itemScoreB.score;
// 1.) identity matches have highest score
if (scoreA === PATH_IDENTITY_SCORE || scoreB === PATH_IDENTITY_SCORE) {
if (scoreA !== scoreB) {
return scoreA === PATH_IDENTITY_SCORE ? -1 : 1;
}
}
// 2.) matches on label are considered higher compared to label+description matches
if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) {
if (scoreA !== scoreB) {
return scoreA > scoreB ? -1 : 1;
}
// prefer more compact matches over longer in label (unless this is a prefix match where
// longer prefix matches are actually preferred)
if (scoreA < LABEL_PREFIX_SCORE_THRESHOLD && scoreB < LABEL_PREFIX_SCORE_THRESHOLD) {
const comparedByMatchLength = compareByMatchLength(itemScoreA.labelMatch, itemScoreB.labelMatch);
if (comparedByMatchLength !== 0) {
return comparedByMatchLength;
}
}
// prefer shorter labels over longer labels
const labelA = accessor.getItemLabel(itemA) || '';
const labelB = accessor.getItemLabel(itemB) || '';
if (labelA.length !== labelB.length) {
return labelA.length - labelB.length;
}
}
// 3.) compare by score in label+description
if (scoreA !== scoreB) {
return scoreA > scoreB ? -1 : 1;
}
// 4.) scores are identical: prefer matches in label over non-label matches
const itemAHasLabelMatches = Array.isArray(itemScoreA.labelMatch) && itemScoreA.labelMatch.length > 0;
const itemBHasLabelMatches = Array.isArray(itemScoreB.labelMatch) && itemScoreB.labelMatch.length > 0;
if (itemAHasLabelMatches && !itemBHasLabelMatches) {
return -1;
} else if (itemBHasLabelMatches && !itemAHasLabelMatches) {
return 1;
}
// 5.) scores are identical: prefer more compact matches (label and description)
const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor);
const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor);
if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) {
return itemBMatchDistance > itemAMatchDistance ? -1 : 1;
}
// 6.) scores are identical: start to use the fallback compare
return fallbackCompare(itemA, itemB, query, accessor);
}
function computeLabelAndDescriptionMatchDistance<T>(item: T, score: IItemScore, accessor: IItemAccessor<T>): number {
let matchStart: number = -1;
let matchEnd: number = -1;
// If we have description matches, the start is first of description match
if (score.descriptionMatch && score.descriptionMatch.length) {
matchStart = score.descriptionMatch[0].start;
}
// Otherwise, the start is the first label match
else if (score.labelMatch && score.labelMatch.length) {
matchStart = score.labelMatch[0].start;
}
// If we have label match, the end is the last label match
// If we had a description match, we add the length of the description
// as offset to the end to indicate this.
if (score.labelMatch && score.labelMatch.length) {
matchEnd = score.labelMatch[score.labelMatch.length - 1].end;
if (score.descriptionMatch && score.descriptionMatch.length) {
const itemDescription = accessor.getItemDescription(item);
if (itemDescription) {
matchEnd += itemDescription.length;
}
}
}
// If we have just a description match, the end is the last description match
else if (score.descriptionMatch && score.descriptionMatch.length) {
matchEnd = score.descriptionMatch[score.descriptionMatch.length - 1].end;
}
return matchEnd - matchStart;
}
function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number {
if ((!matchesA && !matchesB) || ((!matchesA || !matchesA.length) && (!matchesB || !matchesB.length))) {
return 0; // make sure to not cause bad comparing when matches are not provided
}
if (!matchesB || !matchesB.length) {
return -1;
}
if (!matchesA || !matchesA.length) {
return 1;
}
// Compute match length of A (first to last match)
const matchStartA = matchesA[0].start;
const matchEndA = matchesA[matchesA.length - 1].end;
const matchLengthA = matchEndA - matchStartA;
// Compute match length of B (first to last match)
const matchStartB = matchesB[0].start;
const matchEndB = matchesB[matchesB.length - 1].end;
const matchLengthB = matchEndB - matchStartB;
// Prefer shorter match length
return matchLengthA === matchLengthB ? 0 : matchLengthB < matchLengthA ? 1 : -1;
}
function fallbackCompare<T>(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor<T>): number {
// check for label + description length and prefer shorter
const labelA = accessor.getItemLabel(itemA) || '';
const labelB = accessor.getItemLabel(itemB) || '';
const descriptionA = accessor.getItemDescription(itemA);
const descriptionB = accessor.getItemDescription(itemB);
const labelDescriptionALength = labelA.length + (descriptionA ? descriptionA.length : 0);
const labelDescriptionBLength = labelB.length + (descriptionB ? descriptionB.length : 0);
if (labelDescriptionALength !== labelDescriptionBLength) {
return labelDescriptionALength - labelDescriptionBLength;
}
// check for path length and prefer shorter
const pathA = accessor.getItemPath(itemA);
const pathB = accessor.getItemPath(itemB);
if (pathA && pathB && pathA.length !== pathB.length) {
return pathA.length - pathB.length;
}
// 7.) finally we have equal scores and equal length, we fallback to comparer
// compare by label
if (labelA !== labelB) {
return compareAnything(labelA, labelB, query.normalized);
}
// compare by description
if (descriptionA && descriptionB && descriptionA !== descriptionB) {
return compareAnything(descriptionA, descriptionB, query.normalized);
}
// compare by path
if (pathA && pathB && pathA !== pathB) {
return compareAnything(pathA, pathB, query.normalized);
}
// equal
return 0;
}
//#endregion
//#region Query Normalizer
export interface IPreparedQueryPiece {
/**
* The original query as provided as input.
*/
original: string;
originalLowercase: string;
/**
* Original normalized to platform separators:
* - Windows: \
* - Posix: /
*/
pathNormalized: string;
/**
* In addition to the normalized path, will have
* whitespace and wildcards removed.
*/
normalized: string;
normalizedLowercase: string;
}
export interface IPreparedQuery extends IPreparedQueryPiece {
/**
* Query split by spaces into pieces.
*/
values: IPreparedQueryPiece[] | undefined;
/**
* Whether the query contains path separator(s) or not.
*/
containsPathSeparator: boolean;
}
/**
* Helper function to prepare a search value for scoring by removing unwanted characters
* and allowing to score on multiple pieces separated by whitespace character.
*/
const MULTIPLE_QUERY_VALUES_SEPARATOR = ' ';
export function prepareQuery(original: string): IPreparedQuery {
if (typeof original !== 'string') {
original = '';
}
const originalLowercase = original.toLowerCase();
const { pathNormalized, normalized, normalizedLowercase } = normalizeQuery(original);
const containsPathSeparator = pathNormalized.indexOf(sep) >= 0;
let values: IPreparedQueryPiece[] | undefined = undefined;
const originalSplit = original.split(MULTIPLE_QUERY_VALUES_SEPARATOR);
if (originalSplit.length > 1) {
for (const originalPiece of originalSplit) {
const {
pathNormalized: pathNormalizedPiece,
normalized: normalizedPiece,
normalizedLowercase: normalizedLowercasePiece
} = normalizeQuery(originalPiece);
if (normalizedPiece) {
if (!values) {
values = [];
}
values.push({
original: originalPiece,
originalLowercase: originalPiece.toLowerCase(),
pathNormalized: pathNormalizedPiece,
normalized: normalizedPiece,
normalizedLowercase: normalizedLowercasePiece
});
}
}
}
return { original, originalLowercase, pathNormalized, normalized, normalizedLowercase, values, containsPathSeparator };
}
function normalizeQuery(original: string): { pathNormalized: string, normalized: string, normalizedLowercase: string } {
let pathNormalized: string;
if (isWindows) {
pathNormalized = original.replace(/\//g, sep); // Help Windows users to search for paths when using slash
} else {
pathNormalized = original.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash
}
const normalized = stripWildcards(pathNormalized).replace(/\s/g, '');
return {
pathNormalized,
normalized,
normalizedLowercase: normalized.toLowerCase()
};
}
export function pieceToQuery(piece: IPreparedQueryPiece): IPreparedQuery;
export function pieceToQuery(pieces: IPreparedQueryPiece[]): IPreparedQuery;
export function pieceToQuery(arg1: IPreparedQueryPiece | IPreparedQueryPiece[]): IPreparedQuery {
if (Array.isArray(arg1)) {
return prepareQuery(arg1.map(piece => piece.original).join(MULTIPLE_QUERY_VALUES_SEPARATOR));
}
return prepareQuery(arg1.original);
}
//#endregion

View File

@@ -0,0 +1,688 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
import * as extpath from 'vs/base/common/extpath';
import * as paths from 'vs/base/common/path';
import { LRUCache } from 'vs/base/common/map';
import { CharCode } from 'vs/base/common/charCode';
import { isThenable } from 'vs/base/common/async';
export interface IExpression {
[pattern: string]: boolean | SiblingClause;
}
export interface IRelativePattern {
base: string;
pattern: string;
}
export function getEmptyExpression(): IExpression {
return Object.create(null);
}
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;
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 [];
}
const segments: string[] = [];
let inBraces = false;
let inBrackets = false;
let curVal = '';
for (const char of pattern) {
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
const segments = splitGlobAware(pattern, GLOB_SPLIT);
// Special case where we only have globstars
if (segments.every(s => s === GLOBSTAR)) {
regEx = '.*';
}
// Build regex over segments
else {
let previousSegmentWasGlobStar = false;
segments.forEach((segment, index) => {
// Globstar is special
if (segment === GLOBSTAR) {
// 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 = '';
for (const char of segment) {
// Support brace expansion
if (char !== '}' && inBraces) {
braceVal += char;
continue;
}
// Support brackets
if (inBrackets && (char !== ']' || !bracketVal) /* ] is literally only allowed as first character in brackets to match it */) {
let res: string;
// range operator
if (char === '-') {
res = char;
}
// negation operator (only valid on first index in bracket)
else if ((char === '^' || char === '!') && !bracketVal) {
res = '^';
}
// glob split matching is not allowed within character ranges
// see http://man7.org/linux/man-pages/man7/glob.7.html
else if (char === GLOB_SPLIT) {
res = '';
}
// anything else gets escaped
else {
res = strings.escapeRegExpCharacters(char);
}
bracketVal += res;
continue;
}
switch (char) {
case '{':
inBraces = true;
continue;
case '[':
inBrackets = true;
continue;
case '}':
const choices = splitGlobAware(braceVal, ',');
// Converts {foo,bar} => [foo|bar]
const 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 remaining pattern is not a globstar
// For example if pattern: some/**/*.js we want the "/" after some to be included in the RegEx to prevent
// a folder called "something" to match as well.
// However, if pattern: some/**, we tolerate that we also match on "something" because our globstar behaviour
// is to match 0-N segments.
if (index < segments.length - 1 && (segments[index + 1] !== GLOBSTAR || index + 2 < segments.length)) {
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 hasSibling returns a Promise.
export type ParsedExpression = (path: string, basename?: string, hasSibling?: (name: string) => boolean | Promise<boolean>) => string | null | Promise<string | null> /* 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 | null | Promise<string | null> /* the matching pattern */;
basenames?: string[];
patterns?: string[];
allBasenames?: string[];
allPaths?: string[];
}
interface ParsedExpressionPattern {
(path: string, basename?: string, name?: string, hasSibling?: (name: string) => boolean | Promise<boolean>): string | null | Promise<string | null> /* the matching pattern */;
requiresSiblings?: boolean;
allBasenames?: string[];
allPaths?: string[];
}
const CACHE = new LRUCache<string, ParsedStringPattern>(10000); // bounded to 10000 elements
const FALSE = function () {
return false;
};
const NULL = function (): string | null {
return null;
};
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();
// Check cache
const patternKey = `${pattern}_${!!options.trimForExclusions}`;
let parsedPattern = CACHE.get(patternKey);
if (parsedPattern) {
return wrapRelativePattern(parsedPattern, arg1);
}
// Check for Trivias
let match: RegExpExecArray | null;
if (T1.test(pattern)) { // common pattern: **/*.txt just need endsWith check
const base = pattern.substr(4); // '**/*'.length === 4
parsedPattern = function (path, basename) {
return typeof path === 'string' && path.endsWith(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 wrapRelativePattern(parsedPattern, arg1);
}
function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | IRelativePattern): ParsedStringPattern {
if (typeof arg2 === 'string') {
return parsedPattern;
}
return function (path, basename) {
if (!extpath.isEqualOrParent(path, arg2.base)) {
return null;
}
return parsedPattern(paths.relative(arg2.base, path), basename);
};
}
function trimForExclusions(pattern: string, options: IGlobOptions): string {
return options.trimForExclusions && pattern.endsWith('/**') ? 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 (typeof path !== 'string') {
return null;
}
if (basename) {
return basename === base ? originalPattern : null;
}
return path === base || path.endsWith(slashBase) || path.endsWith(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 = parsedPatterns.find(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.sep !== paths.posix.sep ? path.replace(ALL_FORWARD_SLASHES, paths.sep) : path;
const nativePathEnd = paths.sep + nativePath;
const parsedPattern: ParsedStringPattern = matchPathEnds ? function (path, basename) {
return typeof path === 'string' && (path === nativePath || path.endsWith(nativePathEnd)) ? pattern : null;
} : function (path, basename) {
return typeof path === 'string' && 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) {
regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it!
return typeof path === 'string' && 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 | IRelativePattern, path: string): boolean;
export function match(expression: IExpression, path: string, hasSibling?: (name: string) => boolean): string /* the matching pattern */;
export function match(arg1: string | IExpression | IRelativePattern, path: string, hasSibling?: (name: string) => boolean): boolean | string | null | Promise<string | null> {
if (!arg1 || typeof path !== 'string') {
return false;
}
return parse(<IExpression>arg1)(path, undefined, hasSibling);
}
/**
* 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 | IRelativePattern, options?: IGlobOptions): ParsedPattern;
export function parse(expression: IExpression, options?: IGlobOptions): ParsedExpression;
export function parse(arg1: string | IExpression | IRelativePattern, options: IGlobOptions = {}): ParsedPattern | ParsedExpression {
if (!arg1) {
return FALSE;
}
// Glob with String
if (typeof arg1 === 'string' || isRelativePattern(arg1)) {
const parsedPattern = parsePattern(arg1, options);
if (parsedPattern === NULL) {
return FALSE;
}
const resultPattern: ParsedPattern & { allBasenames?: string[]; allPaths?: string[]; } = function (path: string, basename?: string) {
return !!parsedPattern(path, basename);
};
if (parsedPattern.allBasenames) {
resultPattern.allBasenames = parsedPattern.allBasenames;
}
if (parsedPattern.allPaths) {
resultPattern.allPaths = parsedPattern.allPaths;
}
return resultPattern;
}
// Glob with Expression
return parsedExpression(<IExpression>arg1, options);
}
export function hasSiblingPromiseFn(siblingsFn?: () => Promise<string[]>) {
if (!siblingsFn) {
return undefined;
}
let siblings: Promise<Record<string, true>>;
return (name: string) => {
if (!siblings) {
siblings = (siblingsFn() || Promise.resolve([]))
.then(list => list ? listToMap(list) : {});
}
return siblings.then(map => !!map[name]);
};
}
export function hasSiblingFn(siblingsFn?: () => string[]) {
if (!siblingsFn) {
return undefined;
}
let siblings: Record<string, true>;
return (name: string) => {
if (!siblings) {
const list = siblingsFn();
siblings = list ? listToMap(list) : {};
}
return !!siblings[name];
};
}
function listToMap(list: string[]) {
const map: Record<string, true> = {};
for (const key of list) {
map[key] = true;
}
return map;
}
export function isRelativePattern(obj: unknown): obj is IRelativePattern {
const rp = obj as IRelativePattern;
return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string';
}
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) {
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 = parsedPatterns.find(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, hasSibling?: (name: string) => boolean | Promise<boolean>) {
let name: string | undefined = undefined;
for (let i = 0, n = parsedPatterns.length; i < n; i++) {
// Pattern matches path
const parsedPattern = (<ParsedExpressionPattern>parsedPatterns[i]);
if (parsedPattern.requiresSiblings && hasSibling) {
if (!basename) {
basename = paths.basename(path);
}
if (!name) {
name = basename.substr(0, basename.length - paths.extname(path).length);
}
}
const result = parsedPattern(path, basename, name, hasSibling);
if (result) {
return result;
}
}
return null;
};
const withBasenames = parsedPatterns.find(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: boolean | SiblingClause, 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 result: ParsedExpressionPattern = (path: string, basename?: string, name?: string, hasSibling?: (name: string) => boolean | Promise<boolean>) => {
if (!hasSibling || !parsedPattern(path, basename)) {
return null;
}
const clausePattern = when.replace('$(basename)', name!);
const matched = hasSibling(clausePattern);
return isThenable(matched) ?
matched.then(m => m ? pattern : null) :
matched ? pattern : null;
};
result.requiresSiblings = true;
return result;
}
}
// Expression is Anything
return parsedPattern;
}
function aggregateBasenameMatches(parsedPatterns: Array<ParsedStringPattern | ParsedExpressionPattern>, result?: string): Array<ParsedStringPattern | ParsedExpressionPattern> {
const basenamePatterns = parsedPatterns.filter(parsedPattern => !!(<ParsedStringPattern>parsedPattern).basenames);
if (basenamePatterns.length < 2) {
return parsedPatterns;
}
const basenames = basenamePatterns.reduce<string[]>((all, current) => {
const basenames = (<ParsedStringPattern>current).basenames;
return basenames ? all.concat(basenames) : all;
}, <string[]>[]);
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) => {
const patterns = (<ParsedStringPattern>current).patterns;
return patterns ? all.concat(patterns) : all;
}, <string[]>[]);
}
const aggregate: ParsedStringPattern = function (path, basename) {
if (typeof path !== 'string') {
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;
}

View File

@@ -0,0 +1,310 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
/**
* Return a hash value for an object.
*/
export function hash(obj: any): number {
return doHash(obj, 0);
}
export function doHash(obj: any, hashVal: number): 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(937, hashVal);
default:
return numberHash(617, hashVal);
}
}
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);
}
export 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) => doHash(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 doHash(obj[key], hashVal);
}, initialHashVal);
}
export class Hasher {
private _value = 0;
get value(): number {
return this._value;
}
hash(obj: any): number {
this._value = doHash(obj, this._value);
return this._value;
}
}
const enum SHA1Constant {
BLOCK_SIZE = 64, // 512 / 8
UNICODE_REPLACEMENT = 0xFFFD,
}
function leftRotate(value: number, bits: number, totalBits: number = 32): number {
// delta + bits = totalBits
const delta = totalBits - bits;
// All ones, expect `delta` zeros aligned to the right
const mask = ~((1 << delta) - 1);
// Join (value left-shifted `bits` bits) with (masked value right-shifted `delta` bits)
return ((value << bits) | ((mask & value) >>> delta)) >>> 0;
}
function fill(dest: Uint8Array, index: number = 0, count: number = dest.byteLength, value: number = 0): void {
for (let i = 0; i < count; i++) {
dest[index + i] = value;
}
}
function leftPad(value: string, length: number, char: string = '0'): string {
while (value.length < length) {
value = char + value;
}
return value;
}
function toHexString(value: number, bitsize: number = 32): string {
return leftPad((value >>> 0).toString(16), bitsize / 4);
}
/**
* A SHA1 implementation that works with strings and does not allocate.
*/
export class StringSHA1 {
private static _bigBlock32 = new DataView(new ArrayBuffer(320)); // 80 * 4 = 320
private _h0 = 0x67452301;
private _h1 = 0xEFCDAB89;
private _h2 = 0x98BADCFE;
private _h3 = 0x10325476;
private _h4 = 0xC3D2E1F0;
private readonly _buff: Uint8Array;
private readonly _buffDV: DataView;
private _buffLen: number;
private _totalLen: number;
private _leftoverHighSurrogate: number;
private _finished: boolean;
constructor() {
this._buff = new Uint8Array(SHA1Constant.BLOCK_SIZE + 3 /* to fit any utf-8 */);
this._buffDV = new DataView(this._buff.buffer);
this._buffLen = 0;
this._totalLen = 0;
this._leftoverHighSurrogate = 0;
this._finished = false;
}
public update(str: string): void {
const strLen = str.length;
if (strLen === 0) {
return;
}
const buff = this._buff;
let buffLen = this._buffLen;
let leftoverHighSurrogate = this._leftoverHighSurrogate;
let charCode: number;
let offset: number;
if (leftoverHighSurrogate !== 0) {
charCode = leftoverHighSurrogate;
offset = -1;
leftoverHighSurrogate = 0;
} else {
charCode = str.charCodeAt(0);
offset = 0;
}
while (true) {
let codePoint = charCode;
if (strings.isHighSurrogate(charCode)) {
if (offset + 1 < strLen) {
const nextCharCode = str.charCodeAt(offset + 1);
if (strings.isLowSurrogate(nextCharCode)) {
offset++;
codePoint = strings.computeCodePoint(charCode, nextCharCode);
} else {
// illegal => unicode replacement character
codePoint = SHA1Constant.UNICODE_REPLACEMENT;
}
} else {
// last character is a surrogate pair
leftoverHighSurrogate = charCode;
break;
}
} else if (strings.isLowSurrogate(charCode)) {
// illegal => unicode replacement character
codePoint = SHA1Constant.UNICODE_REPLACEMENT;
}
buffLen = this._push(buff, buffLen, codePoint);
offset++;
if (offset < strLen) {
charCode = str.charCodeAt(offset);
} else {
break;
}
}
this._buffLen = buffLen;
this._leftoverHighSurrogate = leftoverHighSurrogate;
}
private _push(buff: Uint8Array, buffLen: number, codePoint: number): number {
if (codePoint < 0x0080) {
buff[buffLen++] = codePoint;
} else if (codePoint < 0x0800) {
buff[buffLen++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
} else if (codePoint < 0x10000) {
buff[buffLen++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
} else {
buff[buffLen++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
}
if (buffLen >= SHA1Constant.BLOCK_SIZE) {
this._step();
buffLen -= SHA1Constant.BLOCK_SIZE;
this._totalLen += SHA1Constant.BLOCK_SIZE;
// take last 3 in case of UTF8 overflow
buff[0] = buff[SHA1Constant.BLOCK_SIZE + 0];
buff[1] = buff[SHA1Constant.BLOCK_SIZE + 1];
buff[2] = buff[SHA1Constant.BLOCK_SIZE + 2];
}
return buffLen;
}
public digest(): string {
if (!this._finished) {
this._finished = true;
if (this._leftoverHighSurrogate) {
// illegal => unicode replacement character
this._leftoverHighSurrogate = 0;
this._buffLen = this._push(this._buff, this._buffLen, SHA1Constant.UNICODE_REPLACEMENT);
}
this._totalLen += this._buffLen;
this._wrapUp();
}
return toHexString(this._h0) + toHexString(this._h1) + toHexString(this._h2) + toHexString(this._h3) + toHexString(this._h4);
}
private _wrapUp(): void {
this._buff[this._buffLen++] = 0x80;
fill(this._buff, this._buffLen);
if (this._buffLen > 56) {
this._step();
fill(this._buff);
}
// this will fit because the mantissa can cover up to 52 bits
const ml = 8 * this._totalLen;
this._buffDV.setUint32(56, Math.floor(ml / 4294967296), false);
this._buffDV.setUint32(60, ml % 4294967296, false);
this._step();
}
private _step(): void {
const bigBlock32 = StringSHA1._bigBlock32;
const data = this._buffDV;
for (let j = 0; j < 64 /* 16*4 */; j += 4) {
bigBlock32.setUint32(j, data.getUint32(j, false), false);
}
for (let j = 64; j < 320 /* 80*4 */; j += 4) {
bigBlock32.setUint32(j, leftRotate((bigBlock32.getUint32(j - 12, false) ^ bigBlock32.getUint32(j - 32, false) ^ bigBlock32.getUint32(j - 56, false) ^ bigBlock32.getUint32(j - 64, false)), 1), false);
}
let a = this._h0;
let b = this._h1;
let c = this._h2;
let d = this._h3;
let e = this._h4;
let f: number, k: number;
let temp: number;
for (let j = 0; j < 80; j++) {
if (j < 20) {
f = (b & c) | ((~b) & d);
k = 0x5A827999;
} else if (j < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (j < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
temp = (leftRotate(a, 5) + f + e + k + bigBlock32.getUint32(j * 4, false)) & 0xffffffff;
e = d;
d = c;
c = leftRotate(b, 30);
b = a;
a = temp;
}
this._h0 = (this._h0 + a) & 0xffffffff;
this._h1 = (this._h1 + b) & 0xffffffff;
this._h2 = (this._h2 + c) & 0xffffffff;
this._h3 = (this._h3 + d) & 0xffffffff;
this._h4 = (this._h4 + e) & 0xffffffff;
}
}

View File

@@ -0,0 +1,202 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { INavigator, ArrayNavigator } from 'vs/base/common/navigator';
export class HistoryNavigator<T> implements INavigator<T> {
private _history!: Set<T>;
private _limit: number;
private _navigator!: ArrayNavigator<T>;
constructor(history: readonly 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 next(): T | null {
if (this._currentPosition() !== this._elements.length - 1) {
return this._navigator.next();
}
return null;
}
public previous(): T | null {
if (this._currentPosition() !== 0) {
return this._navigator.previous();
}
return null;
}
public current(): T | null {
return this._navigator.current();
}
public first(): T | null {
return this._navigator.first();
}
public last(): T | null {
return this._navigator.last();
}
public has(t: T): boolean {
return this._history.has(t);
}
public clear(): void {
this._initialize([]);
this._onChange();
}
private _onChange() {
this._reduceToLimit();
const elements = this._elements;
this._navigator = new ArrayNavigator(elements, 0, elements.length, elements.length);
}
private _reduceToLimit() {
const data = this._elements;
if (data.length > this._limit) {
this._initialize(data.slice(data.length - this._limit));
}
}
private _currentPosition(): number {
const currentElement = this._navigator.current();
if (!currentElement) {
return -1;
}
return this._elements.indexOf(currentElement);
}
private _initialize(history: readonly 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;
}
}
interface HistoryNode<T> {
value: T;
previous: HistoryNode<T> | undefined;
next: HistoryNode<T> | undefined;
}
export class HistoryNavigator2<T> {
private head: HistoryNode<T>;
private tail: HistoryNode<T>;
private cursor: HistoryNode<T>;
private size: number;
constructor(history: readonly T[], private capacity: number = 10) {
if (history.length < 1) {
throw new Error('not supported');
}
this.size = 1;
this.head = this.tail = this.cursor = {
value: history[0],
previous: undefined,
next: undefined
};
for (let i = 1; i < history.length; i++) {
this.add(history[i]);
}
}
add(value: T): void {
const node: HistoryNode<T> = {
value,
previous: this.tail,
next: undefined
};
this.tail.next = node;
this.tail = node;
this.cursor = this.tail;
this.size++;
while (this.size > this.capacity) {
this.head = this.head.next!;
this.head.previous = undefined;
this.size--;
}
}
replaceLast(value: T): void {
this.tail.value = value;
}
isAtEnd(): boolean {
return this.cursor === this.tail;
}
current(): T {
return this.cursor.value;
}
previous(): T {
if (this.cursor.previous) {
this.cursor = this.cursor.previous;
}
return this.cursor.value;
}
next(): T {
if (this.cursor.next) {
this.cursor = this.cursor.next;
}
return this.cursor.value;
}
has(t: T): boolean {
let temp: HistoryNode<T> | undefined = this.head;
while (temp) {
if (temp.value === t) {
return true;
}
temp = temp.next;
}
return false;
}
resetCursor(): T {
this.cursor = this.tail;
return this.cursor.value;
}
*[Symbol.iterator](): Iterator<T> {
let node: HistoryNode<T> | undefined = this.head;
while (node) {
yield node.value;
node = node.next;
}
}
}

View File

@@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { equals } from 'vs/base/common/arrays';
import { UriComponents } from 'vs/base/common/uri';
import { escapeCodicons } from 'vs/base/common/codicons';
import { illegalArgument } from 'vs/base/common/errors';
export interface IMarkdownString {
readonly value: string;
readonly isTrusted?: boolean;
readonly supportThemeIcons?: boolean;
uris?: { [href: string]: UriComponents };
}
export const enum MarkdownStringTextNewlineStyle {
Paragraph = 0,
Break = 1,
}
export class MarkdownString implements IMarkdownString {
private readonly _isTrusted: boolean;
private readonly _supportThemeIcons: boolean;
constructor(
private _value: string = '',
isTrustedOrOptions: boolean | { isTrusted?: boolean, supportThemeIcons?: boolean } = false,
) {
if (typeof this._value !== 'string') {
throw illegalArgument('value');
}
if (typeof isTrustedOrOptions === 'boolean') {
this._isTrusted = isTrustedOrOptions;
this._supportThemeIcons = false;
}
else {
this._isTrusted = isTrustedOrOptions.isTrusted ?? false;
this._supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false;
}
}
get value() { return this._value; }
get isTrusted() { return this._isTrusted; }
get supportThemeIcons() { return this._supportThemeIcons; }
appendText(value: string, newlineStyle: MarkdownStringTextNewlineStyle = MarkdownStringTextNewlineStyle.Paragraph): MarkdownString {
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
this._value += (this._supportThemeIcons ? escapeCodicons(value) : value)
.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
.replace(/\n/g, newlineStyle === MarkdownStringTextNewlineStyle.Break ? '\\\n' : '\n\n');
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[] | null | undefined): 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 (thing && typeof thing === 'object') {
return typeof (<IMarkdownString>thing).value === 'string'
&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === undefined)
&& (typeof (<IMarkdownString>thing).supportThemeIcons === 'boolean' || (<IMarkdownString>thing).supportThemeIcons === undefined);
}
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 && a.supportThemeIcons === b.supportThemeIcons;
}
}
export function removeMarkdownEscapes(text: string): string {
if (!text) {
return text;
}
return text.replace(/\\([\\`*_{}[\]()#+\-.!])/g, '$1');
}
export function parseHrefAndDimensions(href: string): { href: string, dimensions: string[] } {
const dimensions: string[] = [];
const splitted = href.split('|').map(s => s.trim());
href = splitted[0];
const parameters = splitted[1];
if (parameters) {
const heightFromParams = /height=(\d+)/.exec(parameters);
const widthFromParams = /width=(\d+)/.exec(parameters);
const height = heightFromParams ? heightFromParams[1] : '';
const width = widthFromParams ? widthFromParams[1] : '';
const widthIsFinite = isFinite(parseInt(width));
const heightIsFinite = isFinite(parseInt(height));
if (widthIsFinite) {
dimensions.push(`width="${width}"`);
}
if (heightIsFinite) {
dimensions.push(`height="${height}"`);
}
}
return { href, dimensions };
}

View File

@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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,17 @@
{
"registrations": [
{
"component": {
"type": "git",
"git": {
"name": "insane",
"repositoryUrl": "https://github.com/bevacqua/insane",
"commitHash": "7f5a809f44a37e7d11ee5343b2d8bca4be6ba4ae"
}
},
"license": "MIT",
"version": "2.6.2"
}
],
"version": 1
}

View File

@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface InsaneOptions {
readonly allowedSchemes?: readonly string[],
readonly allowedTags?: readonly string[],
readonly allowedAttributes?: { readonly [key: string]: string[] },
readonly filter?: (token: { tag: string, attrs: { readonly [key: string]: string } }) => boolean,
}
export function insane(
html: string,
options?: InsaneOptions,
strict?: boolean,
): string;

View File

@@ -0,0 +1,474 @@
/*
The MIT License (MIT)
Copyright © 2015 Nicolas Bevacqua
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.
*/
let __insane_func;
(function () { function r(e, n, t) { function o(i, f) { if (!n[i]) { if (!e[i]) { var c = "function" == typeof require && require; if (!f && c) return c(i, !0); if (u) return u(i, !0); var a = new Error("Cannot find module '" + i + "'"); throw a.code = "MODULE_NOT_FOUND", a } var p = n[i] = { exports: {} }; e[i][0].call(p.exports, function (r) { var n = e[i][1][r]; return o(n || r) }, p, p.exports, r, e, n, t) } return n[i].exports } for (var u = "function" == typeof require && require, i = 0; i < t.length; i++)o(t[i]); return o } return r })()({
1: [function (require, module, exports) {
'use strict';
var toMap = require('./toMap');
var uris = ['background', 'base', 'cite', 'href', 'longdesc', 'src', 'usemap'];
module.exports = {
uris: toMap(uris) // attributes that have an href and hence need to be sanitized
};
}, { "./toMap": 10 }], 2: [function (require, module, exports) {
'use strict';
var defaults = {
allowedAttributes: {
'*': ['title', 'accesskey'],
a: ['href', 'name', 'target', 'aria-label'],
iframe: ['allowfullscreen', 'frameborder', 'src'],
img: ['src', 'alt', 'title', 'aria-label']
},
allowedClasses: {},
allowedSchemes: ['http', 'https', 'mailto'],
allowedTags: [
'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code', 'del', 'details', 'div', 'em',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark',
'ol', 'p', 'pre', 'section', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table',
'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'
],
filter: null
};
module.exports = defaults;
}, {}], 3: [function (require, module, exports) {
'use strict';
var toMap = require('./toMap');
var voids = ['area', 'br', 'col', 'hr', 'img', 'wbr', 'input', 'base', 'basefont', 'link', 'meta'];
module.exports = {
voids: toMap(voids)
};
}, { "./toMap": 10 }], 4: [function (require, module, exports) {
'use strict';
var he = require('he');
var assign = require('assignment');
var parser = require('./parser');
var sanitizer = require('./sanitizer');
var defaults = require('./defaults');
function insane(html, options, strict) {
var buffer = [];
var configuration = strict === true ? options : assign({}, defaults, options);
var handler = sanitizer(buffer, configuration);
parser(html, handler);
return buffer.join('');
}
insane.defaults = defaults;
module.exports = insane;
__insane_func = insane;
}, { "./defaults": 2, "./parser": 7, "./sanitizer": 8, "assignment": 6, "he": 9 }], 5: [function (require, module, exports) {
'use strict';
module.exports = function lowercase(string) {
return typeof string === 'string' ? string.toLowerCase() : string;
};
}, {}], 6: [function (require, module, exports) {
'use strict';
function assignment(result) {
var stack = Array.prototype.slice.call(arguments, 1);
var item;
var key;
while (stack.length) {
item = stack.shift();
for (key in item) {
if (item.hasOwnProperty(key)) {
if (Object.prototype.toString.call(result[key]) === '[object Object]') {
result[key] = assignment(result[key], item[key]);
} else {
result[key] = item[key];
}
}
}
}
return result;
}
module.exports = assignment;
}, {}], 7: [function (require, module, exports) {
'use strict';
var he = require('he');
var lowercase = require('./lowercase');
var attributes = require('./attributes');
var elements = require('./elements');
var rstart = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/;
var rend = /^<\s*\/\s*([\w:-]+)[^>]*>/;
var rattrs = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g;
var rtag = /^</;
var rtagend = /^<\s*\//;
function createStack() {
var stack = [];
stack.lastItem = function lastItem() {
return stack[stack.length - 1];
};
return stack;
}
function parser(html, handler) {
var stack = createStack();
var last = html;
var chars;
while (html) {
parsePart();
}
parseEndTag(); // clean up any remaining tags
function parsePart() {
chars = true;
parseTag();
var same = html === last;
last = html;
if (same) { // discard, because it's invalid
html = '';
}
}
function parseTag() {
if (html.substr(0, 4) === '<!--') { // comments
parseComment();
} else if (rtagend.test(html)) {
parseEdge(rend, parseEndTag);
} else if (rtag.test(html)) {
parseEdge(rstart, parseStartTag);
}
parseTagDecode();
}
function parseEdge(regex, parser) {
var match = html.match(regex);
if (match) {
html = html.substring(match[0].length);
match[0].replace(regex, parser);
chars = false;
}
}
function parseComment() {
var index = html.indexOf('-->');
if (index >= 0) {
if (handler.comment) {
handler.comment(html.substring(4, index));
}
html = html.substring(index + 3);
chars = false;
}
}
function parseTagDecode() {
if (!chars) {
return;
}
var text;
var index = html.indexOf('<');
if (index >= 0) {
text = html.substring(0, index);
html = html.substring(index);
} else {
text = html;
html = '';
}
if (handler.chars) {
handler.chars(text);
}
}
function parseStartTag(tag, tagName, rest, unary) {
var attrs = {};
var low = lowercase(tagName);
var u = elements.voids[low] || !!unary;
rest.replace(rattrs, attrReplacer);
if (!u) {
stack.push(low);
}
if (handler.start) {
handler.start(low, attrs, u);
}
function attrReplacer(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
if (doubleQuotedValue === void 0 && singleQuotedValue === void 0 && unquotedValue === void 0) {
attrs[name] = void 0; // attribute is like <button disabled></button>
} else {
attrs[name] = he.decode(doubleQuotedValue || singleQuotedValue || unquotedValue || '');
}
}
}
function parseEndTag(tag, tagName) {
var i;
var pos = 0;
var low = lowercase(tagName);
if (low) {
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos] === low) {
break; // find the closest opened tag of the same type
}
}
}
if (pos >= 0) {
for (i = stack.length - 1; i >= pos; i--) {
if (handler.end) { // close all the open elements, up the stack
handler.end(stack[i]);
}
}
stack.length = pos;
}
}
}
module.exports = parser;
}, { "./attributes": 1, "./elements": 3, "./lowercase": 5, "he": 9 }], 8: [function (require, module, exports) {
'use strict';
var he = require('he');
var lowercase = require('./lowercase');
var attributes = require('./attributes');
var elements = require('./elements');
function sanitizer(buffer, options) {
var last;
var context;
var o = options || {};
reset();
return {
start: start,
end: end,
chars: chars
};
function out(value) {
buffer.push(value);
}
function start(tag, attrs, unary) {
var low = lowercase(tag);
if (context.ignoring) {
ignore(low); return;
}
if ((o.allowedTags || []).indexOf(low) === -1) {
ignore(low); return;
}
if (o.filter && !o.filter({ tag: low, attrs: attrs })) {
ignore(low); return;
}
out('<');
out(low);
Object.keys(attrs).forEach(parse);
out(unary ? '/>' : '>');
function parse(key) {
var value = attrs[key];
var classesOk = (o.allowedClasses || {})[low] || [];
var attrsOk = (o.allowedAttributes || {})[low] || [];
attrsOk = attrsOk.concat((o.allowedAttributes || {})['*'] || []);
var valid;
var lkey = lowercase(key);
if (lkey === 'class' && attrsOk.indexOf(lkey) === -1) {
value = value.split(' ').filter(isValidClass).join(' ').trim();
valid = value.length;
} else {
valid = attrsOk.indexOf(lkey) !== -1 && (attributes.uris[lkey] !== true || testUrl(value));
}
if (valid) {
out(' ');
out(key);
if (typeof value === 'string') {
out('="');
out(he.encode(value));
out('"');
}
}
function isValidClass(className) {
return classesOk && classesOk.indexOf(className) !== -1;
}
}
}
function end(tag) {
var low = lowercase(tag);
var allowed = (o.allowedTags || []).indexOf(low) !== -1;
if (allowed) {
if (context.ignoring === false) {
out('</');
out(low);
out('>');
} else {
unignore(low);
}
} else {
unignore(low);
}
}
function testUrl(text) {
var start = text[0];
if (start === '#' || start === '/') {
return true;
}
var colon = text.indexOf(':');
if (colon === -1) {
return true;
}
var questionmark = text.indexOf('?');
if (questionmark !== -1 && colon > questionmark) {
return true;
}
var hash = text.indexOf('#');
if (hash !== -1 && colon > hash) {
return true;
}
return o.allowedSchemes.some(matches);
function matches(scheme) {
return text.indexOf(scheme + ':') === 0;
}
}
function chars(text) {
if (context.ignoring === false) {
out(o.transformText ? o.transformText(text) : text);
}
}
function ignore(tag) {
if (elements.voids[tag]) {
return;
}
if (context.ignoring === false) {
context = { ignoring: tag, depth: 1 };
} else if (context.ignoring === tag) {
context.depth++;
}
}
function unignore(tag) {
if (context.ignoring === tag) {
if (--context.depth <= 0) {
reset();
}
}
}
function reset() {
context = { ignoring: false, depth: 0 };
}
}
module.exports = sanitizer;
}, { "./attributes": 1, "./elements": 3, "./lowercase": 5, "he": 9 }], 9: [function (require, module, exports) {
'use strict';
var escapes = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
};
var unescapes = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#39;': "'"
};
var rescaped = /(&amp;|&lt;|&gt;|&quot;|&#39;)/g;
var runescaped = /[&<>"']/g;
function escapeHtmlChar(match) {
return escapes[match];
}
function unescapeHtmlChar(match) {
return unescapes[match];
}
function escapeHtml(text) {
return text == null ? '' : String(text).replace(runescaped, escapeHtmlChar);
}
function unescapeHtml(html) {
return html == null ? '' : String(html).replace(rescaped, unescapeHtmlChar);
}
escapeHtml.options = unescapeHtml.options = {};
module.exports = {
encode: escapeHtml,
escape: escapeHtml,
decode: unescapeHtml,
unescape: unescapeHtml,
version: '1.0.0-browser'
};
}, {}], 10: [function (require, module, exports) {
'use strict';
function toMap(list) {
return list.reduce(asKey, {});
}
function asKey(accumulator, item) {
accumulator[item] = true;
return accumulator;
}
module.exports = toMap;
}, {}]
}, {}, [4]);
// ESM-comment-begin
define(function() { return { insane: __insane_func }; });
// ESM-comment-end
// ESM-uncomment-begin
// export var insane = __insane_func;
// ESM-uncomment-end

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright © 2015 Nicolas Bevacqua
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,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export namespace Iterable {
export function is<T = any>(thing: any): thing is IterableIterator<T> {
return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function';
}
const _empty: Iterable<any> = Object.freeze([]);
export function empty<T = any>(): Iterable<T> {
return _empty;
}
export function* single<T>(element: T): Iterable<T> {
yield element;
}
export function from<T>(iterable: Iterable<T> | undefined | null): Iterable<T> {
return iterable || _empty;
}
export function first<T>(iterable: Iterable<T>): T | undefined {
return iterable[Symbol.iterator]().next().value;
}
export function some<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): boolean {
for (const element of iterable) {
if (predicate(element)) {
return true;
}
}
return false;
}
export function* filter<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): Iterable<T> {
for (const element of iterable) {
if (predicate(element)) {
yield element;
}
}
}
export function* map<T, R>(iterable: Iterable<T>, fn: (t: T) => R): Iterable<R> {
for (const element of iterable) {
yield fn(element);
}
}
export function* concat<T>(...iterables: Iterable<T>[]): Iterable<T> {
for (const iterable of iterables) {
for (const element of iterable) {
yield element;
}
}
}
/**
* Consumes `atMost` elements from iterable and returns the consumed elements,
* and an iterable for the rest of the elements.
*/
export function consume<T>(iterable: Iterable<T>, atMost: number = Number.POSITIVE_INFINITY): [T[], Iterable<T>] {
const consumed: T[] = [];
if (atMost === 0) {
return [consumed, iterable];
}
const iterator = iterable[Symbol.iterator]();
for (let i = 0; i < atMost; i++) {
const next = iterator.next();
if (next.done) {
return [consumed, Iterable.empty()];
}
consumed.push(next.value);
}
return [consumed, { [Symbol.iterator]() { return iterator; } }];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,177 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ParseError, Node, JSONPath, Segment, parseTree, findNodeAtLocation } from './json';
import { Edit, format, isEOL, FormattingOptions } from './jsonFormatter';
import { mergeSort } from 'vs/base/common/arrays';
export function removeProperty(text: string, path: JSONPath, formattingOptions: FormattingOptions): Edit[] {
return setProperty(text, path, undefined, formattingOptions);
}
export function setProperty(text: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number): Edit[] {
const path = originalPath.slice();
const errors: ParseError[] = [];
const root = parseTree(text, errors);
let parent: Node | undefined = undefined;
let lastSegment: Segment | undefined = undefined;
while (path.length > 0) {
lastSegment = path.pop();
parent = findNodeAtLocation(root, path);
if (parent === undefined && value !== undefined) {
if (typeof lastSegment === 'string') {
value = { [lastSegment]: value };
} else {
value = [value];
}
} else {
break;
}
}
if (!parent) {
// empty document
if (value === undefined) { // 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' && Array.isArray(parent.children)) {
const existing = findNodeAtLocation(parent, [lastSegment]);
if (existing !== undefined) {
if (value === undefined) { // delete
if (!existing.parent) {
throw new Error('Malformed AST');
}
const 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
const 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
const 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 === undefined) { // delete
return []; // property does not exist, nothing to do
}
const newProperty = `${JSON.stringify(lastSegment)}: ${JSON.stringify(value)}`;
const index = getInsertionIndex ? getInsertionIndex(parent.children.map(p => p.children![0].value)) : parent.children.length;
let edit: Edit;
if (index > 0) {
const 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' && Array.isArray(parent.children)) {
if (value !== undefined) {
// Insert
const newProperty = `${JSON.stringify(value)}`;
let edit: Edit;
if (parent.children.length === 0 || lastSegment === 0) {
edit = { offset: parent.offset + 1, length: 0, content: parent.children.length === 0 ? newProperty : newProperty + ',' };
} else {
const index = lastSegment === -1 || lastSegment > parent.children.length ? parent.children.length : lastSegment;
const previous = parent.children[index - 1];
edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
}
return withFormatting(text, edit, formattingOptions);
} else {
//Removal
const removalIndex = lastSegment;
const 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
const previous = parent.children[removalIndex - 1];
const offset = previous.offset + previous.length;
const 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(`Can not add ${typeof lastSegment !== 'number' ? 'index' : 'property'} to parent of type ${parent.type}`);
}
}
export 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;
if (edit.length === 0 || edit.content.length === 0) { // insert or remove
while (begin > 0 && !isEOL(newText, begin - 1)) {
begin--;
}
while (end < newText.length && !isEOL(newText, end)) {
end++;
}
}
const 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--) {
const curr = edits[i];
newText = applyEdit(newText, curr);
begin = Math.min(begin, curr.offset);
end = Math.max(end, curr.offset + curr.length);
end += curr.content.length - curr.length;
}
// create a single edit with all changes
const editLength = text.length - (newText.length - end) - begin;
return [{ offset: begin, length: editLength, content: newText.substring(begin, end) }];
}
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 {
let sortedEdits = mergeSort(edits, (a, b) => {
const diff = a.offset - b.offset;
if (diff === 0) {
return a.length - b.length;
}
return diff;
});
let lastModifiedOffset = text.length;
for (let i = sortedEdits.length - 1; i >= 0; i--) {
let e = sortedEdits[i];
if (e.offset + e.length <= lastModifiedOffset) {
text = applyEdit(text, e);
} else {
throw new Error('Overlapping edit');
}
lastModifiedOffset = e.offset;
}
return text;
}

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* 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,248 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createScanner, SyntaxKind, ScanError } from './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' character. If not set, '\n' is used as default.
*/
eol?: string;
}
/**
* Represents a text modification
*/
export interface Edit {
/**
* The start offset of the modification.
*/
offset: number;
/**
* The length of the modification. Must not be negative. Empty length represents an *insert*.
*/
length: number;
/**
* The new content. Empty content represents a *remove*.
*/
content: string;
}
/**
* A text range in the document
*/
export interface Range {
/**
* The start offset of the range.
*/
offset: number;
/**
* The length of the range. Must not be negative.
*/
length: number;
}
export function format(documentText: string, range: Range | undefined, options: FormattingOptions): Edit[] {
let initialIndentLevel: number;
let formatText: string;
let formatTextStart: number;
let rangeStart: number;
let rangeEnd: number;
if (range) {
rangeStart = range.offset;
rangeEnd = rangeStart + range.length;
formatTextStart = rangeStart;
while (formatTextStart > 0 && !isEOL(documentText, formatTextStart - 1)) {
formatTextStart--;
}
let endOffset = rangeEnd;
while (endOffset < documentText.length && !isEOL(documentText, endOffset)) {
endOffset++;
}
formatText = documentText.substring(formatTextStart, endOffset);
initialIndentLevel = computeIndentLevel(formatText, options);
} else {
formatText = documentText;
initialIndentLevel = 0;
formatTextStart = 0;
rangeStart = 0;
rangeEnd = documentText.length;
}
const eol = getEOL(options, documentText);
let lineBreak = false;
let indentLevel = 0;
let indentValue: string;
if (options.insertSpaces) {
indentValue = repeat(' ', options.tabSize || 4);
} else {
indentValue = '\t';
}
const scanner = createScanner(formatText, false);
let hasError = false;
function newLineAndIndent(): string {
return eol + repeat(indentValue, initialIndentLevel + indentLevel);
}
function scanNext(): SyntaxKind {
let token = scanner.scan();
lineBreak = false;
while (token === SyntaxKind.Trivia || token === SyntaxKind.LineBreakTrivia) {
lineBreak = lineBreak || (token === SyntaxKind.LineBreakTrivia);
token = scanner.scan();
}
hasError = token === SyntaxKind.Unknown || scanner.getTokenError() !== ScanError.None;
return token;
}
const editOperations: Edit[] = [];
function addEdit(text: string, startOffset: number, endOffset: number) {
if (!hasError && startOffset < rangeEnd && endOffset > rangeStart && documentText.substring(startOffset, endOffset) !== text) {
editOperations.push({ offset: startOffset, length: endOffset - startOffset, content: text });
}
}
let firstToken = scanNext();
if (firstToken !== SyntaxKind.EOF) {
const firstTokenStart = scanner.getTokenOffset() + formatTextStart;
const initialIndent = repeat(indentValue, initialIndentLevel);
addEdit(initialIndent, formatTextStart, firstTokenStart);
}
while (firstToken !== SyntaxKind.EOF) {
let firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
let secondToken = scanNext();
let replaceContent = '';
while (!lineBreak && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
// comments on the same line: keep them on the same line, but ignore them otherwise
const commentTokenStart = scanner.getTokenOffset() + formatTextStart;
addEdit(' ', firstTokenEnd, commentTokenStart);
firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
replaceContent = secondToken === SyntaxKind.LineCommentTrivia ? newLineAndIndent() : '';
secondToken = scanNext();
}
if (secondToken === SyntaxKind.CloseBraceToken) {
if (firstToken !== SyntaxKind.OpenBraceToken) {
indentLevel--;
replaceContent = newLineAndIndent();
}
} else if (secondToken === SyntaxKind.CloseBracketToken) {
if (firstToken !== SyntaxKind.OpenBracketToken) {
indentLevel--;
replaceContent = newLineAndIndent();
}
} else {
switch (firstToken) {
case SyntaxKind.OpenBracketToken:
case SyntaxKind.OpenBraceToken:
indentLevel++;
replaceContent = newLineAndIndent();
break;
case SyntaxKind.CommaToken:
case SyntaxKind.LineCommentTrivia:
replaceContent = newLineAndIndent();
break;
case SyntaxKind.BlockCommentTrivia:
if (lineBreak) {
replaceContent = newLineAndIndent();
} else {
// symbol following comment on the same line: keep on same line, separate with ' '
replaceContent = ' ';
}
break;
case SyntaxKind.ColonToken:
replaceContent = ' ';
break;
case SyntaxKind.StringLiteral:
if (secondToken === SyntaxKind.ColonToken) {
replaceContent = '';
break;
}
// fall through
case SyntaxKind.NullKeyword:
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
case SyntaxKind.NumericLiteral:
case SyntaxKind.CloseBraceToken:
case SyntaxKind.CloseBracketToken:
if (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia) {
replaceContent = ' ';
} else if (secondToken !== SyntaxKind.CommaToken && secondToken !== SyntaxKind.EOF) {
hasError = true;
}
break;
case SyntaxKind.Unknown:
hasError = true;
break;
}
if (lineBreak && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
replaceContent = newLineAndIndent();
}
}
const secondTokenStart = scanner.getTokenOffset() + formatTextStart;
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, options: FormattingOptions): number {
let i = 0;
let nChars = 0;
const tabSize = options.tabSize || 4;
while (i < content.length) {
const ch = content.charAt(i);
if (ch === ' ') {
nChars++;
} else if (ch === '\t') {
nChars += tabSize;
} else {
break;
}
i++;
}
return Math.floor(nChars / tabSize);
}
export function getEOL(options: FormattingOptions, text: string): string {
for (let i = 0; i < text.length; i++) {
const 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';
}
export function isEOL(text: string, offset: number) {
return '\r\n'.indexOf(text.charAt(offset)) !== -1;
}

View File

@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object';
export interface IJSONSchema {
id?: string;
$id?: string;
$schema?: string;
type?: JSONSchemaType | JSONSchemaType[];
title?: string;
default?: any;
definitions?: IJSONSchemaMap;
description?: string;
properties?: IJSONSchemaMap;
patternProperties?: IJSONSchemaMap;
additionalProperties?: boolean | IJSONSchema;
minProperties?: number;
maxProperties?: number;
dependencies?: IJSONSchemaMap | { [prop: string]: string[] };
items?: IJSONSchema | IJSONSchema[];
minItems?: number;
maxItems?: number;
uniqueItems?: boolean;
additionalItems?: boolean | IJSONSchema;
pattern?: string;
minLength?: number;
maxLength?: number;
minimum?: number;
maximum?: number;
exclusiveMinimum?: boolean | number;
exclusiveMaximum?: boolean | number;
multipleOf?: number;
required?: string[];
$ref?: string;
anyOf?: IJSONSchema[];
allOf?: IJSONSchema[];
oneOf?: IJSONSchema[];
not?: IJSONSchema;
enum?: any[];
format?: string;
// schema draft 06
const?: any;
contains?: IJSONSchema;
propertyNames?: IJSONSchema;
// schema draft 07
$comment?: string;
if?: IJSONSchema;
then?: IJSONSchema;
else?: IJSONSchema;
// VS Code extensions
defaultSnippets?: IJSONSchemaSnippet[];
errorMessage?: string;
patternErrorMessage?: string;
deprecationMessage?: string;
markdownDeprecationMessage?: string;
enumDescriptions?: string[];
markdownEnumDescriptions?: string[];
markdownDescription?: string;
doNotSuggest?: boolean;
suggestSortText?: string;
allowComments?: boolean;
allowTrailingCommas?: boolean;
}
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,602 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { OperatingSystem } from 'vs/base/common/platform';
import { illegalArgument } from 'vs/base/common/errors';
/**
* 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 {
const chordPart = ((secondPart & 0x0000FFFF) << 16) >>> 0;
return (firstPart | chordPart) >>> 0;
}
export function createKeybinding(keybinding: number, OS: OperatingSystem): Keybinding | null {
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 new ChordKeybinding([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 class SimpleKeybinding {
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: SimpleKeybinding): boolean {
return (
this.ctrlKey === other.ctrlKey
&& this.shiftKey === other.shiftKey
&& this.altKey === other.altKey
&& this.metaKey === other.metaKey
&& this.keyCode === other.keyCode
);
}
public getHashCode(): string {
const ctrl = this.ctrlKey ? '1' : '0';
const shift = this.shiftKey ? '1' : '0';
const alt = this.altKey ? '1' : '0';
const meta = this.metaKey ? '1' : '0';
return `${ctrl}${shift}${alt}${meta}${this.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
);
}
public toChord(): ChordKeybinding {
return new ChordKeybinding([this]);
}
/**
* 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 parts: SimpleKeybinding[];
constructor(parts: SimpleKeybinding[]) {
if (parts.length === 0) {
throw illegalArgument(`parts`);
}
this.parts = parts;
}
public getHashCode(): string {
let result = '';
for (let i = 0, len = this.parts.length; i < len; i++) {
if (i !== 0) {
result += ';';
}
result += this.parts[i].getHashCode();
}
return result;
}
public equals(other: ChordKeybinding | null): boolean {
if (other === null) {
return false;
}
if (this.parts.length !== other.parts.length) {
return false;
}
for (let i = 0; i < this.parts.length; i++) {
if (!this.parts[i].equals(other.parts[i])) {
return false;
}
}
return true;
}
}
export type Keybinding = ChordKeybinding;
export class ResolvedKeybindingPart {
readonly ctrlKey: boolean;
readonly shiftKey: boolean;
readonly altKey: boolean;
readonly metaKey: boolean;
readonly keyLabel: string | null;
readonly keyAriaLabel: string | null;
constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, kbLabel: string | null, kbAriaLabel: string | null) {
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 | null;
/**
* This prints the binding in a format suitable for ARIA.
*/
public abstract getAriaLabel(): string | null;
/**
* 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 | null;
/**
* This prints the binding in a format suitable for user settings.
*/
public abstract getUserSettingsLabel(): string | null;
/**
* Is the user settings label reflecting the label?
*/
public abstract isWYSIWYG(): boolean;
/**
* Is the binding a chord?
*/
public abstract isChord(): boolean;
/**
* Returns the parts that comprise of the keybinding.
* Simple keybindings return one element.
*/
public abstract getParts(): ResolvedKeybindingPart[];
/**
* Returns the parts that should be used for dispatching.
*/
public abstract getDispatchParts(): (string | null)[];
}

View File

@@ -0,0 +1,190 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 interface KeyLabelProvider<T extends Modifiers> {
(keybinding: T): string | null;
}
export class ModifierLabelProvider {
public readonly modifierLabels: ModifierLabels[];
constructor(mac: ModifierLabels, windows: ModifierLabels, linux: ModifierLabels = windows) {
this.modifierLabels = [null!]; // index 0 will never me accessed.
this.modifierLabels[OperatingSystem.Macintosh] = mac;
this.modifierLabels[OperatingSystem.Windows] = windows;
this.modifierLabels[OperatingSystem.Linux] = linux;
}
public toLabel<T extends Modifiers>(OS: OperatingSystem, parts: T[], keyLabelProvider: KeyLabelProvider<T>): string | null {
if (parts.length === 0) {
return null;
}
const result: string[] = [];
for (let i = 0, len = parts.length; i < len; i++) {
const part = parts[i];
const keyLabel = keyLabelProvider(part);
if (keyLabel === null) {
// this keybinding cannot be expressed...
return null;
}
result[i] = _simpleAsString(part, keyLabel, this.modifierLabels[OS]);
}
return result.join(' ');
}
}
/**
* 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({ key: 'ctrlKey', comment: ['This is the short form for the Control key on the keyboard'] }, "Ctrl"),
shiftKey: nls.localize({ key: 'shiftKey', comment: ['This is the short form for the Shift key on the keyboard'] }, "Shift"),
altKey: nls.localize({ key: 'altKey', comment: ['This is the short form for the Alt key on the keyboard'] }, "Alt"),
metaKey: nls.localize({ key: 'windowsKey', comment: ['This is the short form for the Windows key on the keyboard'] }, "Windows"),
separator: '+',
},
{
ctrlKey: nls.localize({ key: 'ctrlKey', comment: ['This is the short form for the Control key on the keyboard'] }, "Ctrl"),
shiftKey: nls.localize({ key: 'shiftKey', comment: ['This is the short form for the Shift key on the keyboard'] }, "Shift"),
altKey: nls.localize({ key: 'altKey', comment: ['This is the short form for the Alt key on the keyboard'] }, "Alt"),
metaKey: nls.localize({ key: 'superKey', comment: ['This is the short form for the Super key on the keyboard'] }, "Super"),
separator: '+',
}
);
/**
* A label provider that prints modifiers in a suitable format for ARIA.
*/
export const AriaLabelProvider = new ModifierLabelProvider(
{
ctrlKey: nls.localize({ key: 'ctrlKey.long', comment: ['This is the long form for the Control key on the keyboard'] }, "Control"),
shiftKey: nls.localize({ key: 'shiftKey.long', comment: ['This is the long form for the Shift key on the keyboard'] }, "Shift"),
altKey: nls.localize({ key: 'altKey.long', comment: ['This is the long form for the Alt key on the keyboard'] }, "Alt"),
metaKey: nls.localize({ key: 'cmdKey.long', comment: ['This is the long form for the Command key on the keyboard'] }, "Command"),
separator: '+',
},
{
ctrlKey: nls.localize({ key: 'ctrlKey.long', comment: ['This is the long form for the Control key on the keyboard'] }, "Control"),
shiftKey: nls.localize({ key: 'shiftKey.long', comment: ['This is the long form for the Shift key on the keyboard'] }, "Shift"),
altKey: nls.localize({ key: 'altKey.long', comment: ['This is the long form for the Alt key on the keyboard'] }, "Alt"),
metaKey: nls.localize({ key: 'windowsKey.long', comment: ['This is the long form for the Windows key on the keyboard'] }, "Windows"),
separator: '+',
},
{
ctrlKey: nls.localize({ key: 'ctrlKey.long', comment: ['This is the long form for the Control key on the keyboard'] }, "Control"),
shiftKey: nls.localize({ key: 'shiftKey.long', comment: ['This is the long form for the Shift key on the keyboard'] }, "Shift"),
altKey: nls.localize({ key: 'altKey.long', comment: ['This is the long form for the Alt key on the keyboard'] }, "Alt"),
metaKey: nls.localize({ key: 'superKey.long', comment: ['This is the long form for the Super key on the keyboard'] }, "Super"),
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 '';
}
const 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
if (key !== '') {
result.push(key);
}
return result.join(labels.separator);
}

View File

@@ -0,0 +1,124 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ChordKeybinding, KeyCodeUtils, Keybinding, SimpleKeybinding } from 'vs/base/common/keyCodes';
import { OperatingSystem } from 'vs/base/common/platform';
import { ScanCodeBinding, ScanCodeUtils } from 'vs/base/common/scanCode';
export class KeybindingParser {
private static _readModifiers(input: string) {
input = input.toLowerCase().trim();
let ctrl = false;
let shift = false;
let alt = false;
let meta = false;
let matchedModifier: boolean;
do {
matchedModifier = false;
if (/^ctrl(\+|\-)/.test(input)) {
ctrl = true;
input = input.substr('ctrl-'.length);
matchedModifier = true;
}
if (/^shift(\+|\-)/.test(input)) {
shift = true;
input = input.substr('shift-'.length);
matchedModifier = true;
}
if (/^alt(\+|\-)/.test(input)) {
alt = true;
input = input.substr('alt-'.length);
matchedModifier = true;
}
if (/^meta(\+|\-)/.test(input)) {
meta = true;
input = input.substr('meta-'.length);
matchedModifier = true;
}
if (/^win(\+|\-)/.test(input)) {
meta = true;
input = input.substr('win-'.length);
matchedModifier = true;
}
if (/^cmd(\+|\-)/.test(input)) {
meta = true;
input = input.substr('cmd-'.length);
matchedModifier = true;
}
} while (matchedModifier);
let key: string;
const firstSpaceIdx = input.indexOf(' ');
if (firstSpaceIdx > 0) {
key = input.substring(0, firstSpaceIdx);
input = input.substring(firstSpaceIdx);
} else {
key = input;
input = '';
}
return {
remains: input,
ctrl,
shift,
alt,
meta,
key
};
}
private static parseSimpleKeybinding(input: string): [SimpleKeybinding, string] {
const mods = this._readModifiers(input);
const keyCode = KeyCodeUtils.fromUserSettings(mods.key);
return [new SimpleKeybinding(mods.ctrl, mods.shift, mods.alt, mods.meta, keyCode), mods.remains];
}
public static parseKeybinding(input: string, OS: OperatingSystem): Keybinding | null {
if (!input) {
return null;
}
const parts: SimpleKeybinding[] = [];
let part: SimpleKeybinding;
do {
[part, input] = this.parseSimpleKeybinding(input);
parts.push(part);
} while (input.length > 0);
return new ChordKeybinding(parts);
}
private static parseSimpleUserBinding(input: string): [SimpleKeybinding | ScanCodeBinding, string] {
const mods = this._readModifiers(input);
const scanCodeMatch = mods.key.match(/^\[([^\]]+)\]$/);
if (scanCodeMatch) {
const strScanCode = scanCodeMatch[1];
const scanCode = ScanCodeUtils.lowerCaseToEnum(strScanCode);
return [new ScanCodeBinding(mods.ctrl, mods.shift, mods.alt, mods.meta, scanCode), mods.remains];
}
const keyCode = KeyCodeUtils.fromUserSettings(mods.key);
return [new SimpleKeybinding(mods.ctrl, mods.shift, mods.alt, mods.meta, keyCode), mods.remains];
}
static parseUserBinding(input: string): (SimpleKeybinding | ScanCodeBinding)[] {
if (!input) {
return [];
}
const parts: (SimpleKeybinding | ScanCodeBinding)[] = [];
let part: SimpleKeybinding | ScanCodeBinding;
while (input.length > 0) {
[part, input] = this.parseSimpleUserBinding(input);
parts.push(part);
}
return parts;
}
}

View File

@@ -0,0 +1,398 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { posix, normalize, win32, sep } from 'vs/base/common/path';
import { startsWithIgnoreCase, rtrim } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform';
import { isEqual, basename, relativePath } from 'vs/base/common/resources';
export interface IWorkspaceFolderProvider {
getWorkspaceFolder(resource: URI): { uri: URI, name?: string } | null;
getWorkspace(): {
folders: { uri: URI, name?: string }[];
};
}
export interface IUserHomeProvider {
userHome?: URI;
}
/**
* @deprecated use LabelService instead
*/
export function getPathLabel(resource: URI | string, userHomeProvider?: IUserHomeProvider, rootProvider?: IWorkspaceFolderProvider): string {
if (typeof resource === 'string') {
resource = URI.file(resource);
}
// return early if we can resolve a relative path label from the root
if (rootProvider) {
const baseResource = rootProvider.getWorkspaceFolder(resource);
if (baseResource) {
const hasMultipleRoots = rootProvider.getWorkspace().folders.length > 1;
let pathLabel: string;
if (isEqual(baseResource.uri, resource)) {
pathLabel = ''; // no label if paths are identical
} else {
pathLabel = relativePath(baseResource.uri, resource)!;
}
if (hasMultipleRoots) {
const rootName = baseResource.name ? baseResource.name : basename(baseResource.uri);
pathLabel = pathLabel ? (rootName + ' • ' + pathLabel) : rootName; // always show root basename if there are multiple
}
return pathLabel;
}
}
// return if the resource is neither file:// nor untitled:// and no baseResource was provided
if (resource.scheme !== Schemas.file && resource.scheme !== Schemas.untitled) {
return resource.with({ query: null, fragment: null }).toString(true);
}
// convert c:\something => C:\something
if (hasDriveLetter(resource.fsPath)) {
return normalize(normalizeDriveLetter(resource.fsPath));
}
// normalize and tildify (macOS, Linux only)
let res = normalize(resource.fsPath);
if (!isWindows && userHomeProvider?.userHome) {
res = tildify(res, userHomeProvider.userHome.fsPath);
}
return res;
}
export function getBaseLabel(resource: URI | string): string;
export function getBaseLabel(resource: URI | string | undefined): string | undefined;
export function getBaseLabel(resource: URI | string | undefined): string | undefined {
if (!resource) {
return undefined;
}
if (typeof resource === 'string') {
resource = URI.file(resource);
}
const base = basename(resource) || (resource.scheme === Schemas.file ? resource.fsPath : resource.path) /* can be empty string if '/' is passed in */;
// convert c: => C:
if (hasDriveLetter(base)) {
return normalizeDriveLetter(base);
}
return base;
}
function hasDriveLetter(path: string): boolean {
return !!(isWindows && path && path[1] === ':');
}
export function normalizeDriveLetter(path: string): string {
if (hasDriveLetter(path)) {
return path.charAt(0).toUpperCase() + path.slice(1);
}
return path;
}
let normalizedUserHomeCached: { original: string; normalized: string } = Object.create(null);
export function tildify(path: string, userHome: string): string {
if (isWindows || !path || !userHome) {
return path; // unsupported
}
// Keep a normalized user home path as cache to prevent accumulated string creation
let normalizedUserHome = normalizedUserHomeCached.original === userHome ? normalizedUserHomeCached.normalized : undefined;
if (!normalizedUserHome) {
normalizedUserHome = `${rtrim(userHome, posix.sep)}${posix.sep}`;
normalizedUserHomeCached = { original: userHome, normalized: normalizedUserHome };
}
// Linux: case sensitive, macOS: case insensitive
if (isLinux ? path.startsWith(normalizedUserHome) : startsWithIgnoreCase(path, normalizedUserHome)) {
path = `~/${path.substr(normalizedUserHome.length)}`;
}
return path;
}
export function untildify(path: string, userHome: string): string {
return path.replace(/^~($|\/|\\)/, `${userHome}$1`);
}
/**
* 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[], pathSeparator: string = sep): 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] = `.${pathSeparator}`;
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(pathSeparator) === 0) {
prefix = path.substr(0, path.indexOf(pathSeparator) + pathSeparator.length);
path = path.substr(path.indexOf(pathSeparator) + pathSeparator.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(pathSeparator);
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(pathSeparator);
// 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(pathSeparator) > -1) ? pathSeparator + subpath : subpath;
const isOtherPathEnding: boolean = paths[otherPathIndex].endsWith(subpathWithSep);
match = !isSubpathEnding || isOtherPathEnding;
}
}
// found unique subpath
if (!match) {
let result = '';
// preserve disk drive or root prefix
if (segments[0].endsWith(':') || prefix !== '') {
if (start === 1) {
// extend subpath to include disk drive prefix
start = 0;
subpathLength++;
subpath = segments[0] + pathSeparator + subpath;
}
if (start > 0) {
result = segments[0] + pathSeparator;
}
result = prefix + result;
}
// add ellipsis at the beginning if neeeded
if (start > 0) {
result = result + ellipsis + pathSeparator;
}
result = result + subpath;
// add ellipsis at the end if needed
if (start + subpathLength < segments.length) {
result = result + pathSeparator + 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 | undefined | null } = Object.create(null)): string {
const segments: ISegment[] = [];
let inVariable = false;
let curVal = '';
for (const char of template) {
// 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.type === Type.TEXT) && 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 (isMacintosh || forceDisableMnemonics) {
return label.replace(/\(&&\w\)|&&/g, '').replace(/&/g, isMacintosh ? '&' : '&&');
}
return label.replace(/&&|&/g, m => m === '&' ? '&&' : '&');
}
/**
* Handles mnemonics for buttons. Depending on OS:
* - Windows: Supported via & character (replace && with & and & with && for escaping)
* - Linux: Supported via _ character (replace && with _)
* - macOS: Unsupported (replace && with empty string)
*/
export function mnemonicButtonLabel(label: string, forceDisableMnemonics?: boolean): string {
if (isMacintosh || forceDisableMnemonics) {
return label.replace(/\(&&\w\)|&&/g, '');
}
if (isWindows) {
return label.replace(/&&|&/g, m => m === '&' ? '&&' : '&');
}
return label.replace(/&&/g, '_');
}
export function unmnemonicLabel(label: string): string {
return label.replace(/&/g, '&&');
}
/**
* Splits a path in name and parent path, supporting both '/' and '\'
*/
export function splitName(fullPath: string): { name: string, parentPath: string } {
const p = fullPath.indexOf('/') !== -1 ? posix : win32;
const name = p.basename(fullPath);
const parentPath = p.dirname(fullPath);
if (name.length) {
return { name, parentPath };
}
// only the root segment
return { name: parentPath, parentPath: '' };
}

View File

@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* A value that is resolved synchronously when it is first needed.
*/
export interface Lazy<T> {
hasValue(): boolean;
getValue(): T;
map<R>(f: (x: T) => R): Lazy<R>;
}
export class Lazy<T> {
private _didRun: boolean = false;
private _value?: T;
private _error: Error | undefined;
constructor(
private readonly executor: () => T,
) { }
/**
* True if the lazy value has been resolved.
*/
hasValue() { return this._didRun; }
/**
* Get the wrapped value.
*
* This will force evaluation of the lazy value if it has not been resolved yet. Lazy values are only
* resolved once. `getValue` will re-throw exceptions that are hit while resolving the value
*/
getValue(): T {
if (!this._didRun) {
try {
this._value = this.executor();
} catch (err) {
this._error = err;
} finally {
this._didRun = true;
}
}
if (this._error) {
throw this._error;
}
return this._value!;
}
/**
* Get the wrapped value without forcing evaluation.
*/
get rawValue(): T | undefined { return this._value; }
/**
* Create a new lazy value that is the result of applying `f` to the wrapped value.
*
* This does not force the evaluation of the current lazy value.
*/
map<R>(f: (x: T) => R): Lazy<R> {
return new Lazy<R>(() => f(this.getValue()));
}
}

View File

@@ -0,0 +1,274 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { once } from 'vs/base/common/functional';
import { Iterable } from 'vs/base/common/iterator';
/**
* Enables logging of potentially leaked disposables.
*
* A disposable is considered leaked if it is not disposed or not registered as the child of
* another disposable. This tracking is very simple an only works for classes that either
* extend Disposable or use a DisposableStore. This means there are a lot of false positives.
*/
const TRACK_DISPOSABLES = false;
const __is_disposable_tracked__ = '__is_disposable_tracked__';
function markTracked<T extends IDisposable>(x: T): void {
if (!TRACK_DISPOSABLES) {
return;
}
if (x && x !== Disposable.None) {
try {
(x as any)[__is_disposable_tracked__] = true;
} catch {
// noop
}
}
}
function trackDisposable<T extends IDisposable>(x: T): T {
if (!TRACK_DISPOSABLES) {
return x;
}
const stack = new Error('Potentially leaked disposable').stack!;
setTimeout(() => {
if (!(x as any)[__is_disposable_tracked__]) {
console.log(stack);
}
}, 3000);
return x;
}
export class MultiDisposeError extends Error {
constructor(
public readonly errors: any[]
) {
super(`Encounter errors while disposing of store. Errors: [${errors.join(', ')}]`);
}
}
export interface IDisposable {
dispose(): void;
}
export function isDisposable<E extends object>(thing: E): thing is E & IDisposable {
return typeof (<IDisposable>thing).dispose === 'function' && (<IDisposable>thing).dispose.length === 0;
}
export function dispose<T extends IDisposable>(disposable: T): T;
export function dispose<T extends IDisposable>(disposable: T | undefined): T | undefined;
export function dispose<T extends IDisposable, A extends IterableIterator<T> = IterableIterator<T>>(disposables: IterableIterator<T>): A;
export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>;
export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>;
export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | undefined): any {
if (Iterable.is(arg)) {
let errors: any[] = [];
for (const d of arg) {
if (d) {
markTracked(d);
try {
d.dispose();
} catch (e) {
errors.push(e);
}
}
}
if (errors.length === 1) {
throw errors[0];
} else if (errors.length > 1) {
throw new MultiDisposeError(errors);
}
return Array.isArray(arg) ? [] : arg;
} else if (arg) {
markTracked(arg);
arg.dispose();
return arg;
}
}
export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
disposables.forEach(markTracked);
return trackDisposable({ dispose: () => dispose(disposables) });
}
export function toDisposable(fn: () => void): IDisposable {
const self = trackDisposable({
dispose: () => {
markTracked(self);
fn();
}
});
return self;
}
export class DisposableStore implements IDisposable {
static DISABLE_DISPOSED_WARNING = false;
private _toDispose = new Set<IDisposable>();
private _isDisposed = false;
/**
* Dispose of all registered disposables and mark this object as disposed.
*
* Any future disposables added to this object will be disposed of on `add`.
*/
public dispose(): void {
if (this._isDisposed) {
return;
}
markTracked(this);
this._isDisposed = true;
this.clear();
}
/**
* Dispose of all registered disposables but do not mark this object as disposed.
*/
public clear(): void {
try {
dispose(this._toDispose.values());
} finally {
this._toDispose.clear();
}
}
public add<T extends IDisposable>(t: T): T {
if (!t) {
return t;
}
if ((t as unknown as DisposableStore) === this) {
throw new Error('Cannot register a disposable on itself!');
}
markTracked(t);
if (this._isDisposed) {
if (!DisposableStore.DISABLE_DISPOSED_WARNING) {
console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack);
}
} else {
this._toDispose.add(t);
}
return t;
}
}
export abstract class Disposable implements IDisposable {
static readonly None = Object.freeze<IDisposable>({ dispose() { } });
private readonly _store = new DisposableStore();
constructor() {
trackDisposable(this);
}
public dispose(): void {
markTracked(this);
this._store.dispose();
}
protected _register<T extends IDisposable>(t: T): T {
if ((t as unknown as Disposable) === this) {
throw new Error('Cannot register a disposable on itself!');
}
return this._store.add(t);
}
}
/**
* Manages the lifecycle of a disposable value that may be changed.
*
* This ensures that when the disposable value is changed, the previously held disposable is disposed of. You can
* also register a `MutableDisposable` on a `Disposable` to ensure it is automatically cleaned up.
*/
export class MutableDisposable<T extends IDisposable> implements IDisposable {
private _value?: T;
private _isDisposed = false;
constructor() {
trackDisposable(this);
}
get value(): T | undefined {
return this._isDisposed ? undefined : this._value;
}
set value(value: T | undefined) {
if (this._isDisposed || value === this._value) {
return;
}
if (this._value) {
this._value.dispose();
}
if (value) {
markTracked(value);
}
this._value = value;
}
clear() {
this.value = undefined;
}
dispose(): void {
this._isDisposed = true;
markTracked(this);
if (this._value) {
this._value.dispose();
}
this._value = undefined;
}
}
export interface IReference<T> extends IDisposable {
readonly object: T;
}
export abstract class ReferenceCollection<T> {
private readonly references: Map<string, { readonly object: T; counter: number; }> = new Map();
acquire(key: string, ...args: any[]): IReference<T> {
let reference = this.references.get(key);
if (!reference) {
reference = { counter: 0, object: this.createReferencedObject(key, ...args) };
this.references.set(key, reference);
}
const { object } = reference;
const dispose = once(() => {
if (--reference!.counter === 0) {
this.destroyReferencedObject(key, reference!.object);
this.references.delete(key);
}
});
reference.counter++;
return { object, dispose };
}
protected abstract createReferencedObject(key: string, ...args: any[]): T;
protected abstract destroyReferencedObject(key: string, object: T): void;
}
export class ImmortalReference<T> implements IReference<T> {
constructor(public object: T) { }
dispose(): void { /* noop */ }
}

View File

@@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class Node<E> {
static readonly Undefined = new Node<any>(undefined);
element: E;
next: Node<E>;
prev: Node<E>;
constructor(element: E) {
this.element = element;
this.next = Node.Undefined;
this.prev = Node.Undefined;
}
}
export class LinkedList<E> {
private _first: Node<E> = Node.Undefined;
private _last: Node<E> = Node.Undefined;
private _size: number = 0;
get size(): number {
return this._size;
}
isEmpty(): boolean {
return this._first === Node.Undefined;
}
clear(): void {
this._first = Node.Undefined;
this._last = Node.Undefined;
this._size = 0;
}
unshift(element: E): () => void {
return this._insert(element, false);
}
push(element: E): () => void {
return this._insert(element, true);
}
private _insert(element: E, atTheEnd: boolean): () => void {
const newNode = new Node(element);
if (this._first === Node.Undefined) {
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;
}
this._size += 1;
let didRemove = false;
return () => {
if (!didRemove) {
didRemove = true;
this._remove(newNode);
}
};
}
shift(): E | undefined {
if (this._first === Node.Undefined) {
return undefined;
} else {
const res = this._first.element;
this._remove(this._first);
return res;
}
}
pop(): E | undefined {
if (this._last === Node.Undefined) {
return undefined;
} else {
const res = this._last.element;
this._remove(this._last);
return res;
}
}
private _remove(node: Node<E>): void {
if (node.prev !== Node.Undefined && node.next !== Node.Undefined) {
// middle
const anchor = node.prev;
anchor.next = node.next;
node.next.prev = anchor;
} else if (node.prev === Node.Undefined && node.next === Node.Undefined) {
// only node
this._first = Node.Undefined;
this._last = Node.Undefined;
} else if (node.next === Node.Undefined) {
// last
this._last = this._last!.prev!;
this._last.next = Node.Undefined;
} else if (node.prev === Node.Undefined) {
// first
this._first = this._first!.next!;
this._first.prev = Node.Undefined;
}
// done
this._size -= 1;
}
*[Symbol.iterator](): Iterator<E> {
let node = this._first;
while (node !== Node.Undefined) {
yield node.element;
node = node.next;
}
}
toArray(): E[] {
const result: E[] = [];
for (let node = this._first; node !== Node.Undefined; node = node.next) {
result.push(node.element);
}
return result;
}
}

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { memoize } from 'vs/base/common/decorators';
export interface ILink {
readonly label: string;
readonly href: string;
readonly title?: string;
}
export type LinkedTextNode = string | ILink;
export class LinkedText {
constructor(readonly nodes: LinkedTextNode[]) { }
@memoize
toString(): string {
return this.nodes.map(node => typeof node === 'string' ? node : node.label).join('');
}
}
const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: ("|')([^\3]+)(\3))?\)/gi;
export function parseLinkedText(text: string): LinkedText {
const result: LinkedTextNode[] = [];
let index = 0;
let match: RegExpExecArray | null;
while (match = LINK_REGEX.exec(text)) {
if (match.index - index > 0) {
result.push(text.substring(index, match.index));
}
const [, label, href, , title] = match;
if (title) {
result.push({ label, href, title });
} else {
result.push({ label, href });
}
index = match.index + match[0].length;
}
if (index < text.length) {
result.push(text.substring(index));
}
return new LinkedText(result);
}

View File

@@ -0,0 +1,990 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { CharCode } from 'vs/base/common/charCode';
import { compareSubstringIgnoreCase, compare, compareSubstring, compareIgnoreCase } from 'vs/base/common/strings';
import { isLinux } from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network';
export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {
let result = map.get(key);
if (result === undefined) {
result = value;
map.set(key, result);
}
return result;
}
export function mapToString<K, V>(map: Map<K, V>): string {
const entries: string[] = [];
map.forEach((value, key) => {
entries.push(`${key} => ${value}`);
});
return `Map(${map.size}) {${entries.join(', ')}}`;
}
export function setToString<K>(set: Set<K>): string {
const entries: K[] = [];
set.forEach(value => {
entries.push(value);
});
return `Set(${set.size}) {${entries.join(', ')}}`;
}
export interface IKeyIterator<K> {
reset(key: K): this;
next(): this;
hasNext(): boolean;
cmp(a: string): number;
value(): string;
}
export class StringIterator implements IKeyIterator<string> {
private _value: string = '';
private _pos: number = 0;
reset(key: string): this {
this._value = key;
this._pos = 0;
return this;
}
next(): this {
this._pos += 1;
return this;
}
hasNext(): boolean {
return this._pos < this._value.length - 1;
}
cmp(a: string): number {
const aCode = a.charCodeAt(0);
const thisCode = this._value.charCodeAt(this._pos);
return aCode - thisCode;
}
value(): string {
return this._value[this._pos];
}
}
export class PathIterator implements IKeyIterator<string> {
private _value!: string;
private _from!: number;
private _to!: number;
constructor(
private readonly _splitOnBackslash: boolean = true,
private readonly _caseSensitive: boolean = true
) { }
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;
}
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 === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash) {
if (justSeps) {
this._from++;
} else {
break;
}
} else {
justSeps = false;
}
}
return this;
}
cmp(a: string): number {
return this._caseSensitive
? compareSubstring(a, this._value, 0, a.length, this._from, this._to)
: compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to);
}
value(): string {
return this._value.substring(this._from, this._to);
}
}
const enum UriIteratorState {
Scheme = 1, Authority = 2, Path = 3, Query = 4, Fragment = 5
}
export class UriIterator implements IKeyIterator<URI> {
private _pathIterator!: PathIterator;
private _value!: URI;
private _states: UriIteratorState[] = [];
private _stateIdx: number = 0;
constructor(private readonly _ignorePathCasing: boolean | undefined) { }
reset(key: URI): this {
this._value = key;
this._states = [];
if (this._value.scheme) {
this._states.push(UriIteratorState.Scheme);
}
if (this._value.authority) {
this._states.push(UriIteratorState.Authority);
}
if (this._value.path) {
this._pathIterator = new PathIterator(false, this._ignorePathCasing === undefined
? key.scheme === Schemas.file && isLinux
: !this._ignorePathCasing
);
this._pathIterator.reset(key.path);
if (this._pathIterator.value()) {
this._states.push(UriIteratorState.Path);
}
}
if (this._value.query) {
this._states.push(UriIteratorState.Query);
}
if (this._value.fragment) {
this._states.push(UriIteratorState.Fragment);
}
this._stateIdx = 0;
return this;
}
next(): this {
if (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext()) {
this._pathIterator.next();
} else {
this._stateIdx += 1;
}
return this;
}
hasNext(): boolean {
return (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext())
|| this._stateIdx < this._states.length - 1;
}
cmp(a: string): number {
if (this._states[this._stateIdx] === UriIteratorState.Scheme) {
return compareIgnoreCase(a, this._value.scheme);
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
return compareIgnoreCase(a, this._value.authority);
} else if (this._states[this._stateIdx] === UriIteratorState.Path) {
return this._pathIterator.cmp(a);
} else if (this._states[this._stateIdx] === UriIteratorState.Query) {
return compare(a, this._value.query);
} else if (this._states[this._stateIdx] === UriIteratorState.Fragment) {
return compare(a, this._value.fragment);
}
throw new Error();
}
value(): string {
if (this._states[this._stateIdx] === UriIteratorState.Scheme) {
return this._value.scheme;
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
return this._value.authority;
} else if (this._states[this._stateIdx] === UriIteratorState.Path) {
return this._pathIterator.value();
} else if (this._states[this._stateIdx] === UriIteratorState.Query) {
return this._value.query;
} else if (this._states[this._stateIdx] === UriIteratorState.Fragment) {
return this._value.fragment;
}
throw new Error();
}
}
class TernarySearchTreeNode<K, V> {
segment!: string;
value: V | undefined;
key!: K;
left: TernarySearchTreeNode<K, V> | undefined;
mid: TernarySearchTreeNode<K, V> | undefined;
right: TernarySearchTreeNode<K, V> | undefined;
isEmpty(): boolean {
return !this.left && !this.mid && !this.right && !this.value;
}
}
export class TernarySearchTree<K, V> {
static forUris<E>(ignorePathCasing?: boolean): TernarySearchTree<URI, E> {
return new TernarySearchTree<URI, E>(new UriIterator(ignorePathCasing));
}
static forPaths<E>(): TernarySearchTree<string, E> {
return new TernarySearchTree<string, E>(new PathIterator());
}
static forStrings<E>(): TernarySearchTree<string, E> {
return new TernarySearchTree<string, E>(new StringIterator());
}
private _iter: IKeyIterator<K>;
private _root: TernarySearchTreeNode<K, V> | undefined;
constructor(segments: IKeyIterator<K>) {
this._iter = segments;
}
clear(): void {
this._root = undefined;
}
set(key: K, element: V): V | undefined {
const iter = this._iter.reset(key);
let node: TernarySearchTreeNode<K, V>;
if (!this._root) {
this._root = new TernarySearchTreeNode<K, V>();
this._root.segment = iter.value();
}
node = this._root;
while (true) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
if (!node.left) {
node.left = new TernarySearchTreeNode<K, V>();
node.left.segment = iter.value();
}
node = node.left;
} else if (val < 0) {
// right
if (!node.right) {
node.right = new TernarySearchTreeNode<K, V>();
node.right.segment = iter.value();
}
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
if (!node.mid) {
node.mid = new TernarySearchTreeNode<K, V>();
node.mid.segment = iter.value();
}
node = node.mid;
} else {
break;
}
}
const oldElement = node.value;
node.value = element;
node.key = key;
return oldElement;
}
get(key: K): V | undefined {
const iter = this._iter.reset(key);
let node = this._root;
while (node) {
const val = iter.cmp(node.segment);
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;
}
}
return node ? node.value : undefined;
}
delete(key: K): void {
return this._delete(key, false);
}
deleteSuperstr(key: K): void {
return this._delete(key, true);
}
private _delete(key: K, superStr: boolean): void {
const iter = this._iter.reset(key);
const stack: [-1 | 0 | 1, TernarySearchTreeNode<K, V>][] = [];
let node = this._root;
// find and unset node
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
stack.push([1, node]);
node = node.left;
} else if (val < 0) {
// right
stack.push([-1, node]);
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
stack.push([0, node]);
node = node.mid;
} else {
// remove element
node.value = undefined;
// clean up empty nodes
while (stack.length > 0 && (node.isEmpty() || superStr)) {
let [dir, parent] = stack.pop()!;
switch (dir) {
case 1: parent.left = undefined; break;
case 0: parent.mid = undefined; break;
case -1: parent.right = undefined; break;
}
node = parent;
}
break;
}
}
}
findSubstr(key: K): V | undefined {
const iter = this._iter.reset(key);
let node = this._root;
let candidate: V | undefined = undefined;
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
node = node.left;
} else if (val < 0) {
// right
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
candidate = node.value || candidate;
node = node.mid;
} else {
break;
}
}
return node && node.value || candidate;
}
findSuperstr(key: K): Iterator<V> | undefined {
const iter = this._iter.reset(key);
let node = this._root;
while (node) {
const val = iter.cmp(node.segment);
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;
} else {
return this._values(node.mid);
}
}
}
return undefined;
}
forEach(callback: (value: V, index: K) => any): void {
for (const [key, value] of this) {
callback(value, key);
}
}
*[Symbol.iterator](): IterableIterator<[K, V]> {
yield* this._entries(this._root);
}
private *_values(node: TernarySearchTreeNode<K, V>): IterableIterator<V> {
for (const [, value] of this._entries(node)) {
yield value;
}
}
private *_entries(node: TernarySearchTreeNode<K, V> | undefined): IterableIterator<[K, V]> {
if (node) {
// left
yield* this._entries(node.left);
// node
if (node.value) {
// callback(node.value, this._iter.join(parts));
yield [node.key, node.value];
}
// mid
yield* this._entries(node.mid);
// right
yield* this._entries(node.right);
}
}
}
interface ResourceMapKeyFn {
(resource: URI): string;
}
export class ResourceMap<T> implements Map<URI, T> {
private static readonly defaultToKey = (resource: URI) => resource.toString();
readonly [Symbol.toStringTag] = 'ResourceMap';
private readonly map: Map<string, T>;
private readonly toKey: ResourceMapKeyFn;
/**
*
* @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
*/
constructor(toKey?: ResourceMapKeyFn);
/**
*
* @param other Another resource which this maps is created from
* @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
*/
constructor(other?: ResourceMap<T>, toKey?: ResourceMapKeyFn);
constructor(mapOrKeyFn?: ResourceMap<T> | ResourceMapKeyFn, toKey?: ResourceMapKeyFn) {
if (mapOrKeyFn instanceof ResourceMap) {
this.map = new Map(mapOrKeyFn.map);
this.toKey = toKey ?? ResourceMap.defaultToKey;
} else {
this.map = new Map();
this.toKey = mapOrKeyFn ?? ResourceMap.defaultToKey;
}
}
set(resource: URI, value: T): this {
this.map.set(this.toKey(resource), value);
return this;
}
get(resource: URI): T | undefined {
return this.map.get(this.toKey(resource));
}
has(resource: URI): boolean {
return this.map.has(this.toKey(resource));
}
get size(): number {
return this.map.size;
}
clear(): void {
this.map.clear();
}
delete(resource: URI): boolean {
return this.map.delete(this.toKey(resource));
}
forEach(clb: (value: T, key: URI, map: Map<URI, T>) => void, thisArg?: any): void {
if (typeof thisArg !== 'undefined') {
clb = clb.bind(thisArg);
}
for (let [index, value] of this.map) {
clb(value, URI.parse(index), <any>this);
}
}
values(): IterableIterator<T> {
return this.map.values();
}
*keys(): IterableIterator<URI> {
for (let key of this.map.keys()) {
yield URI.parse(key);
}
}
*entries(): IterableIterator<[URI, T]> {
for (let tuple of this.map.entries()) {
yield [URI.parse(tuple[0]), tuple[1]];
}
}
*[Symbol.iterator](): IterableIterator<[URI, T]> {
for (let item of this.map) {
yield [URI.parse(item[0]), item[1]];
}
}
}
interface Item<K, V> {
previous: Item<K, V> | undefined;
next: Item<K, V> | undefined;
key: K;
value: V;
}
export const enum Touch {
None = 0,
AsOld = 1,
AsNew = 2
}
export class LinkedMap<K, V> implements Map<K, V> {
readonly [Symbol.toStringTag] = 'LinkedMap';
private _map: Map<K, Item<K, V>>;
private _head: Item<K, V> | undefined;
private _tail: Item<K, V> | undefined;
private _size: number;
private _state: number;
constructor() {
this._map = new Map<K, Item<K, V>>();
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state = 0;
}
clear(): void {
this._map.clear();
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state++;
}
isEmpty(): boolean {
return !this._head && !this._tail;
}
get size(): number {
return this._size;
}
get first(): V | undefined {
return this._head?.value;
}
get last(): V | undefined {
return this._tail?.value;
}
has(key: K): boolean {
return this._map.has(key);
}
get(key: K, touch: Touch = Touch.None): V | undefined {
const item = this._map.get(key);
if (!item) {
return undefined;
}
if (touch !== Touch.None) {
this.touch(item, touch);
}
return item.value;
}
set(key: K, value: V, touch: Touch = Touch.None): this {
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.AsOld:
this.addItemFirst(item);
break;
case Touch.AsNew:
this.addItemLast(item);
break;
default:
this.addItemLast(item);
break;
}
this._map.set(key, item);
this._size++;
}
return this;
}
delete(key: K): boolean {
return !!this.remove(key);
}
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;
}
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;
}
forEach(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {
const state = this._state;
let current = this._head;
while (current) {
if (thisArg) {
callbackfn.bind(thisArg)(current.value, current.key, this);
} else {
callbackfn(current.value, current.key, this);
}
if (this._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
current = current.next;
}
}
keys(): IterableIterator<K> {
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<K> = {
[Symbol.iterator]() {
return iterator;
},
next(): IteratorResult<K> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result = { value: current.key, done: false };
current = current.next;
return result;
} else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
values(): IterableIterator<V> {
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<V> = {
[Symbol.iterator]() {
return iterator;
},
next(): IteratorResult<V> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result = { value: current.value, done: false };
current = current.next;
return result;
} else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
entries(): IterableIterator<[K, V]> {
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<[K, V]> = {
[Symbol.iterator]() {
return iterator;
},
next(): IteratorResult<[K, V]> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false };
current = current.next;
return result;
} else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
[Symbol.iterator](): IterableIterator<[K, V]> {
return this.entries();
}
protected trimOld(newSize: number) {
if (newSize >= this.size) {
return;
}
if (newSize === 0) {
this.clear();
return;
}
let current = this._head;
let currentSize = this.size;
while (current && currentSize > newSize) {
this._map.delete(current.key);
current = current.next;
currentSize--;
}
this._head = current;
this._size = currentSize;
if (current) {
current.previous = undefined;
}
this._state++;
}
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;
this._state++;
}
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;
this._state++;
}
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 can only happend if size === 1 which is handle
// by the case above.
if (!item.next) {
throw new Error('Invalid list');
}
item.next.previous = undefined;
this._head = item.next;
}
else if (item === this._tail) {
// This can only happend if size === 1 which is handle
// by the case above.
if (!item.previous) {
throw new Error('Invalid list');
}
item.previous.next = undefined;
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;
}
item.next = undefined;
item.previous = undefined;
this._state++;
}
private touch(item: Item<K, V>, touch: Touch): void {
if (!this._head || !this._tail) {
throw new Error('Invalid list');
}
if ((touch !== Touch.AsOld && touch !== Touch.AsNew)) {
return;
}
if (touch === Touch.AsOld) {
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;
this._state++;
} else if (touch === Touch.AsNew) {
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;
this._state++;
}
}
toJSON(): [K, V][] {
const data: [K, V][] = [];
this.forEach((value, key) => {
data.push([key, value]);
});
return data;
}
fromJSON(data: [K, V][]): void {
this.clear();
for (const [key, value] of data) {
this.set(key, value);
}
}
}
export class LRUCache<K, V> extends LinkedMap<K, V> {
private _limit: number;
private _ratio: number;
constructor(limit: number, ratio: number = 1) {
super();
this._limit = limit;
this._ratio = Math.min(Math.max(0, ratio), 1);
}
get limit(): number {
return this._limit;
}
set limit(limit: number) {
this._limit = limit;
this.checkTrim();
}
get ratio(): number {
return this._ratio;
}
set ratio(ratio: number) {
this._ratio = Math.min(Math.max(0, ratio), 1);
this.checkTrim();
}
get(key: K, touch: Touch = Touch.AsNew): V | undefined {
return super.get(key, touch);
}
peek(key: K): V | undefined {
return super.get(key, Touch.None);
}
set(key: K, value: V): this {
super.set(key, value, Touch.AsNew);
this.checkTrim();
return this;
}
private checkTrim() {
if (this.size > this._limit) {
this.trimOld(Math.round(this._limit * this._ratio));
}
}
}

View File

@@ -0,0 +1,17 @@
{
"registrations": [
{
"component": {
"type": "git",
"git": {
"name": "marked",
"repositoryUrl": "https://github.com/markedjs/marked",
"commitHash": "529a8d4e185a8aa561e4d8d2891f8556b5717cd4"
}
},
"license": "MIT",
"version": "0.6.2"
}
],
"version": 1
}

View File

@@ -0,0 +1,297 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Type definitions for Marked 0.4
// Project: https://github.com/markedjs/marked
// Definitions by: William Orr <https://github.com/worr>
// BendingBender <https://github.com/BendingBender>
// CrossR <https://github.com/CrossR>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
export as namespace marked;
export = marked;
/**
* 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
*/
declare function marked(src: string, callback: (error: any | undefined, parseResult: string) => void): 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
*/
declare function marked(src: string, options?: marked.MarkedOptions, callback?: (error: any | undefined, parseResult: string) => void): string;
declare namespace marked {
/**
* @param src String of markdown source to be compiled
* @param options Hash of options
*/
function lexer(src: string, options?: MarkedOptions): TokensList;
/**
* 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
*/
function parse(src: string, callback: (error: any | undefined, parseResult: string) => void): 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
*/
function parse(src: string, options?: MarkedOptions, callback?: (error: any | undefined, parseResult: string) => void): string;
/**
* @param src Tokenized source as array of tokens
* @param options Hash of options
*/
function parser(src: TokensList, options?: MarkedOptions): string;
/**
* Sets the default options.
*
* @param options Hash of options
*/
function setOptions(options: MarkedOptions): typeof marked;
class Renderer {
constructor(options?: MarkedOptions);
code(code: string, language: string, isEscaped: boolean): string;
blockquote(quote: string): string;
html(html: string): string;
heading(text: string, level: number, raw: string): 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: {
header: boolean;
align: 'center' | 'left' | 'right' | null;
}): 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;
image(href: string, title: string, text: string): string;
text(text: string): string;
}
class Lexer {
rules: Rules;
tokens: TokensList;
constructor(options?: MarkedOptions);
lex(src: string): TokensList;
}
interface Rules {
[ruleName: string]: RegExp | Rules;
}
type TokensList = Token[] & {
links: {
[key: string]: { href: string; title: string; }
}
};
type Token =
Tokens.Space
| Tokens.Code
| Tokens.Heading
| Tokens.Table
| Tokens.Hr
| Tokens.BlockquoteStart
| Tokens.BlockquoteEnd
| Tokens.ListStart
| Tokens.LooseItemStart
| Tokens.ListItemStart
| Tokens.ListItemEnd
| Tokens.ListEnd
| Tokens.Paragraph
| Tokens.HTML
| Tokens.Text;
namespace Tokens {
interface Space {
type: 'space';
}
interface Code {
type: 'code';
lang?: string;
text: string;
}
interface Heading {
type: 'heading';
depth: number;
text: string;
}
interface Table {
type: 'table';
header: string[];
align: Array<'center' | 'left' | 'right' | null>;
cells: string[][];
}
interface Hr {
type: 'hr';
}
interface BlockquoteStart {
type: 'blockquote_start';
}
interface BlockquoteEnd {
type: 'blockquote_end';
}
interface ListStart {
type: 'list_start';
ordered: boolean;
}
interface LooseItemStart {
type: 'loose_item_start';
}
interface ListItemStart {
type: 'list_item_start';
}
interface ListItemEnd {
type: 'list_item_end';
}
interface ListEnd {
type: 'list_end';
}
interface Paragraph {
type: 'paragraph';
pre?: boolean;
text: string;
}
interface HTML {
type: 'html';
pre: boolean;
text: string;
}
interface Text {
type: 'text';
text: string;
}
}
interface MarkedOptions {
/**
* A prefix URL for any relative link.
*/
baseUrl?: string;
/**
* Enable GFM line breaks. This option requires the gfm option to be true.
*/
breaks?: boolean;
/**
* Enable GitHub flavored markdown.
*/
gfm?: boolean;
/**
* Include an id attribute when emitting headings.
*/
headerIds?: boolean;
/**
* Set the prefix for header tag ids.
*/
headerPrefix?: string;
/**
* A function to highlight code blocks. The function takes three arguments: code, lang, and callback.
*/
highlight?(code: string, lang: string, callback?: (error: any | undefined, code: string) => void): string;
/**
* Set the prefix for code block classes.
*/
langPrefix?: string;
/**
* Mangle autolinks (<email@domain.com>).
*/
mangle?: 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;
/**
* Type: object Default: new Renderer()
*
* An object containing functions to render tokens to HTML.
*/
renderer?: Renderer;
/**
* Sanitize the output. Ignore any HTML that has been input.
*/
sanitize?: boolean;
/**
* Optionally sanitize found HTML with a sanitizer function.
*/
sanitizer?(html: string): string;
/**
* Shows an HTML error message when rendering fails.
*/
silent?: boolean;
/**
* Use smarter list behavior than the original markdown. May eventually be default with the old behavior moved into pedantic.
*/
smartLists?: boolean;
/**
* Use "smart" typograhic punctuation for things like quotes and dashes.
*/
smartypants?: boolean;
/**
* Enable GFM tables. This option requires the gfm option to be true.
*/
tables?: boolean;
/**
* Generate closing slash for self-closing tags (<br/> instead of <br>)
*/
xhtml?: boolean;
}
}

File diff suppressed because it is too large Load Diff

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.

View File

@@ -0,0 +1,78 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { regExpFlags } from 'vs/base/common/strings';
import { URI, UriComponents } from 'vs/base/common/uri';
export function stringify(obj: any): string {
return JSON.stringify(obj, replacer);
}
export function parse(text: string): any {
let data = JSON.parse(text);
data = revive(data);
return data;
}
export 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: value.source,
flags: regExpFlags(value),
};
}
return value;
}
type Deserialize<T> = T extends UriComponents ? URI
: T extends object
? Revived<T>
: T;
export type Revived<T> = { [K in keyof T]: Deserialize<T[K]> };
export function revive<T = any>(obj: any, depth = 0): Revived<T> {
if (!obj || depth > 200) {
return obj;
}
if (typeof obj === 'object') {
switch ((<MarshalledObject>obj).$mid) {
case 1: return <any>URI.revive(obj);
case 2: return <any>new RegExp(obj.source, obj.flags);
}
if (
obj instanceof VSBuffer
|| obj instanceof Uint8Array
) {
return <any>obj;
}
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; ++i) {
obj[i] = revive(obj[i], depth + 1);
}
} else {
// walk object
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
obj[key] = revive(obj[key], depth + 1);
}
}
}
}
return obj;
}

View File

@@ -0,0 +1,318 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { basename, posix, extname } from 'vs/base/common/path';
import { startsWithUTF8BOM } from 'vs/base/common/strings';
import { match } from 'vs/base/common/glob';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { DataUri } from 'vs/base/common/resources';
export const MIME_TEXT = 'text/plain';
export const MIME_BINARY = 'application/octet-stream';
export const MIME_UNKNOWN = 'application/unknown';
export interface ITextMimeAssociation {
readonly id: string;
readonly mime: string;
readonly filename?: string;
readonly extension?: string;
readonly filepattern?: string;
readonly firstline?: RegExp;
readonly userConfigured?: boolean;
}
interface ITextMimeAssociationItem extends ITextMimeAssociation {
readonly filenameLowercase?: string;
readonly extensionLowercase?: string;
readonly filepatternLowercase?: string;
readonly filepatternOnPath?: boolean;
}
let registeredAssociations: ITextMimeAssociationItem[] = [];
let nonUserRegisteredAssociations: ITextMimeAssociationItem[] = [];
let userRegisteredAssociations: ITextMimeAssociationItem[] = [];
/**
* Associate a text mime to the registry.
*/
export function registerTextMime(association: ITextMimeAssociation, warnOnOverwrite = false): 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 (warnOnOverwrite && !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() : undefined,
extensionLowercase: association.extension ? association.extension.toLowerCase() : undefined,
filepatternLowercase: association.filepattern ? association.filepattern.toLowerCase() : undefined,
filepatternOnPath: association.filepattern ? association.filepattern.indexOf(posix.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(resource: URI | null, firstLine?: string): string[] {
let path: string | undefined;
if (resource) {
switch (resource.scheme) {
case Schemas.file:
path = resource.fsPath;
break;
case Schemas.data:
const metadata = DataUri.parseMetaData(resource);
path = metadata.get(DataUri.META_DATA_LABEL);
break;
default:
path = resource.path;
}
}
if (!path) {
return [MIME_UNKNOWN];
}
path = path.toLowerCase();
const filename = basename(path);
// 1.) User configured mappings have highest priority
const configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations);
if (configuredMime) {
return [configuredMime, MIME_TEXT];
}
// 2.) Registered mappings have middle priority
const registeredMime = guessMimeTypeByPath(path, filename, nonUserRegisteredAssociations);
if (registeredMime) {
return [registeredMime, MIME_TEXT];
}
// 3.) Firstline has lowest priority
if (firstLine) {
const firstlineMime = guessMimeTypeByFirstline(firstLine);
if (firstlineMime) {
return [firstlineMime, MIME_TEXT];
}
}
return [MIME_UNKNOWN];
}
function guessMimeTypeByPath(path: string, filename: string, associations: ITextMimeAssociationItem[]): string | null {
let filenameMatch: ITextMimeAssociationItem | null = null;
let patternMatch: ITextMimeAssociationItem | null = null;
let extensionMatch: ITextMimeAssociationItem | null = null;
// 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--) {
const 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) {
const 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 (filename.endsWith(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 | null {
if (startsWithUTF8BOM(firstLine)) {
firstLine = firstLine.substr(1);
}
if (firstLine.length > 0) {
// 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 = registeredAssociations.length - 1; i >= 0; i--) {
const association = registeredAssociations[i];
if (!association.firstline) {
continue;
}
const matches = firstLine.match(association.firstline);
if (matches && matches.length > 0) {
return association.mime;
}
}
}
return null;
}
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]);
}
interface MapExtToMediaMimes {
[index: string]: string;
}
// Known media mimes that we can handle
const mapExtToMediaMimes: MapExtToMediaMimes = {
'.aac': 'audio/x-aac',
'.avi': 'video/x-msvideo',
'.bmp': 'image/bmp',
'.flv': 'video/x-flv',
'.gif': 'image/gif',
'.ico': 'image/x-icon',
'.jpe': 'image/jpg',
'.jpeg': 'image/jpg',
'.jpg': 'image/jpg',
'.m1v': 'video/mpeg',
'.m2a': 'audio/mpeg',
'.m2v': 'video/mpeg',
'.m3a': 'audio/mpeg',
'.mid': 'audio/midi',
'.midi': 'audio/midi',
'.mk3d': 'video/x-matroska',
'.mks': 'video/x-matroska',
'.mkv': 'video/x-matroska',
'.mov': 'video/quicktime',
'.movie': 'video/x-sgi-movie',
'.mp2': 'audio/mpeg',
'.mp2a': 'audio/mpeg',
'.mp3': 'audio/mpeg',
'.mp4': 'video/mp4',
'.mp4a': 'audio/mp4',
'.mp4v': 'video/mp4',
'.mpe': 'video/mpeg',
'.mpeg': 'video/mpeg',
'.mpg': 'video/mpeg',
'.mpg4': 'video/mp4',
'.mpga': 'audio/mpeg',
'.oga': 'audio/ogg',
'.ogg': 'audio/ogg',
'.ogv': 'video/ogg',
'.png': 'image/png',
'.psd': 'image/vnd.adobe.photoshop',
'.qt': 'video/quicktime',
'.spx': 'audio/ogg',
'.svg': 'image/svg+xml',
'.tga': 'image/x-tga',
'.tif': 'image/tiff',
'.tiff': 'image/tiff',
'.wav': 'audio/x-wav',
'.webm': 'video/webm',
'.webp': 'image/webp',
'.wma': 'audio/x-ms-wma',
'.wmv': 'video/x-ms-wmv',
'.woff': 'application/font-woff',
};
export function getMediaMime(path: string): string | undefined {
const ext = extname(path);
return mapExtToMediaMimes[ext.toLowerCase()];
}
export function getExtensionForMimeType(mimeType: string): string | undefined {
for (const extension in mapExtToMediaMimes) {
if (mapExtToMediaMimes[extension] === mimeType) {
return extension;
}
}
return undefined;
}

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface INavigator<T> {
current(): T | null;
previous(): T | null;
first(): T | null;
last(): T | null;
next(): T | null;
}
export class ArrayNavigator<T> implements INavigator<T> {
constructor(
private readonly items: readonly T[],
protected start: number = 0,
protected end: number = items.length,
protected index = start - 1
) { }
current(): T | null {
if (this.index === this.start - 1 || this.index === this.end) {
return null;
}
return this.items[this.index];
}
next(): T | null {
this.index = Math.min(this.index + 1, this.end);
return this.current();
}
previous(): T | null {
this.index = Math.max(this.index - 1, this.start - 1);
return this.current();
}
first(): T | null {
this.index = this.start;
return this.current();
}
last(): T | null {
this.index = this.end - 1;
return this.current();
}
}

View File

@@ -0,0 +1,174 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import * as platform from 'vs/base/common/platform';
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 = 'inmemory';
/**
* A schema that is used for setting files
*/
export const vscode = 'vscode';
/**
* A schema that is used for internal private files
*/
export const internal = 'private';
/**
* A walk-through document.
*/
export const walkThrough = 'walkThrough';
/**
* An embedded code snippet.
*/
export const walkThroughSnippet = 'walkThroughSnippet';
export const http = 'http';
export const https = 'https';
export const file = 'file';
export const mailto = 'mailto';
export const untitled = 'untitled';
export const data = 'data';
export const command = 'command';
export const vscodeRemote = 'vscode-remote';
export const vscodeRemoteResource = 'vscode-remote-resource';
export const userData = 'vscode-userdata';
export const vscodeCustomEditor = 'vscode-custom-editor';
export const vscodeNotebook = 'vscode-notebook';
export const vscodeNotebookCell = 'vscode-notebook-cell';
export const vscodeSettings = 'vscode-settings';
export const webviewPanel = 'webview-panel';
/**
* Scheme used for loading the wrapper html and script in webviews.
*/
export const vscodeWebview = 'vscode-webview';
/**
* Scheme used for loading resources inside of webviews.
*/
export const vscodeWebviewResource = 'vscode-webview-resource';
/**
* Scheme used for extension pages
*/
export const extension = 'extension';
}
class RemoteAuthoritiesImpl {
private readonly _hosts: { [authority: string]: string | undefined; } = Object.create(null);
private readonly _ports: { [authority: string]: number | undefined; } = Object.create(null);
private readonly _connectionTokens: { [authority: string]: string | undefined; } = Object.create(null);
private _preferredWebSchema: 'http' | 'https' = 'http';
private _delegate: ((uri: URI) => URI) | null = null;
setPreferredWebSchema(schema: 'http' | 'https') {
this._preferredWebSchema = schema;
}
setDelegate(delegate: (uri: URI) => URI): void {
this._delegate = delegate;
}
set(authority: string, host: string, port: number): void {
this._hosts[authority] = host;
this._ports[authority] = port;
}
setConnectionToken(authority: string, connectionToken: string): void {
this._connectionTokens[authority] = connectionToken;
}
rewrite(uri: URI): URI {
if (this._delegate) {
return this._delegate(uri);
}
const authority = uri.authority;
let host = this._hosts[authority];
if (host && host.indexOf(':') !== -1) {
host = `[${host}]`;
}
const port = this._ports[authority];
const connectionToken = this._connectionTokens[authority];
let query = `path=${encodeURIComponent(uri.path)}`;
if (typeof connectionToken === 'string') {
query += `&tkn=${encodeURIComponent(connectionToken)}`;
}
return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
authority: `${host}:${port}`,
path: `/vscode-remote-resource`,
query
});
}
}
export const RemoteAuthorities = new RemoteAuthoritiesImpl();
class FileAccessImpl {
/**
* Returns a URI to use in contexts where the browser is responsible
* for loading (e.g. fetch()) or when used within the DOM.
*
* **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context.
*/
asBrowserUri(uri: URI): URI;
asBrowserUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI;
asBrowserUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
const uri = this.toUri(uriOrModule, moduleIdToUrl);
if (uri.scheme === Schemas.vscodeRemote) {
return RemoteAuthorities.rewrite(uri);
}
return uri;
}
/**
* Returns the `file` URI to use in contexts where node.js
* is responsible for loading.
*/
asFileUri(uri: URI): URI;
asFileUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI;
asFileUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
const uri = this.toUri(uriOrModule, moduleIdToUrl);
return uri;
}
private toUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
if (URI.isUri(uriOrModule)) {
return uriOrModule;
}
return URI.parse(moduleIdToUrl!.toUrl(uriOrModule));
}
}
export const FileAccess = new FileAccessImpl();

View File

@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { LRUCache } from 'vs/base/common/map';
/**
* 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 (String.prototype as any /* standalone editor compilation */).normalize === 'function';
const nfcCache = new LRUCache<string, string>(10000); // bounded to 10000 elements
export function normalizeNFC(str: string): string {
return normalize(str, 'NFC', nfcCache);
}
const nfdCache = new LRUCache<string, 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: LRUCache<string, 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(form);
} else {
res = str;
}
// Use the cache for fast lookup
normalizedCache.set(str, res);
return res;
}
export const removeAccents: (str: string) => string = (function () {
if (!canNormalize) {
// no ES6 features...
return function (str: string) { return str; };
} else {
// transform into NFD form and remove accents
// see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463
const regex = /[\u0300-\u036f]/g;
return function (str: string) {
return normalizeNFD(str).replace(regex, '');
};
}
})();

View File

@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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;
}
export class Counter {
private _next = 0;
getNext(): number {
return this._next++;
}
}
export class MovingAverage {
private _n = 1;
private _val = 0;
update(value: number): this {
this._val = this._val + (value - this._val) / this._n;
this._n += 1;
return this;
}
get value(): number {
return this._val;
}
}

View File

@@ -0,0 +1,228 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isObject, isUndefinedOrNull, isArray } from 'vs/base/common/types';
export function deepClone<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: any = Array.isArray(obj) ? [] : {};
Object.keys(<any>obj).forEach((key: string) => {
if ((<any>obj)[key] && typeof (<any>obj)[key] === 'object') {
result[key] = deepClone((<any>obj)[key]);
} else {
result[key] = (<any>obj)[key];
}
});
return result;
}
export function deepFreeze<T>(obj: T): T {
if (!obj || typeof obj !== 'object') {
return obj;
}
const stack: any[] = [obj];
while (stack.length > 0) {
const obj = stack.shift();
Object.freeze(obj);
for (const key in obj) {
if (_hasOwnProperty.call(obj, key)) {
const prop = obj[key];
if (typeof prop === 'object' && !Object.isFrozen(prop)) {
stack.push(prop);
}
}
}
}
return obj;
}
const _hasOwnProperty = Object.prototype.hasOwnProperty;
export function cloneAndChange(obj: any, changer: (orig: any) => any): any {
return _cloneAndChange(obj, changer, new Set());
}
function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set<any>): any {
if (isUndefinedOrNull(obj)) {
return obj;
}
const changed = changer(obj);
if (typeof changed !== 'undefined') {
return changed;
}
if (isArray(obj)) {
const r1: any[] = [];
for (const e of obj) {
r1.push(_cloneAndChange(e, changer, seen));
}
return r1;
}
if (isObject(obj)) {
if (seen.has(obj)) {
throw new Error('Cannot clone recursive data-structure');
}
seen.add(obj);
const r2 = {};
for (let i2 in obj) {
if (_hasOwnProperty.call(obj, i2)) {
(r2 as any)[i2] = _cloneAndChange(obj[i2], changer, seen);
}
}
seen.delete(obj);
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 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;
}
/**
* 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 = new Set<any>();
return JSON.stringify(obj, (key, value) => {
if (isObject(value) || Array.isArray(value)) {
if (seen.has(value)) {
return '[Circular]';
} else {
seen.add(value);
}
}
return value;
});
}
export function getOrDefault<T, R>(obj: T, fn: (obj: T) => R | undefined, defaultValue: R): R {
const result = fn(obj);
return typeof result === 'undefined' ? defaultValue : result;
}
type obj = { [key: string]: any; };
/**
* 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 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;
}
export function getCaseInsensitive(target: obj, key: string): any {
const lowercaseKey = key.toLowerCase();
const equivalentKey = Object.keys(target).find(k => k.toLowerCase() === lowercaseKey);
return equivalentKey ? target[equivalentKey] : target[key];
}

View File

@@ -0,0 +1,204 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isArray } from 'vs/base/common/types';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
import { range } from 'vs/base/common/arrays';
/**
* A Pager is a stateless abstraction over a paged collection.
*/
export interface IPager<T> {
firstPage: T[];
total: number;
pageSize: number;
getPage(pageIndex: number, cancellationToken: CancellationToken): Promise<T[]>;
}
interface IPage<T> {
isResolved: boolean;
promise: Promise<void> | null;
cts: CancellationTokenSource | null;
promiseIndexes: Set<number>;
elements: T[];
}
function createPage<T>(elements?: T[]): IPage<T> {
return {
isResolved: !!elements,
promise: null,
cts: null,
promiseIndexes: new Set<number>(),
elements: elements || []
};
}
/**
* 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, cancellationToken: CancellationToken): Promise<T>;
}
export function singlePagePager<T>(elements: T[]): IPager<T> {
return {
firstPage: elements,
total: elements.length,
pageSize: elements.length,
getPage: (pageIndex: number, cancellationToken: CancellationToken): Promise<T[]> => {
return Promise.resolve(elements);
}
};
}
export class PagedModel<T> implements IPagedModel<T> {
private pager: IPager<T>;
private pages: IPage<T>[] = [];
get length(): number { return this.pager.total; }
constructor(arg: IPager<T> | T[]) {
this.pager = isArray(arg) ? singlePagePager<T>(arg) : arg;
const totalPages = Math.ceil(this.pager.total / this.pager.pageSize);
this.pages = [
createPage(this.pager.firstPage.slice()),
...range(totalPages - 1).map(() => createPage<T>())
];
}
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, cancellationToken: CancellationToken): Promise<T> {
if (cancellationToken.isCancellationRequested) {
return Promise.reject(canceled());
}
const pageIndex = Math.floor(index / this.pager.pageSize);
const indexInPage = index % this.pager.pageSize;
const page = this.pages[pageIndex];
if (page.isResolved) {
return Promise.resolve(page.elements[indexInPage]);
}
if (!page.promise) {
page.cts = new CancellationTokenSource();
page.promise = this.pager.getPage(pageIndex, page.cts.token)
.then(elements => {
page.elements = elements;
page.isResolved = true;
page.promise = null;
page.cts = null;
}, err => {
page.isResolved = false;
page.promise = null;
page.cts = null;
return Promise.reject(err);
});
}
cancellationToken.onCancellationRequested(() => {
if (!page.cts) {
return;
}
page.promiseIndexes.delete(index);
if (page.promiseIndexes.size === 0) {
page.cts.cancel();
}
});
page.promiseIndexes.add(index);
return page.promise.then(() => page.elements[indexInPage]);
}
}
export class DelayedPagedModel<T> implements IPagedModel<T> {
get length(): number { return this.model.length; }
constructor(private model: IPagedModel<T>, private timeout: number = 500) { }
isResolved(index: number): boolean {
return this.model.isResolved(index);
}
get(index: number): T {
return this.model.get(index);
}
resolve(index: number, cancellationToken: CancellationToken): Promise<T> {
return new Promise((c, e) => {
if (cancellationToken.isCancellationRequested) {
return e(canceled());
}
const timer = setTimeout(() => {
if (cancellationToken.isCancellationRequested) {
return e(canceled());
}
timeoutCancellation.dispose();
this.model.resolve(index, cancellationToken).then(c, e);
}, this.timeout);
const timeoutCancellation = cancellationToken.onCancellationRequested(() => {
clearTimeout(timer);
timeoutCancellation.dispose();
e(canceled());
});
});
}
}
/**
* 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, token) => pager.getPage(pageIndex, token).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, token): Promise<T[]> {
return Promise.all([one.getPage(pageIndex, token), other.getPage(pageIndex, token)])
.then(([onePage, otherPage]) => [...onePage, ...otherPage]);
}
};
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface PerformanceEntry {
readonly name: string;
readonly startTime: number;
}
export function mark(name: string): void;
/**
* All entries filtered by type and sorted by `startTime`.
*/
export function getEntries(): PerformanceEntry[];
export function getDuration(from: string, to: string): number;
type ExportData = any[];
export function importEntries(data: ExportData): void;
export function exportEntries(): ExportData;

View File

@@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
//@ts-check
function _factory(sharedObj) {
sharedObj.MonacoPerformanceMarks = sharedObj.MonacoPerformanceMarks || [];
const _dataLen = 2;
const _timeStamp = typeof console.timeStamp === 'function' ? console.timeStamp.bind(console) : () => { };
function importEntries(entries) {
sharedObj.MonacoPerformanceMarks.splice(0, 0, ...entries);
}
function exportEntries() {
return sharedObj.MonacoPerformanceMarks.slice(0);
}
function getEntries() {
const result = [];
const entries = sharedObj.MonacoPerformanceMarks;
for (let i = 0; i < entries.length; i += _dataLen) {
result.push({
name: entries[i],
startTime: entries[i + 1],
});
}
return result;
}
function getDuration(from, to) {
const entries = sharedObj.MonacoPerformanceMarks;
let target = to;
let endIndex = 0;
for (let i = entries.length - _dataLen; i >= 0; i -= _dataLen) {
if (entries[i] === target) {
if (target === to) {
// found `to` (end of interval)
endIndex = i;
target = from;
} else {
// found `from` (start of interval)
return entries[endIndex + 1] - entries[i + 1];
}
}
}
return 0;
}
function mark(name) {
sharedObj.MonacoPerformanceMarks.push(name, Date.now());
_timeStamp(name);
}
const exports = {
mark: mark,
getEntries: getEntries,
getDuration: getDuration,
importEntries: importEntries,
exportEntries: exportEntries
};
return exports;
}
// This module can be loaded in an amd and commonjs-context.
// Because we want both instances to use the same perf-data
// we store them globally
// eslint-disable-next-line no-var
var sharedObj;
if (typeof global === 'object') {
// nodejs
sharedObj = global;
} else if (typeof self === 'object') {
// browser
sharedObj = self;
} else {
sharedObj = {};
}
if (typeof define === 'function') {
// amd
define([], function () { return _factory(sharedObj); });
} else if (typeof module === 'object' && typeof module.exports === 'object') {
// commonjs
module.exports = _factory(sharedObj);
} else {
sharedObj.perf = _factory(sharedObj);
}

View File

@@ -0,0 +1,244 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const LANGUAGE_DEFAULT = 'en';
let _isWindows = false;
let _isMacintosh = false;
let _isLinux = false;
let _isNative = false;
let _isWeb = false;
let _isIOS = false;
let _locale: string | undefined = undefined;
let _language: string = LANGUAGE_DEFAULT;
let _translationsConfigFile: string | undefined = undefined;
let _userAgent: string | undefined = undefined;
interface NLSConfig {
locale: string;
availableLanguages: { [key: string]: string; };
_translationsConfigFile: string;
}
export interface IProcessEnvironment {
[key: string]: string;
}
export interface INodeProcess {
platform: 'win32' | 'linux' | 'darwin';
env: IProcessEnvironment;
nextTick: Function;
versions?: {
electron?: string;
};
type?: string;
getuid(): number;
cwd(): string;
}
declare const process: INodeProcess;
declare const global: any;
interface INavigator {
userAgent: string;
language: string;
maxTouchPoints?: number;
}
declare const navigator: INavigator;
declare const self: any;
const _globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {} as any);
let nodeProcess: INodeProcess | undefined = undefined;
if (typeof process !== 'undefined') {
// Native environment (non-sandboxed)
nodeProcess = process;
} else if (typeof _globals.vscode !== 'undefined') {
// Native envionment (sandboxed)
nodeProcess = _globals.vscode.process;
}
const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer';
// Web environment
if (typeof navigator === 'object' && !isElectronRenderer) {
_userAgent = navigator.userAgent;
_isWindows = _userAgent.indexOf('Windows') >= 0;
_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
_isIOS = (_userAgent.indexOf('Macintosh') >= 0 || _userAgent.indexOf('iPad') >= 0 || _userAgent.indexOf('iPhone') >= 0) && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
_isLinux = _userAgent.indexOf('Linux') >= 0;
_isWeb = true;
_locale = navigator.language;
_language = _locale;
}
// Native environment
else if (typeof nodeProcess === 'object') {
_isWindows = (nodeProcess.platform === 'win32');
_isMacintosh = (nodeProcess.platform === 'darwin');
_isLinux = (nodeProcess.platform === 'linux');
_locale = LANGUAGE_DEFAULT;
_language = LANGUAGE_DEFAULT;
const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG'];
if (rawNlsConfig) {
try {
const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
const resolved = nlsConfig.availableLanguages['*'];
_locale = nlsConfig.locale;
// VSCode's default language is 'en'
_language = resolved ? resolved : LANGUAGE_DEFAULT;
_translationsConfigFile = nlsConfig._translationsConfigFile;
} catch (e) {
}
}
_isNative = true;
}
// Unknown environment
else {
console.error('Unable to resolve platform.');
}
export const enum Platform {
Web,
Mac,
Linux,
Windows
}
export function PlatformToString(platform: Platform) {
switch (platform) {
case Platform.Web: return 'Web';
case Platform.Mac: return 'Mac';
case Platform.Linux: return 'Linux';
case Platform.Windows: return 'Windows';
}
}
let _platform: Platform = Platform.Web;
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 isNative = _isNative;
export const isWeb = _isWeb;
export const isIOS = _isIOS;
export const platform = _platform;
export const userAgent = _userAgent;
export function isRootUser(): boolean {
return !!nodeProcess && !_isWindows && (nodeProcess.getuid() === 0);
}
/**
* 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;
export namespace Language {
export function value(): string {
return language;
}
export function isDefaultVariant(): boolean {
if (language.length === 2) {
return language === 'en';
} else if (language.length >= 3) {
return language[0] === 'e' && language[1] === 'n' && language[2] === '-';
} else {
return false;
}
}
export function isDefault(): boolean {
return language === 'en';
}
}
/**
* 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;
/**
* The translatios that are available through language packs.
*/
export const translationsConfigFile = _translationsConfigFile;
export const globals: any = _globals;
interface ISetImmediate {
(callback: (...args: any[]) => void): void;
}
export const setImmediate: ISetImmediate = (function defineSetImmediate() {
if (globals.setImmediate) {
return globals.setImmediate.bind(globals);
}
if (typeof globals.postMessage === 'function' && !globals.importScripts) {
interface IQueueElement {
id: number;
callback: () => void;
}
let pending: IQueueElement[] = [];
globals.addEventListener('message', (e: MessageEvent) => {
if (e.data && e.data.vscodeSetImmediateId) {
for (let i = 0, len = pending.length; i < len; i++) {
const candidate = pending[i];
if (candidate.id === e.data.vscodeSetImmediateId) {
pending.splice(i, 1);
candidate.callback();
return;
}
}
}
});
let lastId = 0;
return (callback: () => void) => {
const myId = ++lastId;
pending.push({
id: myId,
callback: callback
});
globals.postMessage({ vscodeSetImmediateId: myId }, '*');
};
}
if (nodeProcess) {
return nodeProcess.nextTick.bind(nodeProcess);
}
const _promise = Promise.resolve();
return (callback: (...args: any[]) => void) => _promise.then(callback);
})();
export const enum OperatingSystem {
Windows = 1,
Macintosh = 2,
Linux = 3
}
export const OS = (_isMacintosh || _isIOS ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux));
let _isLittleEndian = true;
let _isLittleEndianComputed = false;
export function isLittleEndian(): boolean {
if (!_isLittleEndianComputed) {
_isLittleEndianComputed = true;
const test = new Uint8Array(2);
test[0] = 1;
test[1] = 2;
const view = new Uint16Array(test.buffer);
_isLittleEndian = (view[0] === (2 << 8) + 1);
}
return _isLittleEndian;
}

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isWindows, isMacintosh, setImmediate, globals, INodeProcess } from 'vs/base/common/platform';
declare const process: INodeProcess;
let safeProcess: INodeProcess;
// Native node.js environment
if (typeof process !== 'undefined') {
safeProcess = process;
}
// Native sandbox environment
else if (typeof globals.vscode !== 'undefined') {
safeProcess = globals.vscode.process;
}
// Web environment
else {
safeProcess = {
// Supported
get platform(): 'win32' | 'linux' | 'darwin' { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; },
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); },
// Unsupported
get env() { return Object.create(null); },
cwd(): string { return '/'; },
getuid(): number { return -1; }
};
}
export const cwd = safeProcess.cwd;
export const env = safeProcess.env;
export const platform = safeProcess.platform;
export const nextTick = safeProcess.nextTick;

View File

@@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IProcessEnvironment } from 'vs/base/common/platform';
/**
* 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 const 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 const enum TerminateResponseCode {
Success = 0,
Unknown = 1,
AccessDenied = 2,
ProcessNotFound = 3,
}
export interface ProcessItem {
name: string;
cmd: string;
pid: number;
ppid: number;
load: number;
mem: number;
children?: ProcessItem[];
}
/**
* Sanitizes a VS Code process environment by removing all Electron/VS Code-related values.
*/
export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve: string[]): void {
const set = preserve.reduce((set, key) => {
set[key] = true;
return set;
}, {} as Record<string, boolean>);
const keysToRemove = [
/^ELECTRON_.+$/,
/^GOOGLE_API_KEY$/,
/^VSCODE_.+$/,
/^SNAP(|_.*)$/,
/^GDK_PIXBUF_.+$/,
];
const envKeys = Object.keys(env);
envKeys
.filter(key => !set[key])
.forEach(envKey => {
for (let i = 0; i < keysToRemove.length; i++) {
if (envKey.search(keysToRemove[i]) !== -1) {
delete env[envKey];
break;
}
}
});
}

View File

@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IRange {
start: number;
end: number;
}
export interface IRangedGroup {
range: IRange;
size: number;
}
export namespace Range {
/**
* Returns the intersection between two ranges as a range itself.
* Returns `{ start: 0, end: 0 }` if the intersection is empty.
*/
export function intersect(one: IRange, other: IRange): IRange {
if (one.start >= other.end || other.start >= one.end) {
return { start: 0, end: 0 };
}
const start = Math.max(one.start, other.start);
const end = Math.min(one.end, other.end);
if (end - start <= 0) {
return { start: 0, end: 0 };
}
return { start, end };
}
export function isEmpty(range: IRange): boolean {
return range.end - range.start <= 0;
}
export function intersects(one: IRange, other: IRange): boolean {
return !isEmpty(intersect(one, other));
}
export function relativeComplement(one: IRange, other: IRange): IRange[] {
const result: IRange[] = [];
const first = { start: one.start, end: Math.min(other.start, one.end) };
const second = { start: Math.max(other.end, one.start), end: one.end };
if (!isEmpty(first)) {
result.push(first);
}
if (!isEmpty(second)) {
result.push(second);
}
return result;
}
}

View File

@@ -0,0 +1,187 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { memoize } from 'vs/base/common/decorators';
import * as paths from 'vs/base/common/path';
import { relativePath, joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { PathIterator } from 'vs/base/common/map';
export interface IResourceNode<T, C = void> {
readonly uri: URI;
readonly relativePath: string;
readonly name: string;
readonly element: T | undefined;
readonly children: Iterable<IResourceNode<T, C>>;
readonly childrenCount: number;
readonly parent: IResourceNode<T, C> | undefined;
readonly context: C;
get(childName: string): IResourceNode<T, C> | undefined;
}
class Node<T, C> implements IResourceNode<T, C> {
private _children = new Map<string, Node<T, C>>();
get childrenCount(): number {
return this._children.size;
}
get children(): Iterable<Node<T, C>> {
return this._children.values();
}
@memoize
get name(): string {
return paths.posix.basename(this.relativePath);
}
constructor(
readonly uri: URI,
readonly relativePath: string,
readonly context: C,
public element: T | undefined = undefined,
readonly parent: IResourceNode<T, C> | undefined = undefined
) { }
get(path: string): Node<T, C> | undefined {
return this._children.get(path);
}
set(path: string, child: Node<T, C>): void {
this._children.set(path, child);
}
delete(path: string): void {
this._children.delete(path);
}
clear(): void {
this._children.clear();
}
}
function collect<T, C>(node: IResourceNode<T, C>, result: T[]): T[] {
if (typeof node.element !== 'undefined') {
result.push(node.element);
}
for (const child of node.children) {
collect(child, result);
}
return result;
}
export class ResourceTree<T extends NonNullable<any>, C> {
readonly root: Node<T, C>;
static getRoot<T, C>(node: IResourceNode<T, C>): IResourceNode<T, C> {
while (node.parent) {
node = node.parent;
}
return node;
}
static collect<T, C>(node: IResourceNode<T, C>): T[] {
return collect(node, []);
}
static isResourceNode<T, C>(obj: any): obj is IResourceNode<T, C> {
return obj instanceof Node;
}
constructor(context: C, rootURI: URI = URI.file('/')) {
this.root = new Node(rootURI, '', context);
}
add(uri: URI, element: T): void {
const key = relativePath(this.root.uri, uri) || uri.fsPath;
const iterator = new PathIterator(false).reset(key);
let node = this.root;
let path = '';
while (true) {
const name = iterator.value();
path = path + '/' + name;
let child = node.get(name);
if (!child) {
child = new Node(
joinPath(this.root.uri, path),
path,
this.root.context,
iterator.hasNext() ? undefined : element,
node
);
node.set(name, child);
} else if (!iterator.hasNext()) {
child.element = element;
}
node = child;
if (!iterator.hasNext()) {
return;
}
iterator.next();
}
}
delete(uri: URI): T | undefined {
const key = relativePath(this.root.uri, uri) || uri.fsPath;
const iterator = new PathIterator(false).reset(key);
return this._delete(this.root, iterator);
}
private _delete(node: Node<T, C>, iterator: PathIterator): T | undefined {
const name = iterator.value();
const child = node.get(name);
if (!child) {
return undefined;
}
if (iterator.hasNext()) {
const result = this._delete(child, iterator.next());
if (typeof result !== 'undefined' && child.childrenCount === 0) {
node.delete(name);
}
return result;
}
node.delete(name);
return child.element;
}
clear(): void {
this.root.clear();
}
getNode(uri: URI): IResourceNode<T, C> | undefined {
const key = relativePath(this.root.uri, uri) || uri.fsPath;
const iterator = new PathIterator(false).reset(key);
let node = this.root;
while (true) {
const name = iterator.value();
const child = node.get(name);
if (!child || !iterator.hasNext()) {
return child;
}
node = child;
iterator.next();
}
}
}

View File

@@ -0,0 +1,468 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as extpath from 'vs/base/common/extpath';
import * as paths from 'vs/base/common/path';
import { URI, uriToFsPath } from 'vs/base/common/uri';
import { equalsIgnoreCase, compare as strCompare } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { CharCode } from 'vs/base/common/charCode';
import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
import { TernarySearchTree } from 'vs/base/common/map';
export function originalFSPath(uri: URI): string {
return uriToFsPath(uri, true);
}
//#region IExtUri
export interface IExtUri {
// --- identity
/**
* Compares two uris.
*
* @param uri1 Uri
* @param uri2 Uri
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
compare(uri1: URI, uri2: URI, ignoreFragment?: boolean): number;
/**
* Tests whether two uris are equal
*
* @param uri1 Uri
* @param uri2 Uri
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment?: boolean): boolean;
/**
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
*
* @param base A uri which is "longer"
* @param parentCandidate A uri which is "shorter" then `base`
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment?: boolean): boolean;
/**
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
* @see ResourceMap
* @param uri Uri
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
getComparisonKey(uri: URI, ignoreFragment?: boolean): string;
// --- path math
basenameOrAuthority(resource: URI): string;
/**
* Returns the basename of the path component of an uri.
* @param resource
*/
basename(resource: URI): string;
/**
* Returns the extension of the path component of an uri.
* @param resource
*/
extname(resource: URI): string;
/**
* Return a URI representing the directory of a URI path.
*
* @param resource The input URI.
* @returns The URI representing the directory of the input URI.
*/
dirname(resource: URI): URI;
/**
* Join a URI path with path fragments and normalizes the resulting path.
*
* @param resource The input URI.
* @param pathFragment The path fragment to add to the URI path.
* @returns The resulting URI.
*/
joinPath(resource: URI, ...pathFragment: string[]): URI
/**
* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
*
* @param resource The URI to normalize the path.
* @returns The URI with the normalized path.
*/
normalizePath(resource: URI): URI;
/**
*
* @param from
* @param to
*/
relativePath(from: URI, to: URI): string | undefined;
/**
* Resolves an absolute or relative path against a base URI.
* The path can be relative or absolute posix or a Windows path
*/
resolvePath(base: URI, path: string): URI;
// --- misc
/**
* Returns true if the URI path is absolute.
*/
isAbsolutePath(resource: URI): boolean;
/**
* Tests whether the two authorities are the same
*/
isEqualAuthority(a1: string, a2: string): boolean;
/**
* Returns true if the URI path has a trailing path separator
*/
hasTrailingPathSeparator(resource: URI, sep?: string): boolean;
/**
* Removes a trailing path separator, if there's one.
* Important: Doesn't remove the first slash, it would make the URI invalid
*/
removeTrailingPathSeparator(resource: URI, sep?: string): URI;
/**
* Adds a trailing path separator to the URI if there isn't one already.
* For example, c:\ would be unchanged, but c:\users would become c:\users\
*/
addTrailingPathSeparator(resource: URI, sep?: string): URI;
}
export class ExtUri implements IExtUri {
constructor(private _ignorePathCasing: (uri: URI) => boolean) { }
compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number {
if (uri1 === uri2) {
return 0;
}
return strCompare(this.getComparisonKey(uri1, ignoreFragment), this.getComparisonKey(uri2, ignoreFragment));
}
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean {
if (uri1 === uri2) {
return true;
}
if (!uri1 || !uri2) {
return false;
}
return this.getComparisonKey(uri1, ignoreFragment) === this.getComparisonKey(uri2, ignoreFragment);
}
getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
return uri.with({
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
fragment: ignoreFragment ? null : undefined
}).toString();
}
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {
if (base.scheme === parentCandidate.scheme) {
if (base.scheme === Schemas.file) {
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), this._ignorePathCasing(base)) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
}
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
return extpath.isEqualOrParent(base.path, parentCandidate.path, this._ignorePathCasing(base), '/') && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
}
}
return false;
}
// --- path math
joinPath(resource: URI, ...pathFragment: string[]): URI {
return URI.joinPath(resource, ...pathFragment);
}
basenameOrAuthority(resource: URI): string {
return basename(resource) || resource.authority;
}
basename(resource: URI): string {
return paths.posix.basename(resource.path);
}
extname(resource: URI): string {
return paths.posix.extname(resource.path);
}
dirname(resource: URI): URI {
if (resource.path.length === 0) {
return resource;
}
let dirname;
if (resource.scheme === Schemas.file) {
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
} else {
dirname = paths.posix.dirname(resource.path);
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
console.error(`dirname("${resource.toString})) resulted in a relative path`);
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
}
}
return resource.with({
path: dirname
});
}
normalizePath(resource: URI): URI {
if (!resource.path.length) {
return resource;
}
let normalizedPath: string;
if (resource.scheme === Schemas.file) {
normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
} else {
normalizedPath = paths.posix.normalize(resource.path);
}
return resource.with({
path: normalizedPath
});
}
relativePath(from: URI, to: URI): string | undefined {
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
return undefined;
}
if (from.scheme === Schemas.file) {
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
}
let fromPath = from.path || '/', toPath = to.path || '/';
if (this._ignorePathCasing(from)) {
// make casing of fromPath match toPath
let i = 0;
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
break;
}
}
}
fromPath = toPath.substr(0, i) + fromPath.substr(i);
}
return paths.posix.relative(fromPath, toPath);
}
resolvePath(base: URI, path: string): URI {
if (base.scheme === Schemas.file) {
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
return base.with({
authority: newURI.authority,
path: newURI.path
});
}
if (path.indexOf('/') === -1) { // no slashes? it's likely a Windows path
path = extpath.toSlashes(path);
if (/^[a-zA-Z]:(\/|$)/.test(path)) { // starts with a drive letter
path = '/' + path;
}
}
return base.with({
path: paths.posix.resolve(base.path, path)
});
}
// --- misc
isAbsolutePath(resource: URI): boolean {
return !!resource.path && resource.path[0] === '/';
}
isEqualAuthority(a1: string, a2: string) {
return a1 === a2 || equalsIgnoreCase(a1, a2);
}
hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
} else {
const p = resource.path;
return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
}
}
removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
if (hasTrailingPathSeparator(resource, sep)) {
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
}
return resource;
}
addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
let isRootSep: boolean = false;
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
} else {
sep = '/';
const p = resource.path;
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;
}
if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
return resource.with({ path: resource.path + '/' });
}
return resource;
}
}
/**
* Unbiased utility that takes uris "as they are". This means it can be interchanged with
* uri#toString() usages. The following is true
* ```
* assertEqual(aUri.toString() === bUri.toString(), exturi.isEqual(aUri, bUri))
* ```
*/
export const extUri = new ExtUri(() => false);
/**
* BIASED utility that _mostly_ ignored the case of urs paths. ONLY use this util if you
* understand what you are doing.
*
* This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
*
* When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
* because those uris come from a "trustworthy source". When creating unknown uris it's always
* better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
* casing matters.
*/
export const extUriBiasedIgnorePathCase = new ExtUri(uri => {
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
return uri.scheme === Schemas.file ? !isLinux : true;
});
/**
* BIASED utility that always ignores the casing of uris paths. ONLY use this util if you
* understand what you are doing.
*
* This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
*
* When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
* because those uris come from a "trustworthy source". When creating unknown uris it's always
* better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
* casing matters.
*/
export const extUriIgnorePathCase = new ExtUri(_ => true);
export const isEqual = extUri.isEqual.bind(extUri);
export const isEqualOrParent = extUri.isEqualOrParent.bind(extUri);
export const getComparisonKey = extUri.getComparisonKey.bind(extUri);
export const basenameOrAuthority = extUri.basenameOrAuthority.bind(extUri);
export const basename = extUri.basename.bind(extUri);
export const extname = extUri.extname.bind(extUri);
export const dirname = extUri.dirname.bind(extUri);
export const joinPath = extUri.joinPath.bind(extUri);
export const normalizePath = extUri.normalizePath.bind(extUri);
export const relativePath = extUri.relativePath.bind(extUri);
export const resolvePath = extUri.resolvePath.bind(extUri);
export const isAbsolutePath = extUri.isAbsolutePath.bind(extUri);
export const isEqualAuthority = extUri.isEqualAuthority.bind(extUri);
export const hasTrailingPathSeparator = extUri.hasTrailingPathSeparator.bind(extUri);
export const removeTrailingPathSeparator = extUri.removeTrailingPathSeparator.bind(extUri);
export const addTrailingPathSeparator = extUri.addTrailingPathSeparator.bind(extUri);
//#endregion
export function distinctParents<T>(items: T[], resourceAccessor: (item: T) => URI): T[] {
const distinctParents: T[] = [];
for (let i = 0; i < items.length; i++) {
const candidateResource = resourceAccessor(items[i]);
if (items.some((otherItem, index) => {
if (index === i) {
return false;
}
return isEqualOrParent(candidateResource, resourceAccessor(otherItem));
})) {
continue;
}
distinctParents.push(items[i]);
}
return distinctParents;
}
/**
* Data URI related helpers.
*/
export namespace DataUri {
export const META_DATA_LABEL = 'label';
export const META_DATA_DESCRIPTION = 'description';
export const META_DATA_SIZE = 'size';
export const META_DATA_MIME = 'mime';
export function parseMetaData(dataUri: URI): Map<string, string> {
const metadata = new Map<string, string>();
// Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5...
// the metadata is: size:2313;label:SomeLabel;description:SomeDescription
const meta = dataUri.path.substring(dataUri.path.indexOf(';') + 1, dataUri.path.lastIndexOf(';'));
meta.split(';').forEach(property => {
const [key, value] = property.split(':');
if (key && value) {
metadata.set(key, value);
}
});
// Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5...
// the mime is: image/png
const mime = dataUri.path.substring(0, dataUri.path.indexOf(';'));
if (mime) {
metadata.set(META_DATA_MIME, mime);
}
return metadata;
}
}
export class ResourceGlobMatcher {
private readonly globalExpression: ParsedExpression;
private readonly expressionsByRoot: TernarySearchTree<URI, { root: URI, expression: ParsedExpression }> = TernarySearchTree.forUris<{ root: URI, expression: ParsedExpression }>();
constructor(
globalExpression: IExpression,
rootExpressions: { root: URI, expression: IExpression }[]
) {
this.globalExpression = parse(globalExpression);
for (const expression of rootExpressions) {
this.expressionsByRoot.set(expression.root, { root: expression.root, expression: parse(expression.expression) });
}
}
matches(resource: URI): boolean {
const rootExpression = this.expressionsByRoot.findSubstr(resource);
if (rootExpression) {
const path = relativePath(rootExpression.root, resource);
if (path && !!rootExpression.expression(path)) {
return true;
}
}
return !!this.globalExpression(resource.path);
}
}
export function toLocalResource(resource: URI, authority: string | undefined, localScheme: string): URI {
if (authority) {
let path = resource.path;
if (path && path[0] !== paths.posix.sep) {
path = paths.posix.sep + path;
}
return resource.with({ scheme: localScheme, authority, path });
}
return resource.with({ scheme: localScheme });
}

View File

@@ -0,0 +1,689 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { KeyCode } from 'vs/base/common/keyCodes';
/**
* keyboardEvent.code
*/
export const enum ScanCode {
None,
Hyper,
Super,
Fn,
FnLock,
Suspend,
Resume,
Turbo,
Sleep,
WakeUp,
KeyA,
KeyB,
KeyC,
KeyD,
KeyE,
KeyF,
KeyG,
KeyH,
KeyI,
KeyJ,
KeyK,
KeyL,
KeyM,
KeyN,
KeyO,
KeyP,
KeyQ,
KeyR,
KeyS,
KeyT,
KeyU,
KeyV,
KeyW,
KeyX,
KeyY,
KeyZ,
Digit1,
Digit2,
Digit3,
Digit4,
Digit5,
Digit6,
Digit7,
Digit8,
Digit9,
Digit0,
Enter,
Escape,
Backspace,
Tab,
Space,
Minus,
Equal,
BracketLeft,
BracketRight,
Backslash,
IntlHash,
Semicolon,
Quote,
Backquote,
Comma,
Period,
Slash,
CapsLock,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
PrintScreen,
ScrollLock,
Pause,
Insert,
Home,
PageUp,
Delete,
End,
PageDown,
ArrowRight,
ArrowLeft,
ArrowDown,
ArrowUp,
NumLock,
NumpadDivide,
NumpadMultiply,
NumpadSubtract,
NumpadAdd,
NumpadEnter,
Numpad1,
Numpad2,
Numpad3,
Numpad4,
Numpad5,
Numpad6,
Numpad7,
Numpad8,
Numpad9,
Numpad0,
NumpadDecimal,
IntlBackslash,
ContextMenu,
Power,
NumpadEqual,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
Open,
Help,
Select,
Again,
Undo,
Cut,
Copy,
Paste,
Find,
AudioVolumeMute,
AudioVolumeUp,
AudioVolumeDown,
NumpadComma,
IntlRo,
KanaMode,
IntlYen,
Convert,
NonConvert,
Lang1,
Lang2,
Lang3,
Lang4,
Lang5,
Abort,
Props,
NumpadParenLeft,
NumpadParenRight,
NumpadBackspace,
NumpadMemoryStore,
NumpadMemoryRecall,
NumpadMemoryClear,
NumpadMemoryAdd,
NumpadMemorySubtract,
NumpadClear,
NumpadClearEntry,
ControlLeft,
ShiftLeft,
AltLeft,
MetaLeft,
ControlRight,
ShiftRight,
AltRight,
MetaRight,
BrightnessUp,
BrightnessDown,
MediaPlay,
MediaRecord,
MediaFastForward,
MediaRewind,
MediaTrackNext,
MediaTrackPrevious,
MediaStop,
Eject,
MediaPlayPause,
MediaSelect,
LaunchMail,
LaunchApp2,
LaunchApp1,
SelectTask,
LaunchScreenSaver,
BrowserSearch,
BrowserHome,
BrowserBack,
BrowserForward,
BrowserStop,
BrowserRefresh,
BrowserFavorites,
ZoomToggle,
MailReply,
MailForward,
MailSend,
MAX_VALUE
}
const scanCodeIntToStr: string[] = [];
const scanCodeStrToInt: { [code: string]: number; } = Object.create(null);
const scanCodeLowerCaseStrToInt: { [code: string]: number; } = Object.create(null);
export const ScanCodeUtils = {
lowerCaseToEnum: (scanCode: string) => scanCodeLowerCaseStrToInt[scanCode] || ScanCode.None,
toEnum: (scanCode: string) => scanCodeStrToInt[scanCode] || ScanCode.None,
toString: (scanCode: ScanCode) => scanCodeIntToStr[scanCode] || 'None'
};
/**
* -1 if a ScanCode => KeyCode mapping depends on kb layout.
*/
export const IMMUTABLE_CODE_TO_KEY_CODE: KeyCode[] = [];
/**
* -1 if a KeyCode => ScanCode mapping depends on kb layout.
*/
export const IMMUTABLE_KEY_CODE_TO_CODE: ScanCode[] = [];
export class ScanCodeBinding {
public readonly ctrlKey: boolean;
public readonly shiftKey: boolean;
public readonly altKey: boolean;
public readonly metaKey: boolean;
public readonly scanCode: ScanCode;
constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, scanCode: ScanCode) {
this.ctrlKey = ctrlKey;
this.shiftKey = shiftKey;
this.altKey = altKey;
this.metaKey = metaKey;
this.scanCode = scanCode;
}
public equals(other: ScanCodeBinding): boolean {
return (
this.ctrlKey === other.ctrlKey
&& this.shiftKey === other.shiftKey
&& this.altKey === other.altKey
&& this.metaKey === other.metaKey
&& this.scanCode === other.scanCode
);
}
/**
* 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.scanCode === ScanCode.ControlLeft || this.scanCode === ScanCode.ControlRight))
|| (this.shiftKey && (this.scanCode === ScanCode.ShiftLeft || this.scanCode === ScanCode.ShiftRight))
|| (this.altKey && (this.scanCode === ScanCode.AltLeft || this.scanCode === ScanCode.AltRight))
|| (this.metaKey && (this.scanCode === ScanCode.MetaLeft || this.scanCode === ScanCode.MetaRight))
);
}
}
(function () {
function d(intScanCode: ScanCode, strScanCode: string): void {
scanCodeIntToStr[intScanCode] = strScanCode;
scanCodeStrToInt[strScanCode] = intScanCode;
scanCodeLowerCaseStrToInt[strScanCode.toLowerCase()] = intScanCode;
}
d(ScanCode.None, 'None');
d(ScanCode.Hyper, 'Hyper');
d(ScanCode.Super, 'Super');
d(ScanCode.Fn, 'Fn');
d(ScanCode.FnLock, 'FnLock');
d(ScanCode.Suspend, 'Suspend');
d(ScanCode.Resume, 'Resume');
d(ScanCode.Turbo, 'Turbo');
d(ScanCode.Sleep, 'Sleep');
d(ScanCode.WakeUp, 'WakeUp');
d(ScanCode.KeyA, 'KeyA');
d(ScanCode.KeyB, 'KeyB');
d(ScanCode.KeyC, 'KeyC');
d(ScanCode.KeyD, 'KeyD');
d(ScanCode.KeyE, 'KeyE');
d(ScanCode.KeyF, 'KeyF');
d(ScanCode.KeyG, 'KeyG');
d(ScanCode.KeyH, 'KeyH');
d(ScanCode.KeyI, 'KeyI');
d(ScanCode.KeyJ, 'KeyJ');
d(ScanCode.KeyK, 'KeyK');
d(ScanCode.KeyL, 'KeyL');
d(ScanCode.KeyM, 'KeyM');
d(ScanCode.KeyN, 'KeyN');
d(ScanCode.KeyO, 'KeyO');
d(ScanCode.KeyP, 'KeyP');
d(ScanCode.KeyQ, 'KeyQ');
d(ScanCode.KeyR, 'KeyR');
d(ScanCode.KeyS, 'KeyS');
d(ScanCode.KeyT, 'KeyT');
d(ScanCode.KeyU, 'KeyU');
d(ScanCode.KeyV, 'KeyV');
d(ScanCode.KeyW, 'KeyW');
d(ScanCode.KeyX, 'KeyX');
d(ScanCode.KeyY, 'KeyY');
d(ScanCode.KeyZ, 'KeyZ');
d(ScanCode.Digit1, 'Digit1');
d(ScanCode.Digit2, 'Digit2');
d(ScanCode.Digit3, 'Digit3');
d(ScanCode.Digit4, 'Digit4');
d(ScanCode.Digit5, 'Digit5');
d(ScanCode.Digit6, 'Digit6');
d(ScanCode.Digit7, 'Digit7');
d(ScanCode.Digit8, 'Digit8');
d(ScanCode.Digit9, 'Digit9');
d(ScanCode.Digit0, 'Digit0');
d(ScanCode.Enter, 'Enter');
d(ScanCode.Escape, 'Escape');
d(ScanCode.Backspace, 'Backspace');
d(ScanCode.Tab, 'Tab');
d(ScanCode.Space, 'Space');
d(ScanCode.Minus, 'Minus');
d(ScanCode.Equal, 'Equal');
d(ScanCode.BracketLeft, 'BracketLeft');
d(ScanCode.BracketRight, 'BracketRight');
d(ScanCode.Backslash, 'Backslash');
d(ScanCode.IntlHash, 'IntlHash');
d(ScanCode.Semicolon, 'Semicolon');
d(ScanCode.Quote, 'Quote');
d(ScanCode.Backquote, 'Backquote');
d(ScanCode.Comma, 'Comma');
d(ScanCode.Period, 'Period');
d(ScanCode.Slash, 'Slash');
d(ScanCode.CapsLock, 'CapsLock');
d(ScanCode.F1, 'F1');
d(ScanCode.F2, 'F2');
d(ScanCode.F3, 'F3');
d(ScanCode.F4, 'F4');
d(ScanCode.F5, 'F5');
d(ScanCode.F6, 'F6');
d(ScanCode.F7, 'F7');
d(ScanCode.F8, 'F8');
d(ScanCode.F9, 'F9');
d(ScanCode.F10, 'F10');
d(ScanCode.F11, 'F11');
d(ScanCode.F12, 'F12');
d(ScanCode.PrintScreen, 'PrintScreen');
d(ScanCode.ScrollLock, 'ScrollLock');
d(ScanCode.Pause, 'Pause');
d(ScanCode.Insert, 'Insert');
d(ScanCode.Home, 'Home');
d(ScanCode.PageUp, 'PageUp');
d(ScanCode.Delete, 'Delete');
d(ScanCode.End, 'End');
d(ScanCode.PageDown, 'PageDown');
d(ScanCode.ArrowRight, 'ArrowRight');
d(ScanCode.ArrowLeft, 'ArrowLeft');
d(ScanCode.ArrowDown, 'ArrowDown');
d(ScanCode.ArrowUp, 'ArrowUp');
d(ScanCode.NumLock, 'NumLock');
d(ScanCode.NumpadDivide, 'NumpadDivide');
d(ScanCode.NumpadMultiply, 'NumpadMultiply');
d(ScanCode.NumpadSubtract, 'NumpadSubtract');
d(ScanCode.NumpadAdd, 'NumpadAdd');
d(ScanCode.NumpadEnter, 'NumpadEnter');
d(ScanCode.Numpad1, 'Numpad1');
d(ScanCode.Numpad2, 'Numpad2');
d(ScanCode.Numpad3, 'Numpad3');
d(ScanCode.Numpad4, 'Numpad4');
d(ScanCode.Numpad5, 'Numpad5');
d(ScanCode.Numpad6, 'Numpad6');
d(ScanCode.Numpad7, 'Numpad7');
d(ScanCode.Numpad8, 'Numpad8');
d(ScanCode.Numpad9, 'Numpad9');
d(ScanCode.Numpad0, 'Numpad0');
d(ScanCode.NumpadDecimal, 'NumpadDecimal');
d(ScanCode.IntlBackslash, 'IntlBackslash');
d(ScanCode.ContextMenu, 'ContextMenu');
d(ScanCode.Power, 'Power');
d(ScanCode.NumpadEqual, 'NumpadEqual');
d(ScanCode.F13, 'F13');
d(ScanCode.F14, 'F14');
d(ScanCode.F15, 'F15');
d(ScanCode.F16, 'F16');
d(ScanCode.F17, 'F17');
d(ScanCode.F18, 'F18');
d(ScanCode.F19, 'F19');
d(ScanCode.F20, 'F20');
d(ScanCode.F21, 'F21');
d(ScanCode.F22, 'F22');
d(ScanCode.F23, 'F23');
d(ScanCode.F24, 'F24');
d(ScanCode.Open, 'Open');
d(ScanCode.Help, 'Help');
d(ScanCode.Select, 'Select');
d(ScanCode.Again, 'Again');
d(ScanCode.Undo, 'Undo');
d(ScanCode.Cut, 'Cut');
d(ScanCode.Copy, 'Copy');
d(ScanCode.Paste, 'Paste');
d(ScanCode.Find, 'Find');
d(ScanCode.AudioVolumeMute, 'AudioVolumeMute');
d(ScanCode.AudioVolumeUp, 'AudioVolumeUp');
d(ScanCode.AudioVolumeDown, 'AudioVolumeDown');
d(ScanCode.NumpadComma, 'NumpadComma');
d(ScanCode.IntlRo, 'IntlRo');
d(ScanCode.KanaMode, 'KanaMode');
d(ScanCode.IntlYen, 'IntlYen');
d(ScanCode.Convert, 'Convert');
d(ScanCode.NonConvert, 'NonConvert');
d(ScanCode.Lang1, 'Lang1');
d(ScanCode.Lang2, 'Lang2');
d(ScanCode.Lang3, 'Lang3');
d(ScanCode.Lang4, 'Lang4');
d(ScanCode.Lang5, 'Lang5');
d(ScanCode.Abort, 'Abort');
d(ScanCode.Props, 'Props');
d(ScanCode.NumpadParenLeft, 'NumpadParenLeft');
d(ScanCode.NumpadParenRight, 'NumpadParenRight');
d(ScanCode.NumpadBackspace, 'NumpadBackspace');
d(ScanCode.NumpadMemoryStore, 'NumpadMemoryStore');
d(ScanCode.NumpadMemoryRecall, 'NumpadMemoryRecall');
d(ScanCode.NumpadMemoryClear, 'NumpadMemoryClear');
d(ScanCode.NumpadMemoryAdd, 'NumpadMemoryAdd');
d(ScanCode.NumpadMemorySubtract, 'NumpadMemorySubtract');
d(ScanCode.NumpadClear, 'NumpadClear');
d(ScanCode.NumpadClearEntry, 'NumpadClearEntry');
d(ScanCode.ControlLeft, 'ControlLeft');
d(ScanCode.ShiftLeft, 'ShiftLeft');
d(ScanCode.AltLeft, 'AltLeft');
d(ScanCode.MetaLeft, 'MetaLeft');
d(ScanCode.ControlRight, 'ControlRight');
d(ScanCode.ShiftRight, 'ShiftRight');
d(ScanCode.AltRight, 'AltRight');
d(ScanCode.MetaRight, 'MetaRight');
d(ScanCode.BrightnessUp, 'BrightnessUp');
d(ScanCode.BrightnessDown, 'BrightnessDown');
d(ScanCode.MediaPlay, 'MediaPlay');
d(ScanCode.MediaRecord, 'MediaRecord');
d(ScanCode.MediaFastForward, 'MediaFastForward');
d(ScanCode.MediaRewind, 'MediaRewind');
d(ScanCode.MediaTrackNext, 'MediaTrackNext');
d(ScanCode.MediaTrackPrevious, 'MediaTrackPrevious');
d(ScanCode.MediaStop, 'MediaStop');
d(ScanCode.Eject, 'Eject');
d(ScanCode.MediaPlayPause, 'MediaPlayPause');
d(ScanCode.MediaSelect, 'MediaSelect');
d(ScanCode.LaunchMail, 'LaunchMail');
d(ScanCode.LaunchApp2, 'LaunchApp2');
d(ScanCode.LaunchApp1, 'LaunchApp1');
d(ScanCode.SelectTask, 'SelectTask');
d(ScanCode.LaunchScreenSaver, 'LaunchScreenSaver');
d(ScanCode.BrowserSearch, 'BrowserSearch');
d(ScanCode.BrowserHome, 'BrowserHome');
d(ScanCode.BrowserBack, 'BrowserBack');
d(ScanCode.BrowserForward, 'BrowserForward');
d(ScanCode.BrowserStop, 'BrowserStop');
d(ScanCode.BrowserRefresh, 'BrowserRefresh');
d(ScanCode.BrowserFavorites, 'BrowserFavorites');
d(ScanCode.ZoomToggle, 'ZoomToggle');
d(ScanCode.MailReply, 'MailReply');
d(ScanCode.MailForward, 'MailForward');
d(ScanCode.MailSend, 'MailSend');
})();
(function () {
for (let i = 0; i <= ScanCode.MAX_VALUE; i++) {
IMMUTABLE_CODE_TO_KEY_CODE[i] = -1;
}
for (let i = 0; i <= KeyCode.MAX_VALUE; i++) {
IMMUTABLE_KEY_CODE_TO_CODE[i] = -1;
}
function define(code: ScanCode, keyCode: KeyCode): void {
IMMUTABLE_CODE_TO_KEY_CODE[code] = keyCode;
if (
(keyCode !== KeyCode.Unknown)
&& (keyCode !== KeyCode.Enter)
&& (keyCode !== KeyCode.Ctrl)
&& (keyCode !== KeyCode.Shift)
&& (keyCode !== KeyCode.Alt)
&& (keyCode !== KeyCode.Meta)
) {
IMMUTABLE_KEY_CODE_TO_CODE[keyCode] = code;
}
}
// Manually added due to the exclusion above (due to duplication with NumpadEnter)
IMMUTABLE_KEY_CODE_TO_CODE[KeyCode.Enter] = ScanCode.Enter;
define(ScanCode.None, KeyCode.Unknown);
define(ScanCode.Hyper, KeyCode.Unknown);
define(ScanCode.Super, KeyCode.Unknown);
define(ScanCode.Fn, KeyCode.Unknown);
define(ScanCode.FnLock, KeyCode.Unknown);
define(ScanCode.Suspend, KeyCode.Unknown);
define(ScanCode.Resume, KeyCode.Unknown);
define(ScanCode.Turbo, KeyCode.Unknown);
define(ScanCode.Sleep, KeyCode.Unknown);
define(ScanCode.WakeUp, KeyCode.Unknown);
// define(ScanCode.KeyA, KeyCode.Unknown);
// define(ScanCode.KeyB, KeyCode.Unknown);
// define(ScanCode.KeyC, KeyCode.Unknown);
// define(ScanCode.KeyD, KeyCode.Unknown);
// define(ScanCode.KeyE, KeyCode.Unknown);
// define(ScanCode.KeyF, KeyCode.Unknown);
// define(ScanCode.KeyG, KeyCode.Unknown);
// define(ScanCode.KeyH, KeyCode.Unknown);
// define(ScanCode.KeyI, KeyCode.Unknown);
// define(ScanCode.KeyJ, KeyCode.Unknown);
// define(ScanCode.KeyK, KeyCode.Unknown);
// define(ScanCode.KeyL, KeyCode.Unknown);
// define(ScanCode.KeyM, KeyCode.Unknown);
// define(ScanCode.KeyN, KeyCode.Unknown);
// define(ScanCode.KeyO, KeyCode.Unknown);
// define(ScanCode.KeyP, KeyCode.Unknown);
// define(ScanCode.KeyQ, KeyCode.Unknown);
// define(ScanCode.KeyR, KeyCode.Unknown);
// define(ScanCode.KeyS, KeyCode.Unknown);
// define(ScanCode.KeyT, KeyCode.Unknown);
// define(ScanCode.KeyU, KeyCode.Unknown);
// define(ScanCode.KeyV, KeyCode.Unknown);
// define(ScanCode.KeyW, KeyCode.Unknown);
// define(ScanCode.KeyX, KeyCode.Unknown);
// define(ScanCode.KeyY, KeyCode.Unknown);
// define(ScanCode.KeyZ, KeyCode.Unknown);
// define(ScanCode.Digit1, KeyCode.Unknown);
// define(ScanCode.Digit2, KeyCode.Unknown);
// define(ScanCode.Digit3, KeyCode.Unknown);
// define(ScanCode.Digit4, KeyCode.Unknown);
// define(ScanCode.Digit5, KeyCode.Unknown);
// define(ScanCode.Digit6, KeyCode.Unknown);
// define(ScanCode.Digit7, KeyCode.Unknown);
// define(ScanCode.Digit8, KeyCode.Unknown);
// define(ScanCode.Digit9, KeyCode.Unknown);
// define(ScanCode.Digit0, KeyCode.Unknown);
define(ScanCode.Enter, KeyCode.Enter);
define(ScanCode.Escape, KeyCode.Escape);
define(ScanCode.Backspace, KeyCode.Backspace);
define(ScanCode.Tab, KeyCode.Tab);
define(ScanCode.Space, KeyCode.Space);
// define(ScanCode.Minus, KeyCode.Unknown);
// define(ScanCode.Equal, KeyCode.Unknown);
// define(ScanCode.BracketLeft, KeyCode.Unknown);
// define(ScanCode.BracketRight, KeyCode.Unknown);
// define(ScanCode.Backslash, KeyCode.Unknown);
// define(ScanCode.IntlHash, KeyCode.Unknown);
// define(ScanCode.Semicolon, KeyCode.Unknown);
// define(ScanCode.Quote, KeyCode.Unknown);
// define(ScanCode.Backquote, KeyCode.Unknown);
// define(ScanCode.Comma, KeyCode.Unknown);
// define(ScanCode.Period, KeyCode.Unknown);
// define(ScanCode.Slash, KeyCode.Unknown);
define(ScanCode.CapsLock, KeyCode.CapsLock);
define(ScanCode.F1, KeyCode.F1);
define(ScanCode.F2, KeyCode.F2);
define(ScanCode.F3, KeyCode.F3);
define(ScanCode.F4, KeyCode.F4);
define(ScanCode.F5, KeyCode.F5);
define(ScanCode.F6, KeyCode.F6);
define(ScanCode.F7, KeyCode.F7);
define(ScanCode.F8, KeyCode.F8);
define(ScanCode.F9, KeyCode.F9);
define(ScanCode.F10, KeyCode.F10);
define(ScanCode.F11, KeyCode.F11);
define(ScanCode.F12, KeyCode.F12);
define(ScanCode.PrintScreen, KeyCode.Unknown);
define(ScanCode.ScrollLock, KeyCode.ScrollLock);
define(ScanCode.Pause, KeyCode.PauseBreak);
define(ScanCode.Insert, KeyCode.Insert);
define(ScanCode.Home, KeyCode.Home);
define(ScanCode.PageUp, KeyCode.PageUp);
define(ScanCode.Delete, KeyCode.Delete);
define(ScanCode.End, KeyCode.End);
define(ScanCode.PageDown, KeyCode.PageDown);
define(ScanCode.ArrowRight, KeyCode.RightArrow);
define(ScanCode.ArrowLeft, KeyCode.LeftArrow);
define(ScanCode.ArrowDown, KeyCode.DownArrow);
define(ScanCode.ArrowUp, KeyCode.UpArrow);
define(ScanCode.NumLock, KeyCode.NumLock);
define(ScanCode.NumpadDivide, KeyCode.NUMPAD_DIVIDE);
define(ScanCode.NumpadMultiply, KeyCode.NUMPAD_MULTIPLY);
define(ScanCode.NumpadSubtract, KeyCode.NUMPAD_SUBTRACT);
define(ScanCode.NumpadAdd, KeyCode.NUMPAD_ADD);
define(ScanCode.NumpadEnter, KeyCode.Enter); // Duplicate
define(ScanCode.Numpad1, KeyCode.NUMPAD_1);
define(ScanCode.Numpad2, KeyCode.NUMPAD_2);
define(ScanCode.Numpad3, KeyCode.NUMPAD_3);
define(ScanCode.Numpad4, KeyCode.NUMPAD_4);
define(ScanCode.Numpad5, KeyCode.NUMPAD_5);
define(ScanCode.Numpad6, KeyCode.NUMPAD_6);
define(ScanCode.Numpad7, KeyCode.NUMPAD_7);
define(ScanCode.Numpad8, KeyCode.NUMPAD_8);
define(ScanCode.Numpad9, KeyCode.NUMPAD_9);
define(ScanCode.Numpad0, KeyCode.NUMPAD_0);
define(ScanCode.NumpadDecimal, KeyCode.NUMPAD_DECIMAL);
// define(ScanCode.IntlBackslash, KeyCode.Unknown);
define(ScanCode.ContextMenu, KeyCode.ContextMenu);
define(ScanCode.Power, KeyCode.Unknown);
define(ScanCode.NumpadEqual, KeyCode.Unknown);
define(ScanCode.F13, KeyCode.F13);
define(ScanCode.F14, KeyCode.F14);
define(ScanCode.F15, KeyCode.F15);
define(ScanCode.F16, KeyCode.F16);
define(ScanCode.F17, KeyCode.F17);
define(ScanCode.F18, KeyCode.F18);
define(ScanCode.F19, KeyCode.F19);
define(ScanCode.F20, KeyCode.Unknown);
define(ScanCode.F21, KeyCode.Unknown);
define(ScanCode.F22, KeyCode.Unknown);
define(ScanCode.F23, KeyCode.Unknown);
define(ScanCode.F24, KeyCode.Unknown);
define(ScanCode.Open, KeyCode.Unknown);
define(ScanCode.Help, KeyCode.Unknown);
define(ScanCode.Select, KeyCode.Unknown);
define(ScanCode.Again, KeyCode.Unknown);
define(ScanCode.Undo, KeyCode.Unknown);
define(ScanCode.Cut, KeyCode.Unknown);
define(ScanCode.Copy, KeyCode.Unknown);
define(ScanCode.Paste, KeyCode.Unknown);
define(ScanCode.Find, KeyCode.Unknown);
define(ScanCode.AudioVolumeMute, KeyCode.Unknown);
define(ScanCode.AudioVolumeUp, KeyCode.Unknown);
define(ScanCode.AudioVolumeDown, KeyCode.Unknown);
define(ScanCode.NumpadComma, KeyCode.NUMPAD_SEPARATOR);
// define(ScanCode.IntlRo, KeyCode.Unknown);
define(ScanCode.KanaMode, KeyCode.Unknown);
// define(ScanCode.IntlYen, KeyCode.Unknown);
define(ScanCode.Convert, KeyCode.Unknown);
define(ScanCode.NonConvert, KeyCode.Unknown);
define(ScanCode.Lang1, KeyCode.Unknown);
define(ScanCode.Lang2, KeyCode.Unknown);
define(ScanCode.Lang3, KeyCode.Unknown);
define(ScanCode.Lang4, KeyCode.Unknown);
define(ScanCode.Lang5, KeyCode.Unknown);
define(ScanCode.Abort, KeyCode.Unknown);
define(ScanCode.Props, KeyCode.Unknown);
define(ScanCode.NumpadParenLeft, KeyCode.Unknown);
define(ScanCode.NumpadParenRight, KeyCode.Unknown);
define(ScanCode.NumpadBackspace, KeyCode.Unknown);
define(ScanCode.NumpadMemoryStore, KeyCode.Unknown);
define(ScanCode.NumpadMemoryRecall, KeyCode.Unknown);
define(ScanCode.NumpadMemoryClear, KeyCode.Unknown);
define(ScanCode.NumpadMemoryAdd, KeyCode.Unknown);
define(ScanCode.NumpadMemorySubtract, KeyCode.Unknown);
define(ScanCode.NumpadClear, KeyCode.Unknown);
define(ScanCode.NumpadClearEntry, KeyCode.Unknown);
define(ScanCode.ControlLeft, KeyCode.Ctrl); // Duplicate
define(ScanCode.ShiftLeft, KeyCode.Shift); // Duplicate
define(ScanCode.AltLeft, KeyCode.Alt); // Duplicate
define(ScanCode.MetaLeft, KeyCode.Meta); // Duplicate
define(ScanCode.ControlRight, KeyCode.Ctrl); // Duplicate
define(ScanCode.ShiftRight, KeyCode.Shift); // Duplicate
define(ScanCode.AltRight, KeyCode.Alt); // Duplicate
define(ScanCode.MetaRight, KeyCode.Meta); // Duplicate
define(ScanCode.BrightnessUp, KeyCode.Unknown);
define(ScanCode.BrightnessDown, KeyCode.Unknown);
define(ScanCode.MediaPlay, KeyCode.Unknown);
define(ScanCode.MediaRecord, KeyCode.Unknown);
define(ScanCode.MediaFastForward, KeyCode.Unknown);
define(ScanCode.MediaRewind, KeyCode.Unknown);
define(ScanCode.MediaTrackNext, KeyCode.Unknown);
define(ScanCode.MediaTrackPrevious, KeyCode.Unknown);
define(ScanCode.MediaStop, KeyCode.Unknown);
define(ScanCode.Eject, KeyCode.Unknown);
define(ScanCode.MediaPlayPause, KeyCode.Unknown);
define(ScanCode.MediaSelect, KeyCode.Unknown);
define(ScanCode.LaunchMail, KeyCode.Unknown);
define(ScanCode.LaunchApp2, KeyCode.Unknown);
define(ScanCode.LaunchApp1, KeyCode.Unknown);
define(ScanCode.SelectTask, KeyCode.Unknown);
define(ScanCode.LaunchScreenSaver, KeyCode.Unknown);
define(ScanCode.BrowserSearch, KeyCode.Unknown);
define(ScanCode.BrowserHome, KeyCode.Unknown);
define(ScanCode.BrowserBack, KeyCode.Unknown);
define(ScanCode.BrowserForward, KeyCode.Unknown);
define(ScanCode.BrowserStop, KeyCode.Unknown);
define(ScanCode.BrowserRefresh, KeyCode.Unknown);
define(ScanCode.BrowserFavorites, KeyCode.Unknown);
define(ScanCode.ZoomToggle, KeyCode.Unknown);
define(ScanCode.MailReply, KeyCode.Unknown);
define(ScanCode.MailForward, KeyCode.Unknown);
define(ScanCode.MailSend, KeyCode.Unknown);
})();

View File

@@ -0,0 +1,493 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
export const enum ScrollbarVisibility {
Auto = 1,
Hidden = 2,
Visible = 3
}
export interface ScrollEvent {
oldWidth: number;
oldScrollWidth: number;
oldScrollLeft: number;
width: number;
scrollWidth: number;
scrollLeft: number;
oldHeight: number;
oldScrollHeight: number;
oldScrollTop: 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 rawScrollLeft: number;
public readonly rawScrollTop: number;
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;
this.rawScrollLeft = scrollLeft; // before validation
this.rawScrollTop = scrollTop; // before validation
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.rawScrollLeft === other.rawScrollLeft
&& this.rawScrollTop === other.rawScrollTop
&& 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, useRawScrollPositions: boolean): ScrollState {
return new ScrollState(
(typeof update.width !== 'undefined' ? update.width : this.width),
(typeof update.scrollWidth !== 'undefined' ? update.scrollWidth : this.scrollWidth),
useRawScrollPositions ? this.rawScrollLeft : this.scrollLeft,
(typeof update.height !== 'undefined' ? update.height : this.height),
(typeof update.scrollHeight !== 'undefined' ? update.scrollHeight : this.scrollHeight),
useRawScrollPositions ? this.rawScrollTop : this.scrollTop
);
}
public withScrollPosition(update: INewScrollPosition): ScrollState {
return new ScrollState(
this.width,
this.scrollWidth,
(typeof update.scrollLeft !== 'undefined' ? update.scrollLeft : this.rawScrollLeft),
this.height,
this.scrollHeight,
(typeof update.scrollTop !== 'undefined' ? update.scrollTop : this.rawScrollTop)
);
}
public createScrollEvent(previous: ScrollState): ScrollEvent {
const widthChanged = (this.width !== previous.width);
const scrollWidthChanged = (this.scrollWidth !== previous.scrollWidth);
const scrollLeftChanged = (this.scrollLeft !== previous.scrollLeft);
const heightChanged = (this.height !== previous.height);
const scrollHeightChanged = (this.scrollHeight !== previous.scrollHeight);
const scrollTopChanged = (this.scrollTop !== previous.scrollTop);
return {
oldWidth: previous.width,
oldScrollWidth: previous.scrollWidth,
oldScrollLeft: previous.scrollLeft,
width: this.width,
scrollWidth: this.scrollWidth,
scrollLeft: this.scrollLeft,
oldHeight: previous.height,
oldScrollHeight: previous.scrollHeight,
oldScrollTop: previous.scrollTop,
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 | null;
private _onScroll = this._register(new Emitter<ScrollEvent>());
public readonly 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, useRawScrollPositions: boolean): void {
const newState = this._state.withScrollDimensions(dimensions, useRawScrollPositions);
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 occurring, 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 {
if (!this._smoothScrolling) {
return;
}
const update = this._smoothScrolling.tick();
const newState = this._state.withScrollPosition(update);
this._setState(newState);
if (!this._smoothScrolling) {
// Looks like someone canceled the smooth scrolling
// from the scroll event handler
return;
}
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 | null;
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,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from './strings';
export function buildReplaceStringWithCasePreserved(matches: string[] | null, pattern: string): string {
if (matches && (matches[0] !== '')) {
const containsHyphens = validateSpecificSpecialCharacter(matches, pattern, '-');
const containsUnderscores = validateSpecificSpecialCharacter(matches, pattern, '_');
if (containsHyphens && !containsUnderscores) {
return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '-');
} else if (!containsHyphens && containsUnderscores) {
return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '_');
}
if (matches[0].toUpperCase() === matches[0]) {
return pattern.toUpperCase();
} else if (matches[0].toLowerCase() === matches[0]) {
return pattern.toLowerCase();
} else if (strings.containsUppercaseCharacter(matches[0][0]) && pattern.length > 0) {
return pattern[0].toUpperCase() + pattern.substr(1);
} else {
// we don't understand its pattern yet.
return pattern;
}
} else {
return pattern;
}
}
function validateSpecificSpecialCharacter(matches: string[], pattern: string, specialCharacter: string): boolean {
const doesContainSpecialCharacter = matches[0].indexOf(specialCharacter) !== -1 && pattern.indexOf(specialCharacter) !== -1;
return doesContainSpecialCharacter && matches[0].split(specialCharacter).length === pattern.split(specialCharacter).length;
}
function buildReplaceStringForSpecificSpecialCharacter(matches: string[], pattern: string, specialCharacter: string): string {
const splitPatternAtSpecialCharacter = pattern.split(specialCharacter);
const splitMatchAtSpecialCharacter = matches[0].split(specialCharacter);
let replaceString: string = '';
splitPatternAtSpecialCharacter.forEach((splitValue, index) => {
replaceString += buildReplaceStringWithCasePreserved([splitMatchAtSpecialCharacter[index]], splitValue) + specialCharacter;
});
return replaceString.slice(0, -1);
}

View File

@@ -0,0 +1,17 @@
{
"registrations": [
{
"component": {
"type": "git",
"git": {
"name": "semver",
"repositoryUrl": "https://github.com/npm/node-semver",
"commitHash": "44cbc8482ac4f0f8d2de0abb7f8808056d2d55f9"
}
},
"license": "The ISC License",
"version": "5.5.0"
}
],
"version": 1
}

View File

@@ -0,0 +1,312 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export as namespace semver;
export = semver;
declare namespace semver {
// Type definitions for semver 6.2
// Project: https://github.com/npm/node-semver
// Definitions by: Bart van der Schoor <https://github.com/Bartvds>
// BendingBender <https://github.com/BendingBender>
// Lucian Buzzo <https://github.com/LucianBuzzo>
// Klaus Meinhardt <https://github.com/ajafff>
// ExE Boss <https://github.com/ExE-Boss>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/semver
export const SEMVER_SPEC_VERSION: "2.0.0";
export type ReleaseType = "major" | "premajor" | "minor" | "preminor" | "patch" | "prepatch" | "prerelease";
export interface Options {
loose?: boolean;
includePrerelease?: boolean;
}
export interface CoerceOptions extends Options {
/**
* Used by `coerce()` to coerce from right to left.
*
* @default false
*
* @example
* coerce('1.2.3.4', { rtl: true });
* // => SemVer { version: '2.3.4', ... }
*
* @since 6.2.0
*/
rtl?: boolean;
}
/**
* Return the parsed version as a SemVer object, or null if it's not valid.
*/
export function parse(version: string | SemVer | null | undefined, optionsOrLoose?: boolean | Options): SemVer | null;
/**
* Return the parsed version as a string, or null if it's not valid.
*/
export function valid(version: string | SemVer | null | undefined, optionsOrLoose?: boolean | Options): string | null;
/**
* Coerces a string to SemVer if possible
*/
export function coerce(version: string | number | SemVer | null | undefined, options?: CoerceOptions): SemVer | null;
/**
* Returns cleaned (removed leading/trailing whitespace, remove '=v' prefix) and parsed version, or null if version is invalid.
*/
export function clean(version: string, optionsOrLoose?: boolean | Options): string | null;
/**
* Return the version incremented by the release type (major, minor, patch, or prerelease), or null if it's not valid.
*/
export function inc(version: string | SemVer, release: ReleaseType, optionsOrLoose?: boolean | Options, identifier?: string): string | null;
export function inc(version: string | SemVer, release: ReleaseType, identifier?: string): string | null;
/**
* Return the major version number.
*/
export function major(version: string | SemVer, optionsOrLoose?: boolean | Options): number;
/**
* Return the minor version number.
*/
export function minor(version: string | SemVer, optionsOrLoose?: boolean | Options): number;
/**
* Return the patch version number.
*/
export function patch(version: string | SemVer, optionsOrLoose?: boolean | Options): number;
/**
* Returns an array of prerelease components, or null if none exist.
*/
export function prerelease(version: string | SemVer, optionsOrLoose?: boolean | Options): ReadonlyArray<string> | null;
// Comparison
/**
* v1 > v2
*/
export function gt(v1: string | SemVer, v2: string | SemVer, optionsOrLoose?: boolean | Options): boolean;
/**
* v1 >= v2
*/
export function gte(v1: string | SemVer, v2: string | SemVer, optionsOrLoose?: boolean | Options): boolean;
/**
* v1 < v2
*/
export function lt(v1: string | SemVer, v2: string | SemVer, optionsOrLoose?: boolean | Options): boolean;
/**
* v1 <= v2
*/
export function lte(v1: string | SemVer, v2: string | SemVer, optionsOrLoose?: boolean | Options): boolean;
/**
* v1 == v2 This is true if they're logically equivalent, even if they're not the exact same string. You already know how to compare strings.
*/
export function eq(v1: string | SemVer, v2: string | SemVer, optionsOrLoose?: boolean | Options): boolean;
/**
* v1 != v2 The opposite of eq.
*/
export function neq(v1: string | SemVer, v2: string | SemVer, optionsOrLoose?: boolean | Options): boolean;
/**
* Pass in a comparison string, and it'll call the corresponding semver comparison function.
* "===" and "!==" do simple string comparison, but are included for completeness.
* Throws if an invalid comparison string is provided.
*/
export function cmp(v1: string | SemVer, operator: Operator, v2: string | SemVer, optionsOrLoose?: boolean | Options): boolean;
export type Operator = '===' | '!==' | '' | '=' | '==' | '!=' | '>' | '>=' | '<' | '<=';
/**
* Compares two versions excluding build identifiers (the bit after `+` in the semantic version string).
*
* Sorts in ascending order when passed to `Array.sort()`.
*
* @return
* - `0` if `v1` == `v2`
* - `1` if `v1` is greater
* - `-1` if `v2` is greater.
*/
export function compare(v1: string | SemVer, v2: string | SemVer, optionsOrLoose?: boolean | Options): 1 | 0 | -1;
/**
* The reverse of compare.
*
* Sorts in descending order when passed to `Array.sort()`.
*/
export function rcompare(v1: string | SemVer, v2: string | SemVer, optionsOrLoose?: boolean | Options): 1 | 0 | -1;
/**
* Compares two identifiers, must be numeric strings or truthy/falsy values.
*
* Sorts in ascending order when passed to `Array.sort()`.
*/
export function compareIdentifiers(a: string | null | undefined, b: string | null | undefined): 1 | 0 | -1;
/**
* The reverse of compareIdentifiers.
*
* Sorts in descending order when passed to `Array.sort()`.
*/
export function rcompareIdentifiers(a: string | null | undefined, b: string | null | undefined): 1 | 0 | -1;
/**
* Compares two versions including build identifiers (the bit after `+` in the semantic version string).
*
* Sorts in ascending order when passed to `Array.sort()`.
*
* @return
* - `0` if `v1` == `v2`
* - `1` if `v1` is greater
* - `-1` if `v2` is greater.
*
* @since 6.1.0
*/
export function compareBuild(a: string | SemVer, b: string | SemVer): 1 | 0 | -1;
/**
* Sorts an array of semver entries in ascending order using `compareBuild()`.
*/
export function sort<T extends string | SemVer>(list: T[], optionsOrLoose?: boolean | Options): T[];
/**
* Sorts an array of semver entries in descending order using `compareBuild()`.
*/
export function rsort<T extends string | SemVer>(list: T[], optionsOrLoose?: boolean | Options): T[];
/**
* Returns difference between two versions by the release type (major, premajor, minor, preminor, patch, prepatch, or prerelease), or null if the versions are the same.
*/
export function diff(v1: string | SemVer, v2: string | SemVer, optionsOrLoose?: boolean | Options): ReleaseType | null;
// Ranges
/**
* Return the valid range or null if it's not valid
*/
export function validRange(range: string | Range | null | undefined, optionsOrLoose?: boolean | Options): string;
/**
* Return true if the version satisfies the range.
*/
export function satisfies(version: string | SemVer, range: string | Range, optionsOrLoose?: boolean | Options): boolean;
/**
* Return the highest version in the list that satisfies the range, or null if none of them do.
*/
export function maxSatisfying<T extends string | SemVer>(versions: ReadonlyArray<T>, range: string | Range, optionsOrLoose?: boolean | Options): T | null;
/**
* Return the lowest version in the list that satisfies the range, or null if none of them do.
*/
export function minSatisfying<T extends string | SemVer>(versions: ReadonlyArray<T>, range: string | Range, optionsOrLoose?: boolean | Options): T | null;
/**
* Return the lowest version that can possibly match the given range.
*/
export function minVersion(range: string | Range, optionsOrLoose?: boolean | Options): SemVer | null;
/**
* Return true if version is greater than all the versions possible in the range.
*/
export function gtr(version: string | SemVer, range: string | Range, optionsOrLoose?: boolean | Options): boolean;
/**
* Return true if version is less than all the versions possible in the range.
*/
export function ltr(version: string | SemVer, range: string | Range, optionsOrLoose?: boolean | Options): boolean;
/**
* Return true if the version is outside the bounds of the range in either the high or low direction.
* The hilo argument must be either the string '>' or '<'. (This is the function called by gtr and ltr.)
*/
export function outside(version: string | SemVer, range: string | Range, hilo: '>' | '<', optionsOrLoose?: boolean | Options): boolean;
/**
* Return true if any of the ranges comparators intersect
*/
export function intersects(range1: string | Range, range2: string | Range, optionsOrLoose?: boolean | Options): boolean;
export class SemVer {
constructor(version: string | SemVer, optionsOrLoose?: boolean | Options);
raw: string;
loose: boolean;
options: Options;
format(): string;
inspect(): string;
major: number;
minor: number;
patch: number;
version: string;
build: ReadonlyArray<string>;
prerelease: ReadonlyArray<string | number>;
/**
* Compares two versions excluding build identifiers (the bit after `+` in the semantic version string).
*
* @return
* - `0` if `this` == `other`
* - `1` if `this` is greater
* - `-1` if `other` is greater.
*/
compare(other: string | SemVer): 1 | 0 | -1;
/**
* Compares the release portion of two versions.
*
* @return
* - `0` if `this` == `other`
* - `1` if `this` is greater
* - `-1` if `other` is greater.
*/
compareMain(other: string | SemVer): 1 | 0 | -1;
/**
* Compares the prerelease portion of two versions.
*
* @return
* - `0` if `this` == `other`
* - `1` if `this` is greater
* - `-1` if `other` is greater.
*/
comparePre(other: string | SemVer): 1 | 0 | -1;
/**
* Compares the build identifier of two versions.
*
* @return
* - `0` if `this` == `other`
* - `1` if `this` is greater
* - `-1` if `other` is greater.
*/
compareBuild(other: string | SemVer): 1 | 0 | -1;
inc(release: ReleaseType, identifier?: string): SemVer;
}
export class Comparator {
constructor(comp: string | Comparator, optionsOrLoose?: boolean | Options);
semver: SemVer;
operator: '' | '=' | '<' | '>' | '<=' | '>=';
value: string;
loose: boolean;
options: Options;
parse(comp: string): void;
test(version: string | SemVer): boolean;
intersects(comp: Comparator, optionsOrLoose?: boolean | Options): boolean;
}
export class Range {
constructor(range: string | Range, optionsOrLoose?: boolean | Options);
range: string;
raw: string;
loose: boolean;
options: Options;
includePrerelease: boolean;
format(): string;
inspect(): string;
set: ReadonlyArray<ReadonlyArray<Comparator>>;
parseRange(range: string): ReadonlyArray<Comparator>;
test(version: string | SemVer): boolean;
intersects(range: Range, optionsOrLoose?: boolean | Options): boolean;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
export interface ISplice<T> {
readonly start: number;
readonly deleteCount: number;
readonly toInsert: T[];
}
export interface ISpliceable<T> {
splice(start: number, deleteCount: number, toInsert: T[]): void;
}
export interface ISequence<T> {
readonly elements: T[];
readonly onDidSplice: Event<ISplice<T>>;
}
export class Sequence<T> implements ISequence<T>, ISpliceable<T> {
readonly elements: T[] = [];
private readonly _onDidSplice = new Emitter<ISplice<T>>();
readonly onDidSplice: Event<ISplice<T>> = this._onDidSplice.event;
splice(start: number, deleteCount: number, toInsert: T[] = []): void {
this.elements.splice(start, deleteCount, ...toInsert);
this._onDidSplice.fire({ start, deleteCount, toInsert });
}
}
export class SimpleSequence<T> implements ISequence<T> {
private _elements: T[];
get elements(): T[] { return this._elements; }
readonly onDidSplice: Event<ISplice<T>>;
private disposable: IDisposable;
constructor(elements: T[], onDidAdd: Event<T>, onDidRemove: Event<T>) {
this._elements = [...elements];
this.onDidSplice = Event.any(
Event.map(onDidAdd, e => ({ start: this.elements.length, deleteCount: 0, toInsert: [e] })),
Event.map(Event.filter(Event.map(onDidRemove, e => this.elements.indexOf(e)), i => i > -1), i => ({ start: i, deleteCount: 1, toInsert: [] }))
);
this.disposable = this.onDidSplice(({ start, deleteCount, toInsert }) => this._elements.splice(start, deleteCount, ...toInsert));
}
dispose(): void {
this.disposable.dispose();
}
}

View File

@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
enum Severity {
Ignore = 0,
Info = 1,
Warning = 2,
Error = 3
}
namespace Severity {
const _error = 'error';
const _warning = 'warning';
const _warn = 'warn';
const _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 default Severity;

View File

@@ -0,0 +1,203 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class Node<K, V> {
readonly forward: Node<K, V>[];
constructor(readonly level: number, readonly key: K, public value: V) {
this.forward = [];
}
}
const NIL: undefined = undefined;
interface Comparator<K> {
(a: K, b: K): number;
}
export class SkipList<K, V> implements Map<K, V> {
readonly [Symbol.toStringTag] = 'SkipList';
private _maxLevel: number;
private _level: number = 0;
private _header: Node<K, V>;
private _size: number = 0;
/**
*
* @param capacity Capacity at which the list performs best
*/
constructor(
readonly comparator: (a: K, b: K) => number,
capacity: number = 2 ** 16
) {
this._maxLevel = Math.max(1, Math.log2(capacity) | 0);
this._header = <any>new Node(this._maxLevel, NIL, NIL);
}
get size(): number {
return this._size;
}
clear(): void {
this._header = <any>new Node(this._maxLevel, NIL, NIL);
}
has(key: K): boolean {
return Boolean(SkipList._search(this, key, this.comparator));
}
get(key: K): V | undefined {
return SkipList._search(this, key, this.comparator)?.value;
}
set(key: K, value: V): this {
if (SkipList._insert(this, key, value, this.comparator)) {
this._size += 1;
}
return this;
}
delete(key: K): boolean {
const didDelete = SkipList._delete(this, key, this.comparator);
if (didDelete) {
this._size -= 1;
}
return didDelete;
}
// --- iteration
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
let node = this._header.forward[0];
while (node) {
callbackfn.call(thisArg, node.value, node.key, this);
node = node.forward[0];
}
}
[Symbol.iterator](): IterableIterator<[K, V]> {
return this.entries();
}
*entries(): IterableIterator<[K, V]> {
let node = this._header.forward[0];
while (node) {
yield [node.key, node.value];
node = node.forward[0];
}
}
*keys(): IterableIterator<K> {
let node = this._header.forward[0];
while (node) {
yield node.key;
node = node.forward[0];
}
}
*values(): IterableIterator<V> {
let node = this._header.forward[0];
while (node) {
yield node.value;
node = node.forward[0];
}
}
toString(): string {
// debug string...
let result = '[SkipList]:';
let node = this._header.forward[0];
while (node) {
result += `node(${node.key}, ${node.value}, lvl:${node.level})`;
node = node.forward[0];
}
return result;
}
// from https://www.epaperpress.com/sortsearch/download/skiplist.pdf
private static _search<K, V>(list: SkipList<K, V>, searchKey: K, comparator: Comparator<K>) {
let x = list._header;
for (let i = list._level - 1; i >= 0; i--) {
while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) {
x = x.forward[i];
}
}
x = x.forward[0];
if (x && comparator(x.key, searchKey) === 0) {
return x;
}
return undefined;
}
private static _insert<K, V>(list: SkipList<K, V>, searchKey: K, value: V, comparator: Comparator<K>) {
let update: Node<K, V>[] = [];
let x = list._header;
for (let i = list._level - 1; i >= 0; i--) {
while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) {
x = x.forward[i];
}
update[i] = x;
}
x = x.forward[0];
if (x && comparator(x.key, searchKey) === 0) {
// update
x.value = value;
return false;
} else {
// insert
let lvl = SkipList._randomLevel(list);
if (lvl > list._level) {
for (let i = list._level; i < lvl; i++) {
update[i] = list._header;
}
list._level = lvl;
}
x = new Node<K, V>(lvl, searchKey, value);
for (let i = 0; i < lvl; i++) {
x.forward[i] = update[i].forward[i];
update[i].forward[i] = x;
}
return true;
}
}
private static _randomLevel(list: SkipList<any, any>, p: number = 0.5): number {
let lvl = 1;
while (Math.random() < p && lvl < list._maxLevel) {
lvl += 1;
}
return lvl;
}
private static _delete<K, V>(list: SkipList<K, V>, searchKey: K, comparator: Comparator<K>) {
let update: Node<K, V>[] = [];
let x = list._header;
for (let i = list._level - 1; i >= 0; i--) {
while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) {
x = x.forward[i];
}
update[i] = x;
}
x = x.forward[0];
if (!x || comparator(x.key, searchKey) !== 0) {
// not found
return false;
}
for (let i = 0; i < list._level; i++) {
if (update[i].forward[i] !== x) {
break;
}
update[i].forward[i] = x.forward[i];
}
while (list._level > 0 && list._header.forward[list._level - 1] === NIL) {
list._level -= 1;
}
return true;
}
}

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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,576 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
/**
* The payload that flows in readable stream events.
*/
export type ReadableStreamEventPayload<T> = T | Error | 'end';
export interface ReadableStreamEvents<T> {
/**
* The 'data' event is emitted whenever the stream is
* relinquishing ownership of a chunk of data to a consumer.
*/
on(event: 'data', callback: (data: T) => void): void;
/**
* Emitted when any error occurs.
*/
on(event: 'error', callback: (err: Error) => void): void;
/**
* The 'end' event is emitted when there is no more data
* to be consumed from the stream. The 'end' event will
* not be emitted unless the data is completely consumed.
*/
on(event: 'end', callback: () => void): void;
}
/**
* A interface that emulates the API shape of a node.js readable
* stream for use in native and web environments.
*/
export interface ReadableStream<T> extends ReadableStreamEvents<T> {
/**
* Stops emitting any events until resume() is called.
*/
pause(): void;
/**
* Starts emitting events again after pause() was called.
*/
resume(): void;
/**
* Destroys the stream and stops emitting any event.
*/
destroy(): void;
/**
* Allows to remove a listener that was previously added.
*/
removeListener(event: string, callback: Function): void;
}
/**
* A interface that emulates the API shape of a node.js readable
* for use in native and web environments.
*/
export interface Readable<T> {
/**
* Read data from the underlying source. Will return
* null to indicate that no more data can be read.
*/
read(): T | null;
}
/**
* A interface that emulates the API shape of a node.js writeable
* stream for use in native and web environments.
*/
export interface WriteableStream<T> extends ReadableStream<T> {
/**
* Writing data to the stream will trigger the on('data')
* event listener if the stream is flowing and buffer the
* data otherwise until the stream is flowing.
*
* If a `highWaterMark` is configured and writing to the
* stream reaches this mark, a promise will be returned
* that should be awaited on before writing more data.
* Otherwise there is a risk of buffering a large number
* of data chunks without consumer.
*/
write(data: T): void | Promise<void>;
/**
* Signals an error to the consumer of the stream via the
* on('error') handler if the stream is flowing.
*/
error(error: Error): void;
/**
* Signals the end of the stream to the consumer. If the
* result is not an error, will trigger the on('data') event
* listener if the stream is flowing and buffer the data
* otherwise until the stream is flowing.
*
* In case of an error, the on('error') event will be used
* if the stream is flowing.
*/
end(result?: T | Error): void;
}
/**
* A stream that has a buffer already read. Returns the original stream
* that was read as well as the chunks that got read.
*
* The `ended` flag indicates if the stream has been fully consumed.
*/
export interface ReadableBufferedStream<T> {
/**
* The original stream that is being read.
*/
stream: ReadableStream<T>;
/**
* An array of chunks already read from this stream.
*/
buffer: T[];
/**
* Signals if the stream has ended or not. If not, consumers
* should continue to read from the stream until consumed.
*/
ended: boolean;
}
export function isReadableStream<T>(obj: unknown): obj is ReadableStream<T> {
const candidate = obj as ReadableStream<T>;
return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
}
export function isReadableBufferedStream<T>(obj: unknown): obj is ReadableBufferedStream<T> {
const candidate = obj as ReadableBufferedStream<T>;
return candidate && isReadableStream(candidate.stream) && Array.isArray(candidate.buffer) && typeof candidate.ended === 'boolean';
}
export interface IReducer<T> {
(data: T[]): T;
}
export interface IDataTransformer<Original, Transformed> {
(data: Original): Transformed;
}
export interface IErrorTransformer {
(error: Error): Error;
}
export interface ITransformer<Original, Transformed> {
data: IDataTransformer<Original, Transformed>;
error?: IErrorTransformer;
}
export function newWriteableStream<T>(reducer: IReducer<T>, options?: WriteableStreamOptions): WriteableStream<T> {
return new WriteableStreamImpl<T>(reducer, options);
}
export interface WriteableStreamOptions {
/**
* The number of objects to buffer before WriteableStream#write()
* signals back that the buffer is full. Can be used to reduce
* the memory pressure when the stream is not flowing.
*/
highWaterMark?: number;
}
class WriteableStreamImpl<T> implements WriteableStream<T> {
private readonly state = {
flowing: false,
ended: false,
destroyed: false
};
private readonly buffer = {
data: [] as T[],
error: [] as Error[]
};
private readonly listeners = {
data: [] as { (data: T): void }[],
error: [] as { (error: Error): void }[],
end: [] as { (): void }[]
};
private readonly pendingWritePromises: Function[] = [];
constructor(private reducer: IReducer<T>, private options?: WriteableStreamOptions) { }
pause(): void {
if (this.state.destroyed) {
return;
}
this.state.flowing = false;
}
resume(): void {
if (this.state.destroyed) {
return;
}
if (!this.state.flowing) {
this.state.flowing = true;
// emit buffered events
this.flowData();
this.flowErrors();
this.flowEnd();
}
}
write(data: T): void | Promise<void> {
if (this.state.destroyed) {
return;
}
// flowing: directly send the data to listeners
if (this.state.flowing) {
this.listeners.data.forEach(listener => listener(data));
}
// not yet flowing: buffer data until flowing
else {
this.buffer.data.push(data);
// highWaterMark: if configured, signal back when buffer reached limits
if (typeof this.options?.highWaterMark === 'number' && this.buffer.data.length > this.options.highWaterMark) {
return new Promise(resolve => this.pendingWritePromises.push(resolve));
}
}
}
error(error: Error): void {
if (this.state.destroyed) {
return;
}
// flowing: directly send the error to listeners
if (this.state.flowing) {
this.listeners.error.forEach(listener => listener(error));
}
// not yet flowing: buffer errors until flowing
else {
this.buffer.error.push(error);
}
}
end(result?: T | Error): void {
if (this.state.destroyed) {
return;
}
// end with data or error if provided
if (result instanceof Error) {
this.error(result);
} else if (result) {
this.write(result);
}
// flowing: send end event to listeners
if (this.state.flowing) {
this.listeners.end.forEach(listener => listener());
this.destroy();
}
// not yet flowing: remember state
else {
this.state.ended = true;
}
}
on(event: 'data', callback: (data: T) => void): void;
on(event: 'error', callback: (err: Error) => void): void;
on(event: 'end', callback: () => void): void;
on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
if (this.state.destroyed) {
return;
}
switch (event) {
case 'data':
this.listeners.data.push(callback);
// switch into flowing mode as soon as the first 'data'
// listener is added and we are not yet in flowing mode
this.resume();
break;
case 'end':
this.listeners.end.push(callback);
// emit 'end' event directly if we are flowing
// and the end has already been reached
//
// finish() when it went through
if (this.state.flowing && this.flowEnd()) {
this.destroy();
}
break;
case 'error':
this.listeners.error.push(callback);
// emit buffered 'error' events unless done already
// now that we know that we have at least one listener
if (this.state.flowing) {
this.flowErrors();
}
break;
}
}
removeListener(event: string, callback: Function): void {
if (this.state.destroyed) {
return;
}
let listeners: unknown[] | undefined = undefined;
switch (event) {
case 'data':
listeners = this.listeners.data;
break;
case 'end':
listeners = this.listeners.end;
break;
case 'error':
listeners = this.listeners.error;
break;
}
if (listeners) {
const index = listeners.indexOf(callback);
if (index >= 0) {
listeners.splice(index, 1);
}
}
}
private flowData(): void {
if (this.buffer.data.length > 0) {
const fullDataBuffer = this.reducer(this.buffer.data);
this.listeners.data.forEach(listener => listener(fullDataBuffer));
this.buffer.data.length = 0;
// When the buffer is empty, resolve all pending writers
const pendingWritePromises = [...this.pendingWritePromises];
this.pendingWritePromises.length = 0;
pendingWritePromises.forEach(pendingWritePromise => pendingWritePromise());
}
}
private flowErrors(): void {
if (this.listeners.error.length > 0) {
for (const error of this.buffer.error) {
this.listeners.error.forEach(listener => listener(error));
}
this.buffer.error.length = 0;
}
}
private flowEnd(): boolean {
if (this.state.ended) {
this.listeners.end.forEach(listener => listener());
return this.listeners.end.length > 0;
}
return false;
}
destroy(): void {
if (!this.state.destroyed) {
this.state.destroyed = true;
this.state.ended = true;
this.buffer.data.length = 0;
this.buffer.error.length = 0;
this.listeners.data.length = 0;
this.listeners.error.length = 0;
this.listeners.end.length = 0;
this.pendingWritePromises.length = 0;
}
}
}
/**
* Helper to fully read a T readable into a T.
*/
export function consumeReadable<T>(readable: Readable<T>, reducer: IReducer<T>): T {
const chunks: T[] = [];
let chunk: T | null;
while ((chunk = readable.read()) !== null) {
chunks.push(chunk);
}
return reducer(chunks);
}
/**
* Helper to read a T readable up to a maximum of chunks. If the limit is
* reached, will return a readable instead to ensure all data can still
* be read.
*/
export function peekReadable<T>(readable: Readable<T>, reducer: IReducer<T>, maxChunks: number): T | Readable<T> {
const chunks: T[] = [];
let chunk: T | null | undefined = undefined;
while ((chunk = readable.read()) !== null && chunks.length < maxChunks) {
chunks.push(chunk);
}
// If the last chunk is null, it means we reached the end of
// the readable and return all the data at once
if (chunk === null && chunks.length > 0) {
return reducer(chunks);
}
// Otherwise, we still have a chunk, it means we reached the maxChunks
// value and as such we return a new Readable that first returns
// the existing read chunks and then continues with reading from
// the underlying readable.
return {
read: () => {
// First consume chunks from our array
if (chunks.length > 0) {
return chunks.shift()!;
}
// Then ensure to return our last read chunk
if (typeof chunk !== 'undefined') {
const lastReadChunk = chunk;
// explicitly use undefined here to indicate that we consumed
// the chunk, which could have either been null or valued.
chunk = undefined;
return lastReadChunk;
}
// Finally delegate back to the Readable
return readable.read();
}
};
}
/**
* Helper to fully read a T stream into a T.
*/
export function consumeStream<T>(stream: ReadableStreamEvents<T>, reducer: IReducer<T>): Promise<T> {
return new Promise((resolve, reject) => {
const chunks: T[] = [];
stream.on('data', data => chunks.push(data));
stream.on('error', error => reject(error));
stream.on('end', () => resolve(reducer(chunks)));
});
}
/**
* Helper to peek up to `maxChunks` into a stream. The return type signals if
* the stream has ended or not. If not, caller needs to add a `data` listener
* to continue reading.
*/
export function peekStream<T>(stream: ReadableStream<T>, maxChunks: number): Promise<ReadableBufferedStream<T>> {
return new Promise((resolve, reject) => {
const streamListeners = new DisposableStore();
// Data Listener
const buffer: T[] = [];
const dataListener = (chunk: T) => {
// Add to buffer
buffer.push(chunk);
// We reached maxChunks and thus need to return
if (buffer.length > maxChunks) {
// Dispose any listeners and ensure to pause the
// stream so that it can be consumed again by caller
streamListeners.dispose();
stream.pause();
return resolve({ stream, buffer, ended: false });
}
};
streamListeners.add(toDisposable(() => stream.removeListener('data', dataListener)));
stream.on('data', dataListener);
// Error Listener
const errorListener = (error: Error) => {
return reject(error);
};
streamListeners.add(toDisposable(() => stream.removeListener('error', errorListener)));
stream.on('error', errorListener);
const endListener = () => {
return resolve({ stream, buffer, ended: true });
};
streamListeners.add(toDisposable(() => stream.removeListener('end', endListener)));
stream.on('end', endListener);
});
}
/**
* Helper to create a readable stream from an existing T.
*/
export function toStream<T>(t: T, reducer: IReducer<T>): ReadableStream<T> {
const stream = newWriteableStream<T>(reducer);
stream.end(t);
return stream;
}
/**
* Helper to convert a T into a Readable<T>.
*/
export function toReadable<T>(t: T): Readable<T> {
let consumed = false;
return {
read: () => {
if (consumed) {
return null;
}
consumed = true;
return t;
}
};
}
/**
* Helper to transform a readable stream into another stream.
*/
export function transform<Original, Transformed>(stream: ReadableStreamEvents<Original>, transformer: ITransformer<Original, Transformed>, reducer: IReducer<Transformed>): ReadableStream<Transformed> {
const target = newWriteableStream<Transformed>(reducer);
stream.on('data', data => target.write(transformer.data(data)));
stream.on('end', () => target.end());
stream.on('error', error => target.error(transformer.error ? transformer.error(error) : error));
return target;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Color } from 'vs/base/common/color';
export type styleFn = (colors: { [name: string]: Color | undefined }) => void;
export interface IThemable {
style: styleFn;
}

View File

@@ -0,0 +1,281 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
/**
* @returns whether the provided parameter is a JavaScript Array or not.
*/
export function isArray<T>(array: T | {}): array is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[] {
return Array.isArray(array);
}
/**
* @returns whether the provided parameter is a JavaScript String or not.
*/
export function isString(str: any): str is string {
return (typeof str === 'string');
}
/**
* @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 Array.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): obj is Object {
// 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 === '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 {
return (typeof obj === 'number' && !isNaN(obj));
}
/**
* @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): obj is undefined {
return (typeof obj === 'undefined');
}
/**
* @returns whether the provided parameter is defined.
*/
export function isDefined<T>(arg: T | null | undefined): arg is T {
return !isUndefinedOrNull(arg);
}
/**
* @returns whether the provided parameter is undefined or null.
*/
export function isUndefinedOrNull(obj: any): obj is undefined | null {
return (isUndefined(obj) || obj === null);
}
export function assertType(condition: any, type?: string): asserts condition {
if (!condition) {
throw new Error(type ? `Unexpected type, expected '${type}'` : 'Unexpected type');
}
}
/**
* Asserts that the argument passed in is neither undefined nor null.
*/
export function assertIsDefined<T>(arg: T | null | undefined): T {
if (isUndefinedOrNull(arg)) {
throw new Error('Assertion Failed: argument is undefined or null');
}
return arg;
}
/**
* Asserts that each argument passed in is neither undefined nor null.
*/
export function assertAllDefined<T1, T2>(t1: T1 | null | undefined, t2: T2 | null | undefined): [T1, T2];
export function assertAllDefined<T1, T2, T3>(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined): [T1, T2, T3];
export function assertAllDefined<T1, T2, T3, T4>(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined, t4: T4 | null | undefined): [T1, T2, T3, T4];
export function assertAllDefined(...args: (unknown | null | undefined)[]): unknown[] {
const result = [];
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (isUndefinedOrNull(arg)) {
throw new Error(`Assertion Failed: argument at index ${i} is undefined or null`);
}
result.push(arg);
}
return result;
}
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 === 'function');
}
/**
* @returns whether the provided parameters is are JavaScript Function or not.
*/
export function areFunctions(...objects: any[]): boolean {
return objects.length > 0 && objects.every(isFunction);
}
export type TypeConstraint = string | Function;
export function validateConstraints(args: any[], constraints: Array<TypeConstraint | undefined>): 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 | undefined): void {
if (isString(constraint)) {
if (typeof arg !== constraint) {
throw new Error(`argument does not match constraint: typeof ${constraint}`);
}
} else if (isFunction(constraint)) {
try {
if (arg instanceof constraint) {
return;
}
} catch {
// ignore
}
if (!isUndefinedOrNull(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`);
}
}
export function getAllPropertyNames(obj: object): string[] {
let res: string[] = [];
let proto = Object.getPrototypeOf(obj);
while (Object.prototype !== proto) {
res = res.concat(Object.getOwnPropertyNames(proto));
proto = Object.getPrototypeOf(proto);
}
return res;
}
export function getAllMethodNames(obj: object): string[] {
const methods: string[] = [];
for (const prop of getAllPropertyNames(obj)) {
if (typeof (obj as any)[prop] === 'function') {
methods.push(prop);
}
}
return methods;
}
export function createProxyObject<T extends object>(methodNames: string[], invoke: (method: string, args: any[]) => any): T {
const createProxyMethod = (method: string): () => any => {
return function () {
const args = Array.prototype.slice.call(arguments, 0);
return invoke(method, args);
};
};
let result = {} as T;
for (const methodName of methodNames) {
(<any>result)[methodName] = createProxyMethod(methodName);
}
return result;
}
/**
* Converts null to undefined, passes all other values through.
*/
export function withNullAsUndefined<T>(x: T | null): T | undefined {
return x === null ? undefined : x;
}
/**
* Converts undefined to null, passes all other values through.
*/
export function withUndefinedAsNull<T>(x: T | undefined): T | null {
return typeof x === 'undefined' ? null : x;
}
/**
* Allows to add a first parameter to functions of a type.
*/
export type AddFirstParameterToFunctions<Target, TargetFunctionsReturnType, FirstParameter> = {
// For every property
[K in keyof Target]:
// Function: add param to function
Target[K] extends (...args: any) => TargetFunctionsReturnType ? (firstArg: FirstParameter, ...args: Parameters<Target[K]>) => ReturnType<Target[K]> :
// Else: just leave as is
Target[K]
};
/**
* Mapped-type that replaces all occurrences of URI with UriComponents
*/
export type UriDto<T> = { [K in keyof T]: T[K] extends URI
? UriComponents
: UriDto<T[K]> };
/**
* Mapped-type that replaces all occurrences of URI with UriComponents and
* drops all functions.
*/
export type Dto<T> = T extends { toJSON(): infer U }
? U
: T extends object
? { [k in keyof T]: Dto<T[k]>; }
: T;
export function NotImplementedProxy<T>(name: string): { new(): T } {
return <any>class {
constructor() {
return new Proxy({}, {
get(target: any, prop: PropertyKey) {
if (target[prop]) {
return target[prop];
}
throw new Error(`Not Implemented: ${name}->${String(prop)}`);
}
});
}
};
}

View File

@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const enum Constants {
/**
* MAX SMI (SMall Integer) as defined in v8.
* one bit is lost for boxing/unboxing flag.
* one bit is lost for sign flag.
* See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
*/
MAX_SAFE_SMALL_INTEGER = 1 << 30,
/**
* MIN SMI (SMall Integer) as defined in v8.
* one bit is lost for boxing/unboxing flag.
* one bit is lost for sign flag.
* See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
*/
MIN_SAFE_SMALL_INTEGER = -(1 << 30),
/**
* Max unsigned integer that fits on 8 bits.
*/
MAX_UINT_8 = 255, // 2^8 - 1
/**
* Max unsigned integer that fits on 16 bits.
*/
MAX_UINT_16 = 65535, // 2^16 - 1
/**
* Max unsigned integer that fits on 32 bits.
*/
MAX_UINT_32 = 4294967295, // 2^32 - 1
UNICODE_SUPPLEMENTARY_PLANE_BEGIN = 0x010000
}
export function toUint8(v: number): number {
if (v < 0) {
return 0;
}
if (v > Constants.MAX_UINT_8) {
return Constants.MAX_UINT_8;
}
return v | 0;
}
export function toUint32(v: number): number {
if (v < 0) {
return 0;
}
if (v > Constants.MAX_UINT_32) {
return Constants.MAX_UINT_32;
}
return v | 0;
}

View File

@@ -0,0 +1,700 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isWindows } from 'vs/base/common/platform';
import { CharCode } from 'vs/base/common/charCode';
import * as paths from 'vs/base/common/path';
const _schemePattern = /^\w[\w\d+.-]*$/;
const _singleSlashStart = /^\//;
const _doubleSlashStart = /^\/\//;
function _validateUri(ret: URI, _strict?: boolean): void {
// scheme, must be set
if (!ret.scheme && _strict) {
throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`);
}
// 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 ("//")');
}
}
}
}
// for a while we allowed uris *without* schemes and this is the migration
// for them, e.g. an uri without scheme and without strict-mode warns and falls
// back to the file-scheme. that should cause the least carnage and still be a
// clear warning
function _schemeFix(scheme: string, _strict: boolean): string {
if (!scheme && !_strict) {
return 'file';
}
return scheme;
}
// implements a bit of https://tools.ietf.org/html/rfc3986#section-5
function _referenceResolution(scheme: string, path: string): string {
// the slash-character is our 'default base' as we don't
// support constructing URIs relative to other URIs. This
// also means that we alter and potentially break paths.
// see https://tools.ietf.org/html/rfc3986#section-5.1.4
switch (scheme) {
case 'https':
case 'http':
case 'file':
if (!path) {
path = _slash;
} else if (path[0] !== _slash) {
path = _slash + path;
}
break;
}
return path;
}
const _empty = '';
const _slash = '/';
const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
/**
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
* This class is a simple parser which creates the basic component parts
* (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
* and encoding.
*
* ```txt
* foo://example.com:8042/over/there?name=ferret#nose
* \_/ \______________/\_________/ \_________/ \__/
* | | | | |
* scheme authority path query fragment
* | _____________________|__
* / \ / \
* urn:example:animal:ferret:nose
* ```
*/
export class URI implements UriComponents {
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'
&& typeof (<URI>thing).fsPath === 'function'
&& typeof (<URI>thing).with === 'function'
&& typeof (<URI>thing).toString === 'function';
}
/**
* 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;
/**
* @internal
*/
protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean);
/**
* @internal
*/
protected constructor(components: UriComponents);
/**
* @internal
*/
protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) {
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 = _schemeFix(schemeOrData, _strict);
this.authority = authority || _empty;
this.path = _referenceResolution(this.scheme, path || _empty);
this.query = query || _empty;
this.fragment = fragment || _empty;
_validateUri(this, _strict);
}
}
// ---- filesystem path -----------------------
/**
* Returns a string representing the corresponding file system path of this URI.
* Will handle UNC paths, normalizes windows drive letters to lower-case, and 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.
* * The result shall *not* be used for display purposes but for accessing a file on disk.
*
*
* The *difference* to `URI#path` is the use of the platform specific separator and the handling
* of UNC paths. See the below sample of a file-uri with an authority (UNC path).
*
* ```ts
const u = URI.parse('file://server/c$/folder/file.txt')
u.authority === 'server'
u.path === '/shares/c$/file.txt'
u.fsPath === '\\server\c$\folder\file.txt'
```
*
* Using `URI#path` to read a file (using fs-apis) would not be enough because parts of the path,
* namely the server name, would be missing. Therefore `URI#fsPath` exists - it's sugar to ease working
* with URIs that represent files on disk (`file` scheme).
*/
get fsPath(): string {
// if (this.scheme !== 'file') {
// console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`);
// }
return uriToFsPath(this, false);
}
// ---- modify to new -------------------------
with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null }): URI {
if (!change) {
return this;
}
let { scheme, authority, path, query, fragment } = change;
if (scheme === undefined) {
scheme = this.scheme;
} else if (scheme === null) {
scheme = _empty;
}
if (authority === undefined) {
authority = this.authority;
} else if (authority === null) {
authority = _empty;
}
if (path === undefined) {
path = this.path;
} else if (path === null) {
path = _empty;
}
if (query === undefined) {
query = this.query;
} else if (query === null) {
query = _empty;
}
if (fragment === undefined) {
fragment = this.fragment;
} else if (fragment === null) {
fragment = _empty;
}
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 ------------------------
/**
* Creates a new URI from a string, e.g. `http://www.msft.com/some/path`,
* `file:///usr/home`, or `scheme:with/path`.
*
* @param value A string which represents an URI (see `URI#toString`).
*/
static parse(value: string, _strict: boolean = false): URI {
const match = _regexp.exec(value);
if (!match) {
return new Uri(_empty, _empty, _empty, _empty, _empty);
}
return new Uri(
match[2] || _empty,
percentDecode(match[4] || _empty),
percentDecode(match[5] || _empty),
percentDecode(match[7] || _empty),
percentDecode(match[9] || _empty),
_strict
);
}
/**
* Creates a new URI from a file system path, e.g. `c:\my\files`,
* `/usr/home`, or `\\server\share\some\path`.
*
* The *difference* between `URI#parse` and `URI#file` is that the latter treats the argument
* as path, not as stringified-uri. E.g. `URI.file(path)` is **not the same as**
* `URI.parse('file://' + path)` because the path might contain characters that are
* interpreted (# and ?). See the following sample:
* ```ts
const good = URI.file('/coding/c#/project1');
good.scheme === 'file';
good.path === '/coding/c#/project1';
good.fragment === '';
const bad = URI.parse('file://' + '/coding/c#/project1');
bad.scheme === 'file';
bad.path === '/coding/c'; // path is now broken
bad.fragment === '/project1';
```
*
* @param path A file system path (see `URI#fsPath`)
*/
static file(path: string): URI {
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 (isWindows) {
path = path.replace(/\\/g, _slash);
}
// check for authority as used in UNC shares
// or use the path as given
if (path[0] === _slash && path[1] === _slash) {
const idx = path.indexOf(_slash, 2);
if (idx === -1) {
authority = path.substring(2);
path = _slash;
} else {
authority = path.substring(2, idx);
path = path.substring(idx) || _slash;
}
}
return new Uri('file', authority, path, _empty, _empty);
}
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,
);
}
/**
* Join a URI path with path fragments and normalizes the resulting path.
*
* @param uri The input URI.
* @param pathFragment The path fragment to add to the URI path.
* @returns The resulting URI.
*/
static joinPath(uri: URI, ...pathFragment: string[]): URI {
if (!uri.path) {
throw new Error(`[UriError]: cannot call joinPaths on URI without path`);
}
let newPath: string;
if (isWindows && uri.scheme === 'file') {
newPath = URI.file(paths.win32.join(uriToFsPath(uri, true), ...pathFragment)).path;
} else {
newPath = paths.posix.join(uri.path, ...pathFragment);
}
return uri.with({ path: newPath });
}
// ---- printing/externalize ---------------------------
/**
* Creates a string representation for this URI. It's guaranteed that calling
* `URI.parse` with the result of this function creates an URI which is equal
* to this URI.
*
* * The result shall *not* be used for display purposes but for externalization or transport.
* * The result will be encoded using the percentage encoding and encoding happens mostly
* ignore the scheme-specific encoding rules.
*
* @param skipEncoding Do not encode the result, default is `false`
*/
toString(skipEncoding: boolean = false): string {
return _asFormatted(this, skipEncoding);
}
toJSON(): UriComponents {
return this;
}
static revive(data: UriComponents | URI): URI;
static revive(data: UriComponents | URI | undefined): URI | undefined;
static revive(data: UriComponents | URI | null): URI | null;
static revive(data: UriComponents | URI | undefined | null): URI | undefined | null;
static revive(data: UriComponents | URI | undefined | null): URI | undefined | null {
if (!data) {
return data;
} else if (data instanceof URI) {
return data;
} else {
const result = new Uri(data);
result._formatted = (<UriState>data).external;
result._fsPath = (<UriState>data)._sep === _pathSepMarker ? (<UriState>data).fsPath : null;
return result;
}
}
}
export interface UriComponents {
scheme: string;
authority: string;
path: string;
query: string;
fragment: string;
}
interface UriState extends UriComponents {
$mid: number;
external: string;
fsPath: string;
_sep: 1 | undefined;
}
const _pathSepMarker = isWindows ? 1 : undefined;
// This class exists so that URI is compatibile with vscode.Uri (API).
class Uri extends URI {
_formatted: string | null = null;
_fsPath: string | null = null;
get fsPath(): string {
if (!this._fsPath) {
this._fsPath = uriToFsPath(this, false);
}
return this._fsPath;
}
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);
}
}
toJSON(): UriComponents {
const res = <UriState>{
$mid: 1
};
// cached state
if (this._fsPath) {
res.fsPath = this._fsPath;
res._sep = _pathSepMarker;
}
if (this._formatted) {
res.external = this._formatted;
}
// uri components
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;
}
}
// reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2
const encodeTable: { [ch: number]: string } = {
[CharCode.Colon]: '%3A', // gen-delims
[CharCode.Slash]: '%2F',
[CharCode.QuestionMark]: '%3F',
[CharCode.Hash]: '%23',
[CharCode.OpenSquareBracket]: '%5B',
[CharCode.CloseSquareBracket]: '%5D',
[CharCode.AtSign]: '%40',
[CharCode.ExclamationMark]: '%21', // sub-delims
[CharCode.DollarSign]: '%24',
[CharCode.Ampersand]: '%26',
[CharCode.SingleQuote]: '%27',
[CharCode.OpenParen]: '%28',
[CharCode.CloseParen]: '%29',
[CharCode.Asterisk]: '%2A',
[CharCode.Plus]: '%2B',
[CharCode.Comma]: '%2C',
[CharCode.Semicolon]: '%3B',
[CharCode.Equals]: '%3D',
[CharCode.Space]: '%20',
};
function encodeURIComponentFast(uriComponent: string, allowSlash: boolean): string {
let res: string | undefined = undefined;
let nativeEncodePos = -1;
for (let pos = 0; pos < uriComponent.length; pos++) {
const code = uriComponent.charCodeAt(pos);
// unreserved characters: https://tools.ietf.org/html/rfc3986#section-2.3
if (
(code >= CharCode.a && code <= CharCode.z)
|| (code >= CharCode.A && code <= CharCode.Z)
|| (code >= CharCode.Digit0 && code <= CharCode.Digit9)
|| code === CharCode.Dash
|| code === CharCode.Period
|| code === CharCode.Underline
|| code === CharCode.Tilde
|| (allowSlash && code === CharCode.Slash)
) {
// check if we are delaying native encode
if (nativeEncodePos !== -1) {
res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos));
nativeEncodePos = -1;
}
// check if we write into a new string (by default we try to return the param)
if (res !== undefined) {
res += uriComponent.charAt(pos);
}
} else {
// encoding needed, we need to allocate a new string
if (res === undefined) {
res = uriComponent.substr(0, pos);
}
// check with default table first
const escaped = encodeTable[code];
if (escaped !== undefined) {
// check if we are delaying native encode
if (nativeEncodePos !== -1) {
res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos));
nativeEncodePos = -1;
}
// append escaped variant to result
res += escaped;
} else if (nativeEncodePos === -1) {
// use native encode only when needed
nativeEncodePos = pos;
}
}
}
if (nativeEncodePos !== -1) {
res += encodeURIComponent(uriComponent.substring(nativeEncodePos));
}
return res !== undefined ? res : uriComponent;
}
function encodeURIComponentMinimal(path: string): string {
let res: string | undefined = undefined;
for (let pos = 0; pos < path.length; pos++) {
const code = path.charCodeAt(pos);
if (code === CharCode.Hash || code === CharCode.QuestionMark) {
if (res === undefined) {
res = path.substr(0, pos);
}
res += encodeTable[code];
} else {
if (res !== undefined) {
res += path[pos];
}
}
}
return res !== undefined ? res : path;
}
/**
* Compute `fsPath` for the given uri
*/
export function uriToFsPath(uri: URI, keepDriveLetterCasing: boolean): string {
let value: string;
if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') {
// unc path: file://shares/c$/far/boo
value = `//${uri.authority}${uri.path}`;
} else if (
uri.path.charCodeAt(0) === CharCode.Slash
&& (uri.path.charCodeAt(1) >= CharCode.A && uri.path.charCodeAt(1) <= CharCode.Z || uri.path.charCodeAt(1) >= CharCode.a && uri.path.charCodeAt(1) <= CharCode.z)
&& uri.path.charCodeAt(2) === CharCode.Colon
) {
if (!keepDriveLetterCasing) {
// windows drive letter: file:///c:/far/boo
value = uri.path[1].toLowerCase() + uri.path.substr(2);
} else {
value = uri.path.substr(1);
}
} else {
// other path
value = uri.path;
}
if (isWindows) {
value = value.replace(/\//g, '\\');
}
return value;
}
/**
* Create the external version of a uri
*/
function _asFormatted(uri: URI, skipEncoding: boolean): string {
const encoder = !skipEncoding
? encodeURIComponentFast
: encodeURIComponentMinimal;
let res = '';
let { scheme, authority, path, query, fragment } = uri;
if (scheme) {
res += scheme;
res += ':';
}
if (authority || scheme === 'file') {
res += _slash;
res += _slash;
}
if (authority) {
let idx = authority.indexOf('@');
if (idx !== -1) {
// <user>@<auth>
const userinfo = authority.substr(0, idx);
authority = authority.substr(idx + 1);
idx = userinfo.indexOf(':');
if (idx === -1) {
res += encoder(userinfo, false);
} else {
// <user>:<pass>@<auth>
res += encoder(userinfo.substr(0, idx), false);
res += ':';
res += encoder(userinfo.substr(idx + 1), false);
}
res += '@';
}
authority = authority.toLowerCase();
idx = authority.indexOf(':');
if (idx === -1) {
res += encoder(authority, false);
} else {
// <auth>:<port>
res += encoder(authority.substr(0, idx), false);
res += authority.substr(idx);
}
}
if (path) {
// lower-case windows drive letters in /C:/fff or C:/fff
if (path.length >= 3 && path.charCodeAt(0) === CharCode.Slash && path.charCodeAt(2) === CharCode.Colon) {
const code = path.charCodeAt(1);
if (code >= CharCode.A && code <= CharCode.Z) {
path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3
}
} else if (path.length >= 2 && path.charCodeAt(1) === CharCode.Colon) {
const code = path.charCodeAt(0);
if (code >= CharCode.A && code <= CharCode.Z) {
path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "/c:".length === 3
}
}
// encode the rest of the path
res += encoder(path, true);
}
if (query) {
res += '?';
res += encoder(query, false);
}
if (fragment) {
res += '#';
res += !skipEncoding ? encodeURIComponentFast(fragment, false) : fragment;
}
return res;
}
// --- decode
function decodeURIComponentGraceful(str: string): string {
try {
return decodeURIComponent(str);
} catch {
if (str.length > 3) {
return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3));
} else {
return str;
}
}
}
const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
function percentDecode(str: string): string {
if (!str.match(_rEncodedAsHex)) {
return str;
}
return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
}

View File

@@ -0,0 +1,155 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
import { MarshalledObject } from 'vs/base/common/marshalling';
export interface IURITransformer {
transformIncoming(uri: UriComponents): UriComponents;
transformOutgoing(uri: UriComponents): UriComponents;
transformOutgoingURI(uri: URI): URI;
transformOutgoingScheme(scheme: string): string;
}
export interface UriParts {
scheme: string;
authority?: string;
path?: string;
}
export interface IRawURITransformer {
transformIncoming(uri: UriParts): UriParts;
transformOutgoing(uri: UriParts): UriParts;
transformOutgoingScheme(scheme: string): string;
}
function toJSON(uri: URI): UriComponents {
return <UriComponents><any>uri.toJSON();
}
export class URITransformer implements IURITransformer {
private readonly _uriTransformer: IRawURITransformer;
constructor(uriTransformer: IRawURITransformer) {
this._uriTransformer = uriTransformer;
}
public transformIncoming(uri: UriComponents): UriComponents {
const result = this._uriTransformer.transformIncoming(uri);
return (result === uri ? uri : toJSON(URI.from(result)));
}
public transformOutgoing(uri: UriComponents): UriComponents {
const result = this._uriTransformer.transformOutgoing(uri);
return (result === uri ? uri : toJSON(URI.from(result)));
}
public transformOutgoingURI(uri: URI): URI {
const result = this._uriTransformer.transformOutgoing(uri);
return (result === uri ? uri : URI.from(result));
}
public transformOutgoingScheme(scheme: string): string {
return this._uriTransformer.transformOutgoingScheme(scheme);
}
}
export const DefaultURITransformer: IURITransformer = new class {
transformIncoming(uri: UriComponents) {
return uri;
}
transformOutgoing(uri: UriComponents): UriComponents {
return uri;
}
transformOutgoingURI(uri: URI): URI {
return uri;
}
transformOutgoingScheme(scheme: string): string {
return scheme;
}
};
function _transformOutgoingURIs(obj: any, transformer: IURITransformer, depth: number): any {
if (!obj || depth > 200) {
return null;
}
if (typeof obj === 'object') {
if (obj instanceof URI) {
return transformer.transformOutgoing(obj);
}
// walk object (or array)
for (let key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
const r = _transformOutgoingURIs(obj[key], transformer, depth + 1);
if (r !== null) {
obj[key] = r;
}
}
}
}
return null;
}
export function transformOutgoingURIs<T>(obj: T, transformer: IURITransformer): T {
const result = _transformOutgoingURIs(obj, transformer, 0);
if (result === null) {
// no change
return obj;
}
return result;
}
function _transformIncomingURIs(obj: any, transformer: IURITransformer, revive: boolean, depth: number): any {
if (!obj || depth > 200) {
return null;
}
if (typeof obj === 'object') {
if ((<MarshalledObject>obj).$mid === 1) {
return revive ? URI.revive(transformer.transformIncoming(obj)) : transformer.transformIncoming(obj);
}
// walk object (or array)
for (let key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
const r = _transformIncomingURIs(obj[key], transformer, revive, depth + 1);
if (r !== null) {
obj[key] = r;
}
}
}
}
return null;
}
export function transformIncomingURIs<T>(obj: T, transformer: IURITransformer): T {
const result = _transformIncomingURIs(obj, transformer, false, 0);
if (result === null) {
// no change
return obj;
}
return result;
}
export function transformAndReviveIncomingURIs<T>(obj: T, transformer: IURITransformer): T {
const result = _transformIncomingURIs(obj, transformer, true, 0);
if (result === null) {
// no change
return obj;
}
return result;
}

View File

@@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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);
}
// prep-work
const _data = new Uint8Array(16);
const _hex: string[] = [];
for (let i = 0; i < 256; i++) {
_hex.push(i.toString(16).padStart(2, '0'));
}
// todo@joh node nodejs use `crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback
// todo@joh use browser-crypto
const _fillRandomValues = function (bucket: Uint8Array): Uint8Array {
for (let i = 0; i < bucket.length; i++) {
bucket[i] = Math.floor(Math.random() * 256);
}
return bucket;
};
export function generateUuid(): string {
// get data
_fillRandomValues(_data);
// set version bits
_data[6] = (_data[6] & 0x0f) | 0x40;
_data[8] = (_data[8] & 0x3f) | 0x80;
// print as string
let i = 0;
let result = '';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
return result;
}

View File

@@ -0,0 +1,388 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { transformErrorForSerialization } from 'vs/base/common/errors';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { isWeb } from 'vs/base/common/platform';
import * as types from 'vs/base/common/types';
const INITIALIZE = '$initialize';
export interface IWorker extends IDisposable {
getId(): number;
postMessage(message: any, transfer: ArrayBuffer[]): void;
}
export interface IWorkerCallback {
(message: any): 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 {
resolve: (value?: any) => void;
reject: (error?: any) => void;
}
interface IMessageHandler {
sendMessage(msg: any, transfer?: ArrayBuffer[]): void;
handleMessage(method: string, args: any[]): Promise<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[]): Promise<any> {
let req = String(++this._lastSentReq);
return new Promise<any>((resolve, reject) => {
this._pendingReplies[req] = {
resolve: resolve,
reject: reject
};
this._send({
vsWorker: this._workerId,
req: req,
method: method,
args: args
});
});
}
public handleMessage(message: IMessage): void {
if (!message || !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.reject(err);
return;
}
reply.resolve(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) => {
if (e.detail instanceof Error) {
// Loading errors have a detail property that points to the actual error
e.detail = transformErrorForSerialization(e.detail);
}
this._send({
vsWorker: this._workerId,
seq: req,
res: undefined,
err: transformErrorForSerialization(e)
});
});
}
private _send(msg: IRequestMessage | IReplyMessage): void {
let transfer: ArrayBuffer[] = [];
if (msg.req) {
const m = <IRequestMessage>msg;
for (let i = 0; i < m.args.length; i++) {
if (m.args[i] instanceof ArrayBuffer) {
transfer.push(m.args[i]);
}
}
} else {
const m = <IReplyMessage>msg;
if (m.res instanceof ArrayBuffer) {
transfer.push(m.res);
}
}
this._handler.sendMessage(msg, transfer);
}
}
export interface IWorkerClient<W> {
getProxyObject(): Promise<W>;
dispose(): void;
}
/**
* Main thread side
*/
export class SimpleWorkerClient<W extends object, H extends object> extends Disposable implements IWorkerClient<W> {
private readonly _worker: IWorker;
private readonly _onModuleLoaded: Promise<string[]>;
private readonly _protocol: SimpleWorkerProtocol;
private readonly _lazyProxy: Promise<W>;
constructor(workerFactory: IWorkerFactory, moduleId: string, host: H) {
super();
let lazyProxyReject: ((err: any) => void) | null = null;
this._worker = this._register(workerFactory.create(
'vs/base/common/worker/simpleWorker',
(msg: any) => {
this._protocol.handleMessage(msg);
},
(err: any) => {
// in Firefox, web workers fail lazily :(
// we will reject the proxy
if (lazyProxyReject) {
lazyProxyReject(err);
}
}
));
this._protocol = new SimpleWorkerProtocol({
sendMessage: (msg: any, transfer: ArrayBuffer[]): void => {
this._worker.postMessage(msg, transfer);
},
handleMessage: (method: string, args: any[]): Promise<any> => {
if (typeof (host as any)[method] !== 'function') {
return Promise.reject(new Error('Missing method ' + method + ' on main thread host.'));
}
try {
return Promise.resolve((host as any)[method].apply(host, args));
} catch (e) {
return Promise.reject(e);
}
}
});
this._protocol.setWorkerId(this._worker.getId());
// Gather loader configuration
let loaderConfiguration: any = null;
if (typeof (<any>self).require !== 'undefined' && typeof (<any>self).require.getConfig === 'function') {
// Get the configuration from the Monaco AMD Loader
loaderConfiguration = (<any>self).require.getConfig();
} else if (typeof (<any>self).requirejs !== 'undefined') {
// Get the configuration from requirejs
loaderConfiguration = (<any>self).requirejs.s.contexts._.config;
}
const hostMethods = types.getAllMethodNames(host);
// Send initialize message
this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [
this._worker.getId(),
JSON.parse(JSON.stringify(loaderConfiguration)),
moduleId,
hostMethods,
]);
// Create proxy to loaded code
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => {
return this._request(method, args);
};
this._lazyProxy = new Promise<W>((resolve, reject) => {
lazyProxyReject = reject;
this._onModuleLoaded.then((availableMethods: string[]) => {
resolve(types.createProxyObject<W>(availableMethods, proxyMethodRequest));
}, (e) => {
reject(e);
this._onError('Worker failed to load ' + moduleId, e);
});
});
}
public getProxyObject(): Promise<W> {
return this._lazyProxy;
}
private _request(method: string, args: any[]): Promise<any> {
return new Promise<any>((resolve, reject) => {
this._onModuleLoaded.then(() => {
this._protocol.sendMessage(method, args).then(resolve, reject);
}, reject);
});
}
private _onError(message: string, error?: any): void {
console.error(message);
console.info(error);
}
}
export interface IRequestHandler {
_requestHandlerBrand: any;
[prop: string]: any;
}
export interface IRequestHandlerFactory<H> {
(host: H): IRequestHandler;
}
/**
* Worker side
*/
export class SimpleWorkerServer<H extends object> {
private _requestHandlerFactory: IRequestHandlerFactory<H> | null;
private _requestHandler: IRequestHandler | null;
private _protocol: SimpleWorkerProtocol;
constructor(postMessage: (msg: any, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IRequestHandlerFactory<H> | null) {
this._requestHandlerFactory = requestHandlerFactory;
this._requestHandler = null;
this._protocol = new SimpleWorkerProtocol({
sendMessage: (msg: any, transfer: ArrayBuffer[]): void => {
postMessage(msg, transfer);
},
handleMessage: (method: string, args: any[]): Promise<any> => this._handleMessage(method, args)
});
}
public onmessage(msg: any): void {
this._protocol.handleMessage(msg);
}
private _handleMessage(method: string, args: any[]): Promise<any> {
if (method === INITIALIZE) {
return this.initialize(<number>args[0], <any>args[1], <string>args[2], <string[]>args[3]);
}
if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') {
return Promise.reject(new Error('Missing requestHandler or method: ' + method));
}
try {
return Promise.resolve(this._requestHandler[method].apply(this._requestHandler, args));
} catch (e) {
return Promise.reject(e);
}
}
private initialize(workerId: number, loaderConfig: any, moduleId: string, hostMethods: string[]): Promise<string[]> {
this._protocol.setWorkerId(workerId);
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => {
return this._protocol.sendMessage(method, args);
};
const hostProxy = types.createProxyObject<H>(hostMethods, proxyMethodRequest);
if (this._requestHandlerFactory) {
// static request handler
this._requestHandler = this._requestHandlerFactory(hostProxy);
return Promise.resolve(types.getAllMethodNames(this._requestHandler));
}
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'];
}
}
// Since this is in a web worker, enable catching errors
loaderConfig.catchError = true;
(<any>self).require.config(loaderConfig);
}
return new Promise<string[]>((resolve, reject) => {
// Use the global require to be sure to get the global config
(<any>self).require([moduleId], (module: { create: IRequestHandlerFactory<H> }) => {
this._requestHandler = module.create(hostProxy);
if (!this._requestHandler) {
reject(new Error(`No RequestHandler!`));
return;
}
resolve(types.getAllMethodNames(this._requestHandler));
}, reject);
});
}
}
/**
* Called on the worker side
*/
export function create(postMessage: (msg: string) => void): SimpleWorkerServer<any> {
return new SimpleWorkerServer(postMessage, null);
}