mirror of
https://github.com/coder/code-server.git
synced 2026-05-08 13:27:25 +02:00
Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
254
lib/vscode/src/vs/base/common/actions.ts
Normal file
254
lib/vscode/src/vs/base/common/actions.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
20
lib/vscode/src/vs/base/common/amd.ts
Normal file
20
lib/vscode/src/vs/base/common/amd.ts
Normal 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));
|
||||
}
|
||||
590
lib/vscode/src/vs/base/common/arrays.ts
Normal file
590
lib/vscode/src/vs/base/common/arrays.ts
Normal 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)];
|
||||
}
|
||||
13
lib/vscode/src/vs/base/common/assert.ts
Normal file
13
lib/vscode/src/vs/base/common/assert.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
976
lib/vscode/src/vs/base/common/async.ts
Normal file
976
lib/vscode/src/vs/base/common/async.ts
Normal 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
|
||||
236
lib/vscode/src/vs/base/common/buffer.ts
Normal file
236
lib/vscode/src/vs/base/common/buffer.ts
Normal 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);
|
||||
}
|
||||
16
lib/vscode/src/vs/base/common/buildunit.json
Normal file
16
lib/vscode/src/vs/base/common/buildunit.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "vs/base",
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "vs",
|
||||
"internal": false
|
||||
}
|
||||
],
|
||||
"libs": [
|
||||
"lib.core.d.ts"
|
||||
],
|
||||
"sources": [
|
||||
"**/*.ts"
|
||||
],
|
||||
"declares": []
|
||||
}
|
||||
37
lib/vscode/src/vs/base/common/cache.ts
Normal file
37
lib/vscode/src/vs/base/common/cache.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
144
lib/vscode/src/vs/base/common/cancellation.ts
Normal file
144
lib/vscode/src/vs/base/common/cancellation.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
435
lib/vscode/src/vs/base/common/charCode.ts
Normal file
435
lib/vscode/src/vs/base/common/charCode.ts
Normal 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
|
||||
}
|
||||
135
lib/vscode/src/vs/base/common/codicon.ts
Normal file
135
lib/vscode/src/vs/base/common/codicon.ts
Normal 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;
|
||||
}
|
||||
515
lib/vscode/src/vs/base/common/codicons.ts
Normal file
515
lib/vscode/src/vs/base/common/codicons.ts
Normal 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 || '');
|
||||
}
|
||||
118
lib/vscode/src/vs/base/common/collections.ts
Normal file
118
lib/vscode/src/vs/base/common/collections.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
619
lib/vscode/src/vs/base/common/color.ts
Normal file
619
lib/vscode/src/vs/base/common/color.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
256
lib/vscode/src/vs/base/common/comparers.ts
Normal file
256
lib/vscode/src/vs/base/common/comparers.ts
Normal 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;
|
||||
}
|
||||
142
lib/vscode/src/vs/base/common/console.ts
Normal file
142
lib/vscode/src/vs/base/common/console.ts
Normal 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}`;
|
||||
}
|
||||
130
lib/vscode/src/vs/base/common/date.ts
Normal file
130
lib/vscode/src/vs/base/common/date.ts
Normal 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';
|
||||
}
|
||||
155
lib/vscode/src/vs/base/common/decorators.ts
Normal file
155
lib/vscode/src/vs/base/common/decorators.ts
Normal 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());
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
1029
lib/vscode/src/vs/base/common/diff/diff.ts
Normal file
1029
lib/vscode/src/vs/base/common/diff/diff.ts
Normal file
File diff suppressed because it is too large
Load Diff
60
lib/vscode/src/vs/base/common/diff/diffChange.ts
Normal file
60
lib/vscode/src/vs/base/common/diff/diffChange.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
83
lib/vscode/src/vs/base/common/errorMessage.ts
Normal file
83
lib/vscode/src/vs/base/common/errorMessage.ts
Normal 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.");
|
||||
}
|
||||
214
lib/vscode/src/vs/base/common/errors.ts
Normal file
214
lib/vscode/src/vs/base/common/errors.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
lib/vscode/src/vs/base/common/errorsWithActions.ts
Normal file
28
lib/vscode/src/vs/base/common/errorsWithActions.ts
Normal 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;
|
||||
}
|
||||
864
lib/vscode/src/vs/base/common/event.ts
Normal file
864
lib/vscode/src/vs/base/common/event.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
338
lib/vscode/src/vs/base/common/extpath.ts
Normal file
338
lib/vscode/src/vs/base/common/extpath.ts
Normal 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
|
||||
};
|
||||
}
|
||||
818
lib/vscode/src/vs/base/common/filters.ts
Normal file
818
lib/vscode/src/vs/base/common/filters.ts
Normal 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
|
||||
21
lib/vscode/src/vs/base/common/functional.ts
Normal file
21
lib/vscode/src/vs/base/common/functional.ts
Normal 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;
|
||||
}
|
||||
882
lib/vscode/src/vs/base/common/fuzzyScorer.ts
Normal file
882
lib/vscode/src/vs/base/common/fuzzyScorer.ts
Normal 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
|
||||
688
lib/vscode/src/vs/base/common/glob.ts
Normal file
688
lib/vscode/src/vs/base/common/glob.ts
Normal 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;
|
||||
}
|
||||
310
lib/vscode/src/vs/base/common/hash.ts
Normal file
310
lib/vscode/src/vs/base/common/hash.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
202
lib/vscode/src/vs/base/common/history.ts
Normal file
202
lib/vscode/src/vs/base/common/history.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
146
lib/vscode/src/vs/base/common/htmlContent.ts
Normal file
146
lib/vscode/src/vs/base/common/htmlContent.ts
Normal 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 };
|
||||
}
|
||||
21
lib/vscode/src/vs/base/common/idGenerator.ts
Normal file
21
lib/vscode/src/vs/base/common/idGenerator.ts
Normal 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#');
|
||||
17
lib/vscode/src/vs/base/common/insane/cgmanifest.json
Normal file
17
lib/vscode/src/vs/base/common/insane/cgmanifest.json
Normal 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
|
||||
}
|
||||
17
lib/vscode/src/vs/base/common/insane/insane.d.ts
vendored
Normal file
17
lib/vscode/src/vs/base/common/insane/insane.d.ts
vendored
Normal 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;
|
||||
474
lib/vscode/src/vs/base/common/insane/insane.js
Normal file
474
lib/vscode/src/vs/base/common/insane/insane.js
Normal 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 = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
var unescapes = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
''': "'"
|
||||
};
|
||||
var rescaped = /(&|<|>|"|')/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
|
||||
20
lib/vscode/src/vs/base/common/insane/insane.license.txt
Normal file
20
lib/vscode/src/vs/base/common/insane/insane.license.txt
Normal 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.
|
||||
85
lib/vscode/src/vs/base/common/iterator.ts
Normal file
85
lib/vscode/src/vs/base/common/iterator.ts
Normal 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; } }];
|
||||
}
|
||||
}
|
||||
1357
lib/vscode/src/vs/base/common/json.ts
Normal file
1357
lib/vscode/src/vs/base/common/json.ts
Normal file
File diff suppressed because it is too large
Load Diff
177
lib/vscode/src/vs/base/common/jsonEdit.ts
Normal file
177
lib/vscode/src/vs/base/common/jsonEdit.ts
Normal 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;
|
||||
}
|
||||
26
lib/vscode/src/vs/base/common/jsonErrorMessages.ts
Normal file
26
lib/vscode/src/vs/base/common/jsonErrorMessages.ts
Normal 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 '';
|
||||
}
|
||||
}
|
||||
248
lib/vscode/src/vs/base/common/jsonFormatter.ts
Normal file
248
lib/vscode/src/vs/base/common/jsonFormatter.ts
Normal 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;
|
||||
}
|
||||
80
lib/vscode/src/vs/base/common/jsonSchema.ts
Normal file
80
lib/vscode/src/vs/base/common/jsonSchema.ts
Normal 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)
|
||||
}
|
||||
602
lib/vscode/src/vs/base/common/keyCodes.ts
Normal file
602
lib/vscode/src/vs/base/common/keyCodes.ts
Normal 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)[];
|
||||
}
|
||||
190
lib/vscode/src/vs/base/common/keybindingLabels.ts
Normal file
190
lib/vscode/src/vs/base/common/keybindingLabels.ts
Normal 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);
|
||||
}
|
||||
124
lib/vscode/src/vs/base/common/keybindingParser.ts
Normal file
124
lib/vscode/src/vs/base/common/keybindingParser.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
398
lib/vscode/src/vs/base/common/labels.ts
Normal file
398
lib/vscode/src/vs/base/common/labels.ts
Normal 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: '' };
|
||||
}
|
||||
70
lib/vscode/src/vs/base/common/lazy.ts
Normal file
70
lib/vscode/src/vs/base/common/lazy.ts
Normal 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()));
|
||||
}
|
||||
}
|
||||
274
lib/vscode/src/vs/base/common/lifecycle.ts
Normal file
274
lib/vscode/src/vs/base/common/lifecycle.ts
Normal 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 */ }
|
||||
}
|
||||
142
lib/vscode/src/vs/base/common/linkedList.ts
Normal file
142
lib/vscode/src/vs/base/common/linkedList.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
55
lib/vscode/src/vs/base/common/linkedText.ts
Normal file
55
lib/vscode/src/vs/base/common/linkedText.ts
Normal 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);
|
||||
}
|
||||
990
lib/vscode/src/vs/base/common/map.ts
Normal file
990
lib/vscode/src/vs/base/common/map.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
17
lib/vscode/src/vs/base/common/marked/cgmanifest.json
Normal file
17
lib/vscode/src/vs/base/common/marked/cgmanifest.json
Normal 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
|
||||
}
|
||||
297
lib/vscode/src/vs/base/common/marked/marked.d.ts
vendored
Normal file
297
lib/vscode/src/vs/base/common/marked/marked.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
2627
lib/vscode/src/vs/base/common/marked/marked.js
Normal file
2627
lib/vscode/src/vs/base/common/marked/marked.js
Normal file
File diff suppressed because it is too large
Load Diff
19
lib/vscode/src/vs/base/common/marked/marked.license.txt
Normal file
19
lib/vscode/src/vs/base/common/marked/marked.license.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2011-2014, Christopher Jeffrey (https://github.com/chjj/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
78
lib/vscode/src/vs/base/common/marshalling.ts
Normal file
78
lib/vscode/src/vs/base/common/marshalling.ts
Normal 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;
|
||||
}
|
||||
318
lib/vscode/src/vs/base/common/mime.ts
Normal file
318
lib/vscode/src/vs/base/common/mime.ts
Normal 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;
|
||||
}
|
||||
50
lib/vscode/src/vs/base/common/navigator.ts
Normal file
50
lib/vscode/src/vs/base/common/navigator.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
174
lib/vscode/src/vs/base/common/network.ts
Normal file
174
lib/vscode/src/vs/base/common/network.ts
Normal 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();
|
||||
62
lib/vscode/src/vs/base/common/normalization.ts
Normal file
62
lib/vscode/src/vs/base/common/normalization.ts
Normal 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, '');
|
||||
};
|
||||
}
|
||||
})();
|
||||
36
lib/vscode/src/vs/base/common/numbers.ts
Normal file
36
lib/vscode/src/vs/base/common/numbers.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
228
lib/vscode/src/vs/base/common/objects.ts
Normal file
228
lib/vscode/src/vs/base/common/objects.ts
Normal 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];
|
||||
}
|
||||
204
lib/vscode/src/vs/base/common/paging.ts
Normal file
204
lib/vscode/src/vs/base/common/paging.ts
Normal 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]);
|
||||
}
|
||||
};
|
||||
}
|
||||
79
lib/vscode/src/vs/base/common/parsers.ts
Normal file
79
lib/vscode/src/vs/base/common/parsers.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
1506
lib/vscode/src/vs/base/common/path.ts
Normal file
1506
lib/vscode/src/vs/base/common/path.ts
Normal file
File diff suppressed because it is too large
Load Diff
22
lib/vscode/src/vs/base/common/performance.d.ts
vendored
Normal file
22
lib/vscode/src/vs/base/common/performance.d.ts
vendored
Normal 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;
|
||||
96
lib/vscode/src/vs/base/common/performance.js
Normal file
96
lib/vscode/src/vs/base/common/performance.js
Normal 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);
|
||||
}
|
||||
244
lib/vscode/src/vs/base/common/platform.ts
Normal file
244
lib/vscode/src/vs/base/common/platform.ts
Normal 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;
|
||||
}
|
||||
40
lib/vscode/src/vs/base/common/process.ts
Normal file
40
lib/vscode/src/vs/base/common/process.ts
Normal 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;
|
||||
127
lib/vscode/src/vs/base/common/processes.ts
Normal file
127
lib/vscode/src/vs/base/common/processes.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
60
lib/vscode/src/vs/base/common/range.ts
Normal file
60
lib/vscode/src/vs/base/common/range.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
187
lib/vscode/src/vs/base/common/resourceTree.ts
Normal file
187
lib/vscode/src/vs/base/common/resourceTree.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
468
lib/vscode/src/vs/base/common/resources.ts
Normal file
468
lib/vscode/src/vs/base/common/resources.ts
Normal 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 });
|
||||
}
|
||||
689
lib/vscode/src/vs/base/common/scanCode.ts
Normal file
689
lib/vscode/src/vs/base/common/scanCode.ts
Normal 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);
|
||||
})();
|
||||
493
lib/vscode/src/vs/base/common/scrollable.ts
Normal file
493
lib/vscode/src/vs/base/common/scrollable.ts
Normal 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);
|
||||
}
|
||||
46
lib/vscode/src/vs/base/common/search.ts
Normal file
46
lib/vscode/src/vs/base/common/search.ts
Normal 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);
|
||||
}
|
||||
17
lib/vscode/src/vs/base/common/semver/cgmanifest.json
Normal file
17
lib/vscode/src/vs/base/common/semver/cgmanifest.json
Normal 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
|
||||
}
|
||||
312
lib/vscode/src/vs/base/common/semver/semver.d.ts
vendored
Normal file
312
lib/vscode/src/vs/base/common/semver/semver.d.ts
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
11
lib/vscode/src/vs/base/common/semver/semver.js
Normal file
11
lib/vscode/src/vs/base/common/semver/semver.js
Normal file
File diff suppressed because one or more lines are too long
58
lib/vscode/src/vs/base/common/sequence.ts
Normal file
58
lib/vscode/src/vs/base/common/sequence.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
46
lib/vscode/src/vs/base/common/severity.ts
Normal file
46
lib/vscode/src/vs/base/common/severity.ts
Normal 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;
|
||||
203
lib/vscode/src/vs/base/common/skipList.ts
Normal file
203
lib/vscode/src/vs/base/common/skipList.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
40
lib/vscode/src/vs/base/common/stopwatch.ts
Normal file
40
lib/vscode/src/vs/base/common/stopwatch.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
576
lib/vscode/src/vs/base/common/stream.ts
Normal file
576
lib/vscode/src/vs/base/common/stream.ts
Normal 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;
|
||||
}
|
||||
1064
lib/vscode/src/vs/base/common/strings.ts
Normal file
1064
lib/vscode/src/vs/base/common/strings.ts
Normal file
File diff suppressed because one or more lines are too long
12
lib/vscode/src/vs/base/common/styler.ts
Normal file
12
lib/vscode/src/vs/base/common/styler.ts
Normal 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;
|
||||
}
|
||||
281
lib/vscode/src/vs/base/common/types.ts
Normal file
281
lib/vscode/src/vs/base/common/types.ts
Normal 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)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
59
lib/vscode/src/vs/base/common/uint.ts
Normal file
59
lib/vscode/src/vs/base/common/uint.ts
Normal 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;
|
||||
}
|
||||
700
lib/vscode/src/vs/base/common/uri.ts
Normal file
700
lib/vscode/src/vs/base/common/uri.ts
Normal 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));
|
||||
}
|
||||
155
lib/vscode/src/vs/base/common/uriIpc.ts
Normal file
155
lib/vscode/src/vs/base/common/uriIpc.ts
Normal 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;
|
||||
}
|
||||
61
lib/vscode/src/vs/base/common/uuid.ts
Normal file
61
lib/vscode/src/vs/base/common/uuid.ts
Normal 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;
|
||||
}
|
||||
388
lib/vscode/src/vs/base/common/worker/simpleWorker.ts
Normal file
388
lib/vscode/src/vs/base/common/worker/simpleWorker.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user