feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
+174
View File
@@ -0,0 +1,174 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from './event.js';
import { Disposable } from './lifecycle.js';
import * as nls from '../../nls.js';
export class Action extends Disposable {
constructor(id, label = '', cssClass = '', enabled = true, actionCallback) {
super();
this._onDidChange = this._register(new Emitter());
this.onDidChange = this._onDidChange.event;
this._enabled = true;
this._id = id;
this._label = label;
this._cssClass = cssClass;
this._enabled = enabled;
this._actionCallback = actionCallback;
}
get id() {
return this._id;
}
get label() {
return this._label;
}
set label(value) {
this._setLabel(value);
}
_setLabel(value) {
if (this._label !== value) {
this._label = value;
this._onDidChange.fire({ label: value });
}
}
get tooltip() {
return this._tooltip || '';
}
set tooltip(value) {
this._setTooltip(value);
}
_setTooltip(value) {
if (this._tooltip !== value) {
this._tooltip = value;
this._onDidChange.fire({ tooltip: value });
}
}
get class() {
return this._cssClass;
}
set class(value) {
this._setClass(value);
}
_setClass(value) {
if (this._cssClass !== value) {
this._cssClass = value;
this._onDidChange.fire({ class: value });
}
}
get enabled() {
return this._enabled;
}
set enabled(value) {
this._setEnabled(value);
}
_setEnabled(value) {
if (this._enabled !== value) {
this._enabled = value;
this._onDidChange.fire({ enabled: value });
}
}
get checked() {
return this._checked;
}
set checked(value) {
this._setChecked(value);
}
_setChecked(value) {
if (this._checked !== value) {
this._checked = value;
this._onDidChange.fire({ checked: value });
}
}
async run(event, data) {
if (this._actionCallback) {
await this._actionCallback(event);
}
}
}
export class ActionRunner extends Disposable {
constructor() {
super(...arguments);
this._onWillRun = this._register(new Emitter());
this.onWillRun = this._onWillRun.event;
this._onDidRun = this._register(new Emitter());
this.onDidRun = this._onDidRun.event;
}
async run(action, context) {
if (!action.enabled) {
return;
}
this._onWillRun.fire({ action });
let error = undefined;
try {
await this.runAction(action, context);
}
catch (e) {
error = e;
}
this._onDidRun.fire({ action, error });
}
async runAction(action, context) {
await action.run(context);
}
}
export class Separator {
constructor() {
this.id = Separator.ID;
this.label = '';
this.tooltip = '';
this.class = 'separator';
this.enabled = false;
this.checked = false;
}
/**
* Joins all non-empty lists of actions with separators.
*/
static join(...actionLists) {
let out = [];
for (const list of actionLists) {
if (!list.length) {
// skip
}
else if (out.length) {
out = [...out, new Separator(), ...list];
}
else {
out = list;
}
}
return out;
}
static { this.ID = 'vs.actions.separator'; }
async run() { }
}
export class SubmenuAction {
get actions() { return this._actions; }
constructor(id, label, actions, cssClass) {
this.tooltip = '';
this.enabled = true;
this.checked = undefined;
this.id = id;
this.label = label;
this.class = cssClass;
this._actions = actions;
}
async run() { }
}
export class EmptySubmenuAction extends Action {
static { this.ID = 'vs.actions.empty'; }
constructor() {
super(EmptySubmenuAction.ID, nls.localize('submenu.empty', '(empty)'), undefined, false);
}
}
export function toAction(props) {
return {
id: props.id,
label: props.label,
tooltip: props.tooltip ?? props.label,
class: props.class,
enabled: props.enabled ?? true,
checked: props.checked,
run: async (...args) => props.run(...args),
};
}
+501
View File
@@ -0,0 +1,501 @@
/**
* Returns the last element of an array.
* @param array The array.
* @param n Which element from the end (default is zero).
*/
export function tail(array, n = 0) {
return array[array.length - (1 + n)];
}
export function tail2(arr) {
if (arr.length === 0) {
throw new Error('Invalid tail call');
}
return [arr.slice(0, arr.length - 1), arr[arr.length - 1]];
}
export function equals(one, other, itemEquals = (a, b) => a === b) {
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;
}
/**
* Remove the element at `index` by replacing it with the last element. This is faster than `splice`
* but changes the order of the array
*/
export function removeFastWithoutKeepingOrder(array, index) {
const last = array.length - 1;
if (index < last) {
array[index] = array[last];
}
array.pop();
}
/**
* Performs a binary search algorithm over a sorted array.
*
* @param array The array being searched.
* @param key The value we search for.
* @param comparator A function that takes two array elements and returns zero
* if they are equal, a negative number if the first element precedes the
* second one in the sorting order, or a positive number if the second element
* precedes the first one.
* @return See {@link binarySearch2}
*/
export function binarySearch(array, key, comparator) {
return binarySearch2(array.length, i => comparator(array[i], key));
}
/**
* Performs a binary search algorithm over a sorted collection. Useful for cases
* when we need to perform a binary search over something that isn't actually an
* array, and converting data to an array would defeat the use of binary search
* in the first place.
*
* @param length The collection length.
* @param compareToKey A function that takes an index of an element in the
* collection and returns zero if the value at this index is equal to the
* search key, a negative number if the value precedes the search key in the
* sorting order, or a positive number if the search key precedes the value.
* @return A non-negative index of an element, if found. If not found, the
* result is -(n+1) (or ~n, using bitwise notation), where n is the index
* where the key should be inserted to maintain the sorting order.
*/
export function binarySearch2(length, compareToKey) {
let low = 0, high = length - 1;
while (low <= high) {
const mid = ((low + high) / 2) | 0;
const comp = compareToKey(mid);
if (comp < 0) {
low = mid + 1;
}
else if (comp > 0) {
high = mid - 1;
}
else {
return mid;
}
}
return -(low + 1);
}
export function quickSelect(nth, data, compare) {
nth = nth | 0;
if (nth >= data.length) {
throw new TypeError('invalid index');
}
const pivotValue = data[Math.floor(data.length * Math.random())];
const lower = [];
const higher = [];
const pivots = [];
for (const 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);
}
}
export function groupBy(data, compare) {
const result = [];
let currentGroup = undefined;
for (const element of data.slice(0).sort(compare)) {
if (!currentGroup || compare(currentGroup[0], element) !== 0) {
currentGroup = [element];
result.push(currentGroup);
}
else {
currentGroup.push(element);
}
}
return result;
}
/**
* Splits the given items into a list of (non-empty) groups.
* `shouldBeGrouped` is used to decide if two consecutive items should be in the same group.
* The order of the items is preserved.
*/
export function* groupAdjacentBy(items, shouldBeGrouped) {
let currentGroup;
let last;
for (const item of items) {
if (last !== undefined && shouldBeGrouped(last, item)) {
currentGroup.push(item);
}
else {
if (currentGroup) {
yield currentGroup;
}
currentGroup = [item];
}
last = item;
}
if (currentGroup) {
yield currentGroup;
}
}
export function forEachAdjacent(arr, f) {
for (let i = 0; i <= arr.length; i++) {
f(i === 0 ? undefined : arr[i - 1], i === arr.length ? undefined : arr[i]);
}
}
export function forEachWithNeighbors(arr, f) {
for (let i = 0; i < arr.length; i++) {
f(i === 0 ? undefined : arr[i - 1], arr[i], i + 1 === arr.length ? undefined : arr[i + 1]);
}
}
/**
* @returns New array with all falsy values removed. The original array IS NOT modified.
*/
export function coalesce(array) {
return array.filter((e) => !!e);
}
/**
* Remove all falsy values from `array`. The original array IS modified.
*/
export function coalesceInPlace(array) {
let to = 0;
for (let i = 0; i < array.length; i++) {
if (!!array[i]) {
array[to] = array[i];
to += 1;
}
}
array.length = to;
}
/**
* @returns false if the provided object is an array and not empty.
*/
export function isFalsyOrEmpty(obj) {
return !Array.isArray(obj) || obj.length === 0;
}
export function isNonEmptyArray(obj) {
return Array.isArray(obj) && obj.length > 0;
}
/**
* Removes duplicates from the given array. The optional keyFn allows to specify
* how elements are checked for equality by returning an alternate value for each.
*/
export function distinct(array, keyFn = value => value) {
const seen = new Set();
return array.filter(element => {
const key = keyFn(element);
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
}
export function firstOrDefault(array, notFoundValue) {
return array.length > 0 ? array[0] : notFoundValue;
}
export function range(arg, to) {
let from = typeof to === 'number' ? arg : 0;
if (typeof to === 'number') {
from = arg;
}
else {
from = 0;
to = arg;
}
const result = [];
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;
}
/**
* 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(target, insertIndex, insertArr) {
const before = target.slice(0, insertIndex);
const after = target.slice(insertIndex);
return before.concat(insertArr, after);
}
/**
* Pushes an element to the start of the array, if found.
*/
export function pushToStart(arr, value) {
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(arr, value) {
const index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
arr.push(value);
}
}
export function pushMany(arr, items) {
for (const item of items) {
arr.push(item);
}
}
export function asArray(x) {
return Array.isArray(x) ? x : [x];
}
/**
* Insert the new items in the array.
* @param array The original array.
* @param start The zero-based location in the array from which to start inserting elements.
* @param newItems The items to be inserted
*/
export function insertInto(array, start, newItems) {
const startIdx = getActualStartIndex(array, start);
const originalLength = array.length;
const newItemsLength = newItems.length;
array.length = originalLength + newItemsLength;
// Move the items after the start index, start from the end so that we don't overwrite any value.
for (let i = originalLength - 1; i >= startIdx; i--) {
array[i + newItemsLength] = array[i];
}
for (let i = 0; i < newItemsLength; i++) {
array[i + startIdx] = newItems[i];
}
}
/**
* Removes elements from an array and inserts new elements in their place, returning the deleted elements. Alternative to the native Array.splice method, it
* can only support limited number of items due to the maximum call stack size limit.
* @param array The original array.
* @param start The zero-based location in the array from which to start removing elements.
* @param deleteCount The number of elements to remove.
* @returns An array containing the elements that were deleted.
*/
export function splice(array, start, deleteCount, newItems) {
const index = getActualStartIndex(array, start);
let result = array.splice(index, deleteCount);
if (result === undefined) {
// see https://bugs.webkit.org/show_bug.cgi?id=261140
result = [];
}
insertInto(array, index, newItems);
return result;
}
/**
* Determine the actual start index (same logic as the native splice() or slice())
* If greater than the length of the array, start will be set to the length of the array. In this case, no element will be deleted but the method will behave as an adding function, adding as many element as item[n*] provided.
* If negative, it will begin that many elements from the end of the array. (In this case, the origin -1, meaning -n is the index of the nth last element, and is therefore equivalent to the index of array.length - n.) If array.length + start is less than 0, it will begin from index 0.
* @param array The target array.
* @param start The operation index.
*/
function getActualStartIndex(array, start) {
return start < 0 ? Math.max(start + array.length, 0) : Math.min(start, array.length);
}
export var CompareResult;
(function (CompareResult) {
function isLessThan(result) {
return result < 0;
}
CompareResult.isLessThan = isLessThan;
function isLessThanOrEqual(result) {
return result <= 0;
}
CompareResult.isLessThanOrEqual = isLessThanOrEqual;
function isGreaterThan(result) {
return result > 0;
}
CompareResult.isGreaterThan = isGreaterThan;
function isNeitherLessOrGreaterThan(result) {
return result === 0;
}
CompareResult.isNeitherLessOrGreaterThan = isNeitherLessOrGreaterThan;
CompareResult.greaterThan = 1;
CompareResult.lessThan = -1;
CompareResult.neitherLessOrGreaterThan = 0;
})(CompareResult || (CompareResult = {}));
export function compareBy(selector, comparator) {
return (a, b) => comparator(selector(a), selector(b));
}
export function tieBreakComparators(...comparators) {
return (item1, item2) => {
for (const comparator of comparators) {
const result = comparator(item1, item2);
if (!CompareResult.isNeitherLessOrGreaterThan(result)) {
return result;
}
}
return CompareResult.neitherLessOrGreaterThan;
};
}
/**
* The natural order on numbers.
*/
export const numberComparator = (a, b) => a - b;
export const booleanComparator = (a, b) => numberComparator(a ? 1 : 0, b ? 1 : 0);
export function reverseOrder(comparator) {
return (a, b) => -comparator(a, b);
}
export class ArrayQueue {
/**
* Constructs a queue that is backed by the given array. Runtime is O(1).
*/
constructor(items) {
this.items = items;
this.firstIdx = 0;
this.lastIdx = this.items.length - 1;
}
get length() {
return this.lastIdx - this.firstIdx + 1;
}
/**
* Consumes elements from the beginning of the queue as long as the predicate returns true.
* If no elements were consumed, `null` is returned. Has a runtime of O(result.length).
*/
takeWhile(predicate) {
// P(k) := k <= this.lastIdx && predicate(this.items[k])
// Find s := min { k | k >= this.firstIdx && !P(k) } and return this.data[this.firstIdx...s)
let startIdx = this.firstIdx;
while (startIdx < this.items.length && predicate(this.items[startIdx])) {
startIdx++;
}
const result = startIdx === this.firstIdx ? null : this.items.slice(this.firstIdx, startIdx);
this.firstIdx = startIdx;
return result;
}
/**
* Consumes elements from the end of the queue as long as the predicate returns true.
* If no elements were consumed, `null` is returned.
* The result has the same order as the underlying array!
*/
takeFromEndWhile(predicate) {
// P(k) := this.firstIdx >= k && predicate(this.items[k])
// Find s := max { k | k <= this.lastIdx && !P(k) } and return this.data(s...this.lastIdx]
let endIdx = this.lastIdx;
while (endIdx >= 0 && predicate(this.items[endIdx])) {
endIdx--;
}
const result = endIdx === this.lastIdx ? null : this.items.slice(endIdx + 1, this.lastIdx + 1);
this.lastIdx = endIdx;
return result;
}
peek() {
if (this.length === 0) {
return undefined;
}
return this.items[this.firstIdx];
}
dequeue() {
const result = this.items[this.firstIdx];
this.firstIdx++;
return result;
}
takeCount(count) {
const result = this.items.slice(this.firstIdx, this.firstIdx + count);
this.firstIdx += count;
return result;
}
}
/**
* This class is faster than an iterator and array for lazy computed data.
*/
export class CallbackIterable {
static { this.empty = new CallbackIterable(_callback => { }); }
constructor(
/**
* Calls the callback for every item.
* Stops when the callback returns false.
*/
iterate) {
this.iterate = iterate;
}
toArray() {
const result = [];
this.iterate(item => { result.push(item); return true; });
return result;
}
filter(predicate) {
return new CallbackIterable(cb => this.iterate(item => predicate(item) ? cb(item) : true));
}
map(mapFn) {
return new CallbackIterable(cb => this.iterate(item => cb(mapFn(item))));
}
findLast(predicate) {
let result;
this.iterate(item => {
if (predicate(item)) {
result = item;
}
return true;
});
return result;
}
findLastMaxBy(comparator) {
let result;
let first = true;
this.iterate(item => {
if (first || CompareResult.isGreaterThan(comparator(item, result))) {
first = false;
result = item;
}
return true;
});
return result;
}
}
/**
* Represents a re-arrangement of items in an array.
*/
export class Permutation {
constructor(_indexMap) {
this._indexMap = _indexMap;
}
/**
* Returns a permutation that sorts the given array according to the given compare function.
*/
static createSortPermutation(arr, compareFn) {
const sortIndices = Array.from(arr.keys()).sort((index1, index2) => compareFn(arr[index1], arr[index2]));
return new Permutation(sortIndices);
}
/**
* Returns a new array with the elements of the given array re-arranged according to this permutation.
*/
apply(arr) {
return arr.map((_, index) => arr[this._indexMap[index]]);
}
/**
* Returns a new permutation that undoes the re-arrangement of this permutation.
*/
inverse() {
const inverseIndexMap = this._indexMap.slice();
for (let i = 0; i < this._indexMap.length; i++) {
inverseIndexMap[this._indexMap[i]] = i;
}
return new Permutation(inverseIndexMap);
}
}
@@ -0,0 +1,175 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function findLast(array, predicate) {
const idx = findLastIdx(array, predicate);
if (idx === -1) {
return undefined;
}
return array[idx];
}
export function findLastIdx(array, predicate, fromIndex = array.length - 1) {
for (let i = fromIndex; i >= 0; i--) {
const element = array[i];
if (predicate(element)) {
return i;
}
}
return -1;
}
/**
* Finds the last item where predicate is true using binary search.
* `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[true, ..., true, false, ..., false]`!
*
* @returns `undefined` if no item matches, otherwise the last item that matches the predicate.
*/
export function findLastMonotonous(array, predicate) {
const idx = findLastIdxMonotonous(array, predicate);
return idx === -1 ? undefined : array[idx];
}
/**
* Finds the last item where predicate is true using binary search.
* `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[true, ..., true, false, ..., false]`!
*
* @returns `startIdx - 1` if predicate is false for all items, otherwise the index of the last item that matches the predicate.
*/
export function findLastIdxMonotonous(array, predicate, startIdx = 0, endIdxEx = array.length) {
let i = startIdx;
let j = endIdxEx;
while (i < j) {
const k = Math.floor((i + j) / 2);
if (predicate(array[k])) {
i = k + 1;
}
else {
j = k;
}
}
return i - 1;
}
/**
* Finds the first item where predicate is true using binary search.
* `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[false, ..., false, true, ..., true]`!
*
* @returns `undefined` if no item matches, otherwise the first item that matches the predicate.
*/
export function findFirstMonotonous(array, predicate) {
const idx = findFirstIdxMonotonousOrArrLen(array, predicate);
return idx === array.length ? undefined : array[idx];
}
/**
* Finds the first item where predicate is true using binary search.
* `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[false, ..., false, true, ..., true]`!
*
* @returns `endIdxEx` if predicate is false for all items, otherwise the index of the first item that matches the predicate.
*/
export function findFirstIdxMonotonousOrArrLen(array, predicate, startIdx = 0, endIdxEx = array.length) {
let i = startIdx;
let j = endIdxEx;
while (i < j) {
const k = Math.floor((i + j) / 2);
if (predicate(array[k])) {
j = k;
}
else {
i = k + 1;
}
}
return i;
}
/**
* Use this when
* * You have a sorted array
* * You query this array with a monotonous predicate to find the last item that has a certain property.
* * You query this array multiple times with monotonous predicates that get weaker and weaker.
*/
export class MonotonousArray {
static { this.assertInvariants = false; }
constructor(_array) {
this._array = _array;
this._findLastMonotonousLastIdx = 0;
}
/**
* The predicate must be monotonous, i.e. `arr.map(predicate)` must be like `[true, ..., true, false, ..., false]`!
* For subsequent calls, current predicate must be weaker than (or equal to) the previous predicate, i.e. more entries must be `true`.
*/
findLastMonotonous(predicate) {
if (MonotonousArray.assertInvariants) {
if (this._prevFindLastPredicate) {
for (const item of this._array) {
if (this._prevFindLastPredicate(item) && !predicate(item)) {
throw new Error('MonotonousArray: current predicate must be weaker than (or equal to) the previous predicate.');
}
}
}
this._prevFindLastPredicate = predicate;
}
const idx = findLastIdxMonotonous(this._array, predicate, this._findLastMonotonousLastIdx);
this._findLastMonotonousLastIdx = idx + 1;
return idx === -1 ? undefined : this._array[idx];
}
}
/**
* Returns the first item that is equal to or greater than every other item.
*/
export function findFirstMax(array, comparator) {
if (array.length === 0) {
return undefined;
}
let max = array[0];
for (let i = 1; i < array.length; i++) {
const item = array[i];
if (comparator(item, max) > 0) {
max = item;
}
}
return max;
}
/**
* Returns the last item that is equal to or greater than every other item.
*/
export function findLastMax(array, comparator) {
if (array.length === 0) {
return undefined;
}
let max = array[0];
for (let i = 1; i < array.length; i++) {
const item = array[i];
if (comparator(item, max) >= 0) {
max = item;
}
}
return max;
}
/**
* Returns the first item that is equal to or less than every other item.
*/
export function findFirstMin(array, comparator) {
return findFirstMax(array, (a, b) => -comparator(a, b));
}
export function findMaxIdx(array, comparator) {
if (array.length === 0) {
return -1;
}
let maxIdx = 0;
for (let i = 1; i < array.length; i++) {
const item = array[i];
if (comparator(item, array[maxIdx]) > 0) {
maxIdx = i;
}
}
return maxIdx;
}
/**
* Returns the first mapped value of the array which is not undefined.
*/
export function mapFindFirst(items, mapFn) {
for (const value of items) {
const mapped = mapFn(value);
if (mapped !== undefined) {
return mapped;
}
}
return undefined;
}
+59
View File
@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { BugIndicatingError, onUnexpectedError } from './errors.js';
/**
* Throws an error with the provided message if the provided value does not evaluate to a true Javascript value.
*
* @deprecated Use `assert(...)` instead.
* This method is usually used like this:
* ```ts
* import * as assert from 'vs/base/common/assert';
* assert.ok(...);
* ```
*
* However, `assert` in that example is a user chosen name.
* There is no tooling for generating such an import statement.
* Thus, the `assert(...)` function should be used instead.
*/
export function ok(value, message) {
if (!value) {
throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed');
}
}
export function assertNever(value, message = 'Unreachable') {
throw new Error(message);
}
/**
* Like assert, but doesn't throw.
*/
export function softAssert(condition) {
if (!condition) {
onUnexpectedError(new BugIndicatingError('Soft Assertion Failed'));
}
}
/**
* condition must be side-effect free!
*/
export function assertFn(condition) {
if (!condition()) {
// eslint-disable-next-line no-debugger
debugger;
// Reevaluate `condition` again to make debugging easier
condition();
onUnexpectedError(new BugIndicatingError('Assertion Failed'));
}
}
export function checkAdjacentItems(items, predicate) {
let i = 0;
while (i < items.length - 1) {
const a = items[i];
const b = items[i + 1];
if (!predicate(a, b)) {
return false;
}
i++;
}
return true;
}
+843
View File
@@ -0,0 +1,843 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationTokenSource } from './cancellation.js';
import { BugIndicatingError, CancellationError } from './errors.js';
import { Emitter, Event } from './event.js';
import { toDisposable } from './lifecycle.js';
import { setTimeout0 } from './platform.js';
import { MicrotaskDelay } from './symbols.js';
export function isThenable(obj) {
return !!obj && typeof obj.then === 'function';
}
export function createCancelablePromise(callback) {
const source = new CancellationTokenSource();
const thenable = callback(source.token);
const promise = new Promise((resolve, reject) => {
const subscription = source.token.onCancellationRequested(() => {
subscription.dispose();
reject(new CancellationError());
});
Promise.resolve(thenable).then(value => {
subscription.dispose();
source.dispose();
resolve(value);
}, err => {
subscription.dispose();
source.dispose();
reject(err);
});
});
return new class {
cancel() {
source.cancel();
source.dispose();
}
then(resolve, reject) {
return promise.then(resolve, reject);
}
catch(reject) {
return this.then(undefined, reject);
}
finally(onfinally) {
return promise.finally(onfinally);
}
};
}
export function raceCancellation(promise, token, defaultValue) {
return new Promise((resolve, reject) => {
const ref = token.onCancellationRequested(() => {
ref.dispose();
resolve(defaultValue);
});
promise.then(resolve, reject).finally(() => ref.dispose());
});
}
/**
* 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 {
constructor() {
this.isDisposed = false;
this.activePromise = null;
this.queuedPromise = null;
this.queuedPromiseFactory = null;
}
queue(promiseFactory) {
if (this.isDisposed) {
return Promise.reject(new Error('Throttler is disposed'));
}
if (this.activePromise) {
this.queuedPromiseFactory = promiseFactory;
if (!this.queuedPromise) {
const onComplete = () => {
this.queuedPromise = null;
if (this.isDisposed) {
return;
}
const result = this.queue(this.queuedPromiseFactory);
this.queuedPromiseFactory = null;
return result;
};
this.queuedPromise = new Promise(resolve => {
this.activePromise.then(onComplete, onComplete).then(resolve);
});
}
return new Promise((resolve, reject) => {
this.queuedPromise.then(resolve, reject);
});
}
this.activePromise = promiseFactory();
return new Promise((resolve, reject) => {
this.activePromise.then((result) => {
this.activePromise = null;
resolve(result);
}, (err) => {
this.activePromise = null;
reject(err);
});
});
}
dispose() {
this.isDisposed = true;
}
}
const timeoutDeferred = (timeout, fn) => {
let scheduled = true;
const handle = setTimeout(() => {
scheduled = false;
fn();
}, timeout);
return {
isTriggered: () => scheduled,
dispose: () => {
clearTimeout(handle);
scheduled = false;
},
};
};
const microtaskDeferred = (fn) => {
let scheduled = true;
queueMicrotask(() => {
if (scheduled) {
scheduled = false;
fn();
}
});
return {
isTriggered: () => scheduled,
dispose: () => { scheduled = false; },
};
};
/**
* A helper to delay (debounce) 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 {
constructor(defaultDelay) {
this.defaultDelay = defaultDelay;
this.deferred = null;
this.completionPromise = null;
this.doResolve = null;
this.doReject = null;
this.task = null;
}
trigger(task, delay = this.defaultDelay) {
this.task = task;
this.cancelTimeout();
if (!this.completionPromise) {
this.completionPromise = new Promise((resolve, reject) => {
this.doResolve = resolve;
this.doReject = reject;
}).then(() => {
this.completionPromise = null;
this.doResolve = null;
if (this.task) {
const task = this.task;
this.task = null;
return task();
}
return undefined;
});
}
const fn = () => {
this.deferred = null;
this.doResolve?.(null);
};
this.deferred = delay === MicrotaskDelay ? microtaskDeferred(fn) : timeoutDeferred(delay, fn);
return this.completionPromise;
}
isTriggered() {
return !!this.deferred?.isTriggered();
}
cancel() {
this.cancelTimeout();
if (this.completionPromise) {
this.doReject?.(new CancellationError());
this.completionPromise = null;
}
}
cancelTimeout() {
this.deferred?.dispose();
this.deferred = null;
}
dispose() {
this.cancel();
}
}
/**
* 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 {
constructor(defaultDelay) {
this.delayer = new Delayer(defaultDelay);
this.throttler = new Throttler();
}
trigger(promiseFactory, delay) {
return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay);
}
cancel() {
this.delayer.cancel();
}
dispose() {
this.delayer.dispose();
this.throttler.dispose();
}
}
export function timeout(millis, token) {
if (!token) {
return createCancelablePromise(token => timeout(millis, token));
}
return new Promise((resolve, reject) => {
const handle = setTimeout(() => {
disposable.dispose();
resolve();
}, millis);
const disposable = token.onCancellationRequested(() => {
clearTimeout(handle);
disposable.dispose();
reject(new CancellationError());
});
});
}
/**
* Creates a timeout that can be disposed using its returned value.
* @param handler The timeout handler.
* @param timeout An optional timeout in milliseconds.
* @param store An optional {@link DisposableStore} that will have the timeout disposable managed automatically.
*
* @example
* const store = new DisposableStore;
* // Call the timeout after 1000ms at which point it will be automatically
* // evicted from the store.
* const timeoutDisposable = disposableTimeout(() => {}, 1000, store);
*
* if (foo) {
* // Cancel the timeout and evict it from store.
* timeoutDisposable.dispose();
* }
*/
export function disposableTimeout(handler, timeout = 0, store) {
const timer = setTimeout(() => {
handler();
if (store) {
disposable.dispose();
}
}, timeout);
const disposable = toDisposable(() => {
clearTimeout(timer);
store?.deleteAndLeak(disposable);
});
store?.add(disposable);
return disposable;
}
export function first(promiseFactories, shouldStop = t => !!t, defaultValue = null) {
let index = 0;
const len = promiseFactories.length;
const loop = () => {
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();
}
export class TimeoutTimer {
constructor(runner, timeout) {
this._isDisposed = false;
this._token = -1;
if (typeof runner === 'function' && typeof timeout === 'number') {
this.setIfNotSet(runner, timeout);
}
}
dispose() {
this.cancel();
this._isDisposed = true;
}
cancel() {
if (this._token !== -1) {
clearTimeout(this._token);
this._token = -1;
}
}
cancelAndSet(runner, timeout) {
if (this._isDisposed) {
throw new BugIndicatingError(`Calling 'cancelAndSet' on a disposed TimeoutTimer`);
}
this.cancel();
this._token = setTimeout(() => {
this._token = -1;
runner();
}, timeout);
}
setIfNotSet(runner, timeout) {
if (this._isDisposed) {
throw new BugIndicatingError(`Calling 'setIfNotSet' on a disposed TimeoutTimer`);
}
if (this._token !== -1) {
// timer is already set
return;
}
this._token = setTimeout(() => {
this._token = -1;
runner();
}, timeout);
}
}
export class IntervalTimer {
constructor() {
this.disposable = undefined;
this.isDisposed = false;
}
cancel() {
this.disposable?.dispose();
this.disposable = undefined;
}
cancelAndSet(runner, interval, context = globalThis) {
if (this.isDisposed) {
throw new BugIndicatingError(`Calling 'cancelAndSet' on a disposed IntervalTimer`);
}
this.cancel();
const handle = context.setInterval(() => {
runner();
}, interval);
this.disposable = toDisposable(() => {
context.clearInterval(handle);
this.disposable = undefined;
});
}
dispose() {
this.cancel();
this.isDisposed = true;
}
}
export class RunOnceScheduler {
constructor(runner, delay) {
this.timeoutToken = -1;
this.runner = runner;
this.timeout = delay;
this.timeoutHandler = this.onTimeout.bind(this);
}
/**
* Dispose RunOnceScheduler
*/
dispose() {
this.cancel();
this.runner = null;
}
/**
* Cancel current scheduled runner (if any).
*/
cancel() {
if (this.isScheduled()) {
clearTimeout(this.timeoutToken);
this.timeoutToken = -1;
}
}
/**
* Cancel previous runner (if any) & schedule a new runner.
*/
schedule(delay = this.timeout) {
this.cancel();
this.timeoutToken = setTimeout(this.timeoutHandler, delay);
}
get delay() {
return this.timeout;
}
set delay(value) {
this.timeout = value;
}
/**
* Returns true if scheduled.
*/
isScheduled() {
return this.timeoutToken !== -1;
}
onTimeout() {
this.timeoutToken = -1;
if (this.runner) {
this.doRun();
}
}
doRun() {
this.runner?.();
}
}
/**
* Execute the callback the next time the browser is idle, returning an
* {@link IDisposable} that will cancel the callback when disposed. This wraps
* [requestIdleCallback] so it will fallback to [setTimeout] if the environment
* doesn't support it.
*
* @param callback The callback to run when idle, this includes an
* [IdleDeadline] that provides the time alloted for the idle callback by the
* browser. Not respecting this deadline will result in a degraded user
* experience.
* @param timeout A timeout at which point to queue no longer wait for an idle
* callback but queue it on the regular event loop (like setTimeout). Typically
* this should not be used.
*
* [IdleDeadline]: https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline
* [requestIdleCallback]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
* [setTimeout]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout
*
* **Note** that there is `dom.ts#runWhenWindowIdle` which is better suited when running inside a browser
* context
*/
export let runWhenGlobalIdle;
export let _runWhenIdle;
(function () {
if (typeof globalThis.requestIdleCallback !== 'function' || typeof globalThis.cancelIdleCallback !== 'function') {
_runWhenIdle = (_targetWindow, runner) => {
setTimeout0(() => {
if (disposed) {
return;
}
const end = Date.now() + 15; // one frame at 64fps
const deadline = {
didTimeout: true,
timeRemaining() {
return Math.max(0, end - Date.now());
}
};
runner(Object.freeze(deadline));
});
let disposed = false;
return {
dispose() {
if (disposed) {
return;
}
disposed = true;
}
};
};
}
else {
_runWhenIdle = (targetWindow, runner, timeout) => {
const handle = targetWindow.requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined);
let disposed = false;
return {
dispose() {
if (disposed) {
return;
}
disposed = true;
targetWindow.cancelIdleCallback(handle);
}
};
};
}
runWhenGlobalIdle = (runner) => _runWhenIdle(globalThis, runner);
})();
export class AbstractIdleValue {
constructor(targetWindow, executor) {
this._didRun = false;
this._executor = () => {
try {
this._value = executor();
}
catch (err) {
this._error = err;
}
finally {
this._didRun = true;
}
};
this._handle = _runWhenIdle(targetWindow, () => this._executor());
}
dispose() {
this._handle.dispose();
}
get value() {
if (!this._didRun) {
this._handle.dispose();
this._executor();
}
if (this._error) {
throw this._error;
}
return this._value;
}
get isInitialized() {
return this._didRun;
}
}
/**
* An `IdleValue` that always uses the current window (which might be throttled or inactive)
*
* **Note** that there is `dom.ts#WindowIdleValue` which is better suited when running inside a browser
* context
*/
export class GlobalIdleValue extends AbstractIdleValue {
constructor(executor) {
super(globalThis, executor);
}
}
/**
* Creates a promise whose resolution or rejection can be controlled imperatively.
*/
export class DeferredPromise {
get isRejected() {
return this.outcome?.outcome === 1 /* DeferredOutcome.Rejected */;
}
get isSettled() {
return !!this.outcome;
}
constructor() {
this.p = new Promise((c, e) => {
this.completeCallback = c;
this.errorCallback = e;
});
}
complete(value) {
return new Promise(resolve => {
this.completeCallback(value);
this.outcome = { outcome: 0 /* DeferredOutcome.Resolved */, value };
resolve();
});
}
error(err) {
return new Promise(resolve => {
this.errorCallback(err);
this.outcome = { outcome: 1 /* DeferredOutcome.Rejected */, value: err };
resolve();
});
}
cancel() {
return this.error(new CancellationError());
}
}
//#endregion
//#region Promises
export var Promises;
(function (Promises) {
/**
* A drop-in replacement for `Promise.all` with the only difference
* that the method awaits every promise to either fulfill or reject.
*
* Similar to `Promise.all`, only the first error will be returned
* if any.
*/
async function settled(promises) {
let firstError = undefined;
const result = await Promise.all(promises.map(promise => promise.then(value => value, error => {
if (!firstError) {
firstError = error;
}
return undefined; // do not rethrow so that other promises can settle
})));
if (typeof firstError !== 'undefined') {
throw firstError;
}
return result; // cast is needed and protected by the `throw` above
}
Promises.settled = settled;
/**
* A helper to create a new `Promise<T>` with a body that is a promise
* itself. By default, an error that raises from the async body will
* end up as a unhandled rejection, so this utility properly awaits the
* body and rejects the promise as a normal promise does without async
* body.
*
* This method should only be used in rare cases where otherwise `async`
* cannot be used (e.g. when callbacks are involved that require this).
*/
function withAsyncBody(bodyFn) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
try {
await bodyFn(resolve, reject);
}
catch (error) {
reject(error);
}
});
}
Promises.withAsyncBody = withAsyncBody;
})(Promises || (Promises = {}));
/**
* A rich implementation for an `AsyncIterable<T>`.
*/
export class AsyncIterableObject {
static fromArray(items) {
return new AsyncIterableObject((writer) => {
writer.emitMany(items);
});
}
static fromPromise(promise) {
return new AsyncIterableObject(async (emitter) => {
emitter.emitMany(await promise);
});
}
static fromPromises(promises) {
return new AsyncIterableObject(async (emitter) => {
await Promise.all(promises.map(async (p) => emitter.emitOne(await p)));
});
}
static merge(iterables) {
return new AsyncIterableObject(async (emitter) => {
await Promise.all(iterables.map(async (iterable) => {
for await (const item of iterable) {
emitter.emitOne(item);
}
}));
});
}
static { this.EMPTY = AsyncIterableObject.fromArray([]); }
constructor(executor, onReturn) {
this._state = 0 /* AsyncIterableSourceState.Initial */;
this._results = [];
this._error = null;
this._onReturn = onReturn;
this._onStateChanged = new Emitter();
queueMicrotask(async () => {
const writer = {
emitOne: (item) => this.emitOne(item),
emitMany: (items) => this.emitMany(items),
reject: (error) => this.reject(error)
};
try {
await Promise.resolve(executor(writer));
this.resolve();
}
catch (err) {
this.reject(err);
}
finally {
writer.emitOne = undefined;
writer.emitMany = undefined;
writer.reject = undefined;
}
});
}
[Symbol.asyncIterator]() {
let i = 0;
return {
next: async () => {
do {
if (this._state === 2 /* AsyncIterableSourceState.DoneError */) {
throw this._error;
}
if (i < this._results.length) {
return { done: false, value: this._results[i++] };
}
if (this._state === 1 /* AsyncIterableSourceState.DoneOK */) {
return { done: true, value: undefined };
}
await Event.toPromise(this._onStateChanged.event);
} while (true);
},
return: async () => {
this._onReturn?.();
return { done: true, value: undefined };
}
};
}
static map(iterable, mapFn) {
return new AsyncIterableObject(async (emitter) => {
for await (const item of iterable) {
emitter.emitOne(mapFn(item));
}
});
}
map(mapFn) {
return AsyncIterableObject.map(this, mapFn);
}
static filter(iterable, filterFn) {
return new AsyncIterableObject(async (emitter) => {
for await (const item of iterable) {
if (filterFn(item)) {
emitter.emitOne(item);
}
}
});
}
filter(filterFn) {
return AsyncIterableObject.filter(this, filterFn);
}
static coalesce(iterable) {
return AsyncIterableObject.filter(iterable, item => !!item);
}
coalesce() {
return AsyncIterableObject.coalesce(this);
}
static async toPromise(iterable) {
const result = [];
for await (const item of iterable) {
result.push(item);
}
return result;
}
toPromise() {
return AsyncIterableObject.toPromise(this);
}
/**
* The value will be appended at the end.
*
* **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
*/
emitOne(value) {
if (this._state !== 0 /* AsyncIterableSourceState.Initial */) {
return;
}
// it is important to add new values at the end,
// as we may have iterators already running on the array
this._results.push(value);
this._onStateChanged.fire();
}
/**
* The values will be appended at the end.
*
* **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
*/
emitMany(values) {
if (this._state !== 0 /* AsyncIterableSourceState.Initial */) {
return;
}
// it is important to add new values at the end,
// as we may have iterators already running on the array
this._results = this._results.concat(values);
this._onStateChanged.fire();
}
/**
* Calling `resolve()` will mark the result array as complete.
*
* **NOTE** `resolve()` must be called, otherwise all consumers of this iterable will hang indefinitely, similar to a non-resolved promise.
* **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
*/
resolve() {
if (this._state !== 0 /* AsyncIterableSourceState.Initial */) {
return;
}
this._state = 1 /* AsyncIterableSourceState.DoneOK */;
this._onStateChanged.fire();
}
/**
* Writing an error will permanently invalidate this iterable.
* The current users will receive an error thrown, as will all future users.
*
* **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
*/
reject(error) {
if (this._state !== 0 /* AsyncIterableSourceState.Initial */) {
return;
}
this._state = 2 /* AsyncIterableSourceState.DoneError */;
this._error = error;
this._onStateChanged.fire();
}
}
export class CancelableAsyncIterableObject extends AsyncIterableObject {
constructor(_source, executor) {
super(executor);
this._source = _source;
}
cancel() {
this._source.cancel();
}
}
export function createCancelableAsyncIterable(callback) {
const source = new CancellationTokenSource();
const innerIterable = callback(source.token);
return new CancelableAsyncIterableObject(source, async (emitter) => {
const subscription = source.token.onCancellationRequested(() => {
subscription.dispose();
source.dispose();
emitter.reject(new CancellationError());
});
try {
for await (const item of innerIterable) {
if (source.token.isCancellationRequested) {
// canceled in the meantime
return;
}
emitter.emitOne(item);
}
subscription.dispose();
source.dispose();
}
catch (err) {
subscription.dispose();
source.dispose();
emitter.reject(err);
}
});
}
//#endregion
+68
View File
@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Lazy } from './lazy.js';
const hasBuffer = (typeof Buffer !== 'undefined');
const indexOfTable = new Lazy(() => new Uint8Array(256));
let textDecoder;
export class VSBuffer {
/**
* When running in a nodejs context, if `actual` is not a nodejs Buffer, the backing store for
* the returned `VSBuffer` instance might use a nodejs Buffer allocated from node's Buffer pool,
* which is not transferrable.
*/
static wrap(actual) {
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);
}
constructor(buffer) {
this.buffer = buffer;
this.byteLength = this.buffer.byteLength;
}
toString() {
if (hasBuffer) {
return this.buffer.toString();
}
else {
if (!textDecoder) {
textDecoder = new TextDecoder();
}
return textDecoder.decode(this.buffer);
}
}
}
export function readUInt16LE(source, offset) {
return (((source[offset + 0] << 0) >>> 0) |
((source[offset + 1] << 8) >>> 0));
}
export function writeUInt16LE(destination, value, offset) {
destination[offset + 0] = (value & 0b11111111);
value = value >>> 8;
destination[offset + 1] = (value & 0b11111111);
}
export function readUInt32BE(source, offset) {
return (source[offset] * 2 ** 24
+ source[offset + 1] * 2 ** 16
+ source[offset + 2] * 2 ** 8
+ source[offset + 3]);
}
export function writeUInt32BE(destination, value, offset) {
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 readUInt8(source, offset) {
return source[offset];
}
export function writeUInt8(destination, value, offset) {
destination[offset] = value;
}
+59
View File
@@ -0,0 +1,59 @@
export function identity(t) {
return t;
}
/**
* Uses a LRU cache to make a given parametrized function cached.
* Caches just the last key/value.
*/
export class LRUCachedFunction {
constructor(arg1, arg2) {
this.lastCache = undefined;
this.lastArgKey = undefined;
if (typeof arg1 === 'function') {
this._fn = arg1;
this._computeKey = identity;
}
else {
this._fn = arg2;
this._computeKey = arg1.getCacheKey;
}
}
get(arg) {
const key = this._computeKey(arg);
if (this.lastArgKey !== key) {
this.lastArgKey = key;
this.lastCache = this._fn(arg);
}
return this.lastCache;
}
}
/**
* Uses an unbounded cache to memoize the results of the given function.
*/
export class CachedFunction {
get cachedValues() {
return this._map;
}
constructor(arg1, arg2) {
this._map = new Map();
this._map2 = new Map();
if (typeof arg1 === 'function') {
this._fn = arg1;
this._computeKey = identity;
}
else {
this._fn = arg2;
this._computeKey = arg1.getCacheKey;
}
}
get(arg) {
const key = this._computeKey(arg);
if (this._map2.has(key)) {
return this._map2.get(key);
}
const value = this._fn(arg);
this._map.set(arg, value);
this._map2.set(key, value);
return value;
}
}
@@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* 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 './event.js';
const shortcutEvent = Object.freeze(function (callback, context) {
const handle = setTimeout(callback.bind(context), 0);
return { dispose() { clearTimeout(handle); } };
});
export var CancellationToken;
(function (CancellationToken) {
function isCancellationToken(thing) {
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.isCancellationRequested === 'boolean'
&& typeof thing.onCancellationRequested === 'function';
}
CancellationToken.isCancellationToken = isCancellationToken;
CancellationToken.None = Object.freeze({
isCancellationRequested: false,
onCancellationRequested: Event.None
});
CancellationToken.Cancelled = Object.freeze({
isCancellationRequested: true,
onCancellationRequested: shortcutEvent
});
})(CancellationToken || (CancellationToken = {}));
class MutableToken {
constructor() {
this._isCancelled = false;
this._emitter = null;
}
cancel() {
if (!this._isCancelled) {
this._isCancelled = true;
if (this._emitter) {
this._emitter.fire(undefined);
this.dispose();
}
}
}
get isCancellationRequested() {
return this._isCancelled;
}
get onCancellationRequested() {
if (this._isCancelled) {
return shortcutEvent;
}
if (!this._emitter) {
this._emitter = new Emitter();
}
return this._emitter.event;
}
dispose() {
if (this._emitter) {
this._emitter.dispose();
this._emitter = null;
}
}
}
export class CancellationTokenSource {
constructor(parent) {
this._token = undefined;
this._parentListener = undefined;
this._parentListener = parent && parent.onCancellationRequested(this.cancel, this);
}
get token() {
if (!this._token) {
// be lazy and create the token only when
// actually needed
this._token = new MutableToken();
}
return this._token;
}
cancel() {
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 = false) {
if (cancel) {
this.cancel();
}
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();
}
}
}
export function cancelOnDispose(store) {
const source = new CancellationTokenSource();
store.add({ dispose() { source.cancel(); } });
return source.token;
}
@@ -0,0 +1,5 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export {};
@@ -0,0 +1,47 @@
import { register } from './codiconsUtil.js';
import { codiconsLibrary } from './codiconsLibrary.js';
/**
* Derived icons, that could become separate icons.
* These mappings should be moved into the mapping file in the vscode-codicons repo at some point.
*/
export const codiconsDerived = {
dialogError: register('dialog-error', 'error'),
dialogWarning: register('dialog-warning', 'warning'),
dialogInfo: register('dialog-info', 'info'),
dialogClose: register('dialog-close', 'close'),
treeItemExpanded: register('tree-item-expanded', 'chevron-down'), // collapsed is done with rotation
treeFilterOnTypeOn: register('tree-filter-on-type-on', 'list-filter'),
treeFilterOnTypeOff: register('tree-filter-on-type-off', 'list-selection'),
treeFilterClear: register('tree-filter-clear', 'close'),
treeItemLoading: register('tree-item-loading', 'loading'),
menuSelection: register('menu-selection', 'check'),
menuSubmenu: register('menu-submenu', 'chevron-right'),
menuBarMore: register('menubar-more', 'more'),
scrollbarButtonLeft: register('scrollbar-button-left', 'triangle-left'),
scrollbarButtonRight: register('scrollbar-button-right', 'triangle-right'),
scrollbarButtonUp: register('scrollbar-button-up', 'triangle-up'),
scrollbarButtonDown: register('scrollbar-button-down', 'triangle-down'),
toolBarMore: register('toolbar-more', 'more'),
quickInputBack: register('quick-input-back', 'arrow-left'),
dropDownButton: register('drop-down-button', 0xeab4),
symbolCustomColor: register('symbol-customcolor', 0xeb5c),
exportIcon: register('export', 0xebac),
workspaceUnspecified: register('workspace-unspecified', 0xebc3),
newLine: register('newline', 0xebea),
thumbsDownFilled: register('thumbsdown-filled', 0xec13),
thumbsUpFilled: register('thumbsup-filled', 0xec14),
gitFetch: register('git-fetch', 0xec1d),
lightbulbSparkleAutofix: register('lightbulb-sparkle-autofix', 0xec1f),
debugBreakpointPending: register('debug-breakpoint-pending', 0xebd9),
};
/**
* The Codicon library is a set of default icons that are built-in in VS Code.
*
* In the product (outside of base) Codicons should only be used as defaults. In order to have all icons in VS Code
* themeable, component should define new, UI component specific icons using `iconRegistry.registerIcon`.
* In that call a Codicon can be named as default.
*/
export const Codicon = {
...codiconsLibrary,
...codiconsDerived
};
@@ -0,0 +1,580 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { register } from './codiconsUtil.js';
// This file is automatically generated by (microsoft/vscode-codicons)/scripts/export-to-ts.js
// Please don't edit it, as your changes will be overwritten.
// Instead, add mappings to codiconsDerived in codicons.ts.
export const codiconsLibrary = {
add: register('add', 0xea60),
plus: register('plus', 0xea60),
gistNew: register('gist-new', 0xea60),
repoCreate: register('repo-create', 0xea60),
lightbulb: register('lightbulb', 0xea61),
lightBulb: register('light-bulb', 0xea61),
repo: register('repo', 0xea62),
repoDelete: register('repo-delete', 0xea62),
gistFork: register('gist-fork', 0xea63),
repoForked: register('repo-forked', 0xea63),
gitPullRequest: register('git-pull-request', 0xea64),
gitPullRequestAbandoned: register('git-pull-request-abandoned', 0xea64),
recordKeys: register('record-keys', 0xea65),
keyboard: register('keyboard', 0xea65),
tag: register('tag', 0xea66),
gitPullRequestLabel: register('git-pull-request-label', 0xea66),
tagAdd: register('tag-add', 0xea66),
tagRemove: register('tag-remove', 0xea66),
person: register('person', 0xea67),
personFollow: register('person-follow', 0xea67),
personOutline: register('person-outline', 0xea67),
personFilled: register('person-filled', 0xea67),
gitBranch: register('git-branch', 0xea68),
gitBranchCreate: register('git-branch-create', 0xea68),
gitBranchDelete: register('git-branch-delete', 0xea68),
sourceControl: register('source-control', 0xea68),
mirror: register('mirror', 0xea69),
mirrorPublic: register('mirror-public', 0xea69),
star: register('star', 0xea6a),
starAdd: register('star-add', 0xea6a),
starDelete: register('star-delete', 0xea6a),
starEmpty: register('star-empty', 0xea6a),
comment: register('comment', 0xea6b),
commentAdd: register('comment-add', 0xea6b),
alert: register('alert', 0xea6c),
warning: register('warning', 0xea6c),
search: register('search', 0xea6d),
searchSave: register('search-save', 0xea6d),
logOut: register('log-out', 0xea6e),
signOut: register('sign-out', 0xea6e),
logIn: register('log-in', 0xea6f),
signIn: register('sign-in', 0xea6f),
eye: register('eye', 0xea70),
eyeUnwatch: register('eye-unwatch', 0xea70),
eyeWatch: register('eye-watch', 0xea70),
circleFilled: register('circle-filled', 0xea71),
primitiveDot: register('primitive-dot', 0xea71),
closeDirty: register('close-dirty', 0xea71),
debugBreakpoint: register('debug-breakpoint', 0xea71),
debugBreakpointDisabled: register('debug-breakpoint-disabled', 0xea71),
debugHint: register('debug-hint', 0xea71),
terminalDecorationSuccess: register('terminal-decoration-success', 0xea71),
primitiveSquare: register('primitive-square', 0xea72),
edit: register('edit', 0xea73),
pencil: register('pencil', 0xea73),
info: register('info', 0xea74),
issueOpened: register('issue-opened', 0xea74),
gistPrivate: register('gist-private', 0xea75),
gitForkPrivate: register('git-fork-private', 0xea75),
lock: register('lock', 0xea75),
mirrorPrivate: register('mirror-private', 0xea75),
close: register('close', 0xea76),
removeClose: register('remove-close', 0xea76),
x: register('x', 0xea76),
repoSync: register('repo-sync', 0xea77),
sync: register('sync', 0xea77),
clone: register('clone', 0xea78),
desktopDownload: register('desktop-download', 0xea78),
beaker: register('beaker', 0xea79),
microscope: register('microscope', 0xea79),
vm: register('vm', 0xea7a),
deviceDesktop: register('device-desktop', 0xea7a),
file: register('file', 0xea7b),
fileText: register('file-text', 0xea7b),
more: register('more', 0xea7c),
ellipsis: register('ellipsis', 0xea7c),
kebabHorizontal: register('kebab-horizontal', 0xea7c),
mailReply: register('mail-reply', 0xea7d),
reply: register('reply', 0xea7d),
organization: register('organization', 0xea7e),
organizationFilled: register('organization-filled', 0xea7e),
organizationOutline: register('organization-outline', 0xea7e),
newFile: register('new-file', 0xea7f),
fileAdd: register('file-add', 0xea7f),
newFolder: register('new-folder', 0xea80),
fileDirectoryCreate: register('file-directory-create', 0xea80),
trash: register('trash', 0xea81),
trashcan: register('trashcan', 0xea81),
history: register('history', 0xea82),
clock: register('clock', 0xea82),
folder: register('folder', 0xea83),
fileDirectory: register('file-directory', 0xea83),
symbolFolder: register('symbol-folder', 0xea83),
logoGithub: register('logo-github', 0xea84),
markGithub: register('mark-github', 0xea84),
github: register('github', 0xea84),
terminal: register('terminal', 0xea85),
console: register('console', 0xea85),
repl: register('repl', 0xea85),
zap: register('zap', 0xea86),
symbolEvent: register('symbol-event', 0xea86),
error: register('error', 0xea87),
stop: register('stop', 0xea87),
variable: register('variable', 0xea88),
symbolVariable: register('symbol-variable', 0xea88),
array: register('array', 0xea8a),
symbolArray: register('symbol-array', 0xea8a),
symbolModule: register('symbol-module', 0xea8b),
symbolPackage: register('symbol-package', 0xea8b),
symbolNamespace: register('symbol-namespace', 0xea8b),
symbolObject: register('symbol-object', 0xea8b),
symbolMethod: register('symbol-method', 0xea8c),
symbolFunction: register('symbol-function', 0xea8c),
symbolConstructor: register('symbol-constructor', 0xea8c),
symbolBoolean: register('symbol-boolean', 0xea8f),
symbolNull: register('symbol-null', 0xea8f),
symbolNumeric: register('symbol-numeric', 0xea90),
symbolNumber: register('symbol-number', 0xea90),
symbolStructure: register('symbol-structure', 0xea91),
symbolStruct: register('symbol-struct', 0xea91),
symbolParameter: register('symbol-parameter', 0xea92),
symbolTypeParameter: register('symbol-type-parameter', 0xea92),
symbolKey: register('symbol-key', 0xea93),
symbolText: register('symbol-text', 0xea93),
symbolReference: register('symbol-reference', 0xea94),
goToFile: register('go-to-file', 0xea94),
symbolEnum: register('symbol-enum', 0xea95),
symbolValue: register('symbol-value', 0xea95),
symbolRuler: register('symbol-ruler', 0xea96),
symbolUnit: register('symbol-unit', 0xea96),
activateBreakpoints: register('activate-breakpoints', 0xea97),
archive: register('archive', 0xea98),
arrowBoth: register('arrow-both', 0xea99),
arrowDown: register('arrow-down', 0xea9a),
arrowLeft: register('arrow-left', 0xea9b),
arrowRight: register('arrow-right', 0xea9c),
arrowSmallDown: register('arrow-small-down', 0xea9d),
arrowSmallLeft: register('arrow-small-left', 0xea9e),
arrowSmallRight: register('arrow-small-right', 0xea9f),
arrowSmallUp: register('arrow-small-up', 0xeaa0),
arrowUp: register('arrow-up', 0xeaa1),
bell: register('bell', 0xeaa2),
bold: register('bold', 0xeaa3),
book: register('book', 0xeaa4),
bookmark: register('bookmark', 0xeaa5),
debugBreakpointConditionalUnverified: register('debug-breakpoint-conditional-unverified', 0xeaa6),
debugBreakpointConditional: register('debug-breakpoint-conditional', 0xeaa7),
debugBreakpointConditionalDisabled: register('debug-breakpoint-conditional-disabled', 0xeaa7),
debugBreakpointDataUnverified: register('debug-breakpoint-data-unverified', 0xeaa8),
debugBreakpointData: register('debug-breakpoint-data', 0xeaa9),
debugBreakpointDataDisabled: register('debug-breakpoint-data-disabled', 0xeaa9),
debugBreakpointLogUnverified: register('debug-breakpoint-log-unverified', 0xeaaa),
debugBreakpointLog: register('debug-breakpoint-log', 0xeaab),
debugBreakpointLogDisabled: register('debug-breakpoint-log-disabled', 0xeaab),
briefcase: register('briefcase', 0xeaac),
broadcast: register('broadcast', 0xeaad),
browser: register('browser', 0xeaae),
bug: register('bug', 0xeaaf),
calendar: register('calendar', 0xeab0),
caseSensitive: register('case-sensitive', 0xeab1),
check: register('check', 0xeab2),
checklist: register('checklist', 0xeab3),
chevronDown: register('chevron-down', 0xeab4),
chevronLeft: register('chevron-left', 0xeab5),
chevronRight: register('chevron-right', 0xeab6),
chevronUp: register('chevron-up', 0xeab7),
chromeClose: register('chrome-close', 0xeab8),
chromeMaximize: register('chrome-maximize', 0xeab9),
chromeMinimize: register('chrome-minimize', 0xeaba),
chromeRestore: register('chrome-restore', 0xeabb),
circleOutline: register('circle-outline', 0xeabc),
circle: register('circle', 0xeabc),
debugBreakpointUnverified: register('debug-breakpoint-unverified', 0xeabc),
terminalDecorationIncomplete: register('terminal-decoration-incomplete', 0xeabc),
circleSlash: register('circle-slash', 0xeabd),
circuitBoard: register('circuit-board', 0xeabe),
clearAll: register('clear-all', 0xeabf),
clippy: register('clippy', 0xeac0),
closeAll: register('close-all', 0xeac1),
cloudDownload: register('cloud-download', 0xeac2),
cloudUpload: register('cloud-upload', 0xeac3),
code: register('code', 0xeac4),
collapseAll: register('collapse-all', 0xeac5),
colorMode: register('color-mode', 0xeac6),
commentDiscussion: register('comment-discussion', 0xeac7),
creditCard: register('credit-card', 0xeac9),
dash: register('dash', 0xeacc),
dashboard: register('dashboard', 0xeacd),
database: register('database', 0xeace),
debugContinue: register('debug-continue', 0xeacf),
debugDisconnect: register('debug-disconnect', 0xead0),
debugPause: register('debug-pause', 0xead1),
debugRestart: register('debug-restart', 0xead2),
debugStart: register('debug-start', 0xead3),
debugStepInto: register('debug-step-into', 0xead4),
debugStepOut: register('debug-step-out', 0xead5),
debugStepOver: register('debug-step-over', 0xead6),
debugStop: register('debug-stop', 0xead7),
debug: register('debug', 0xead8),
deviceCameraVideo: register('device-camera-video', 0xead9),
deviceCamera: register('device-camera', 0xeada),
deviceMobile: register('device-mobile', 0xeadb),
diffAdded: register('diff-added', 0xeadc),
diffIgnored: register('diff-ignored', 0xeadd),
diffModified: register('diff-modified', 0xeade),
diffRemoved: register('diff-removed', 0xeadf),
diffRenamed: register('diff-renamed', 0xeae0),
diff: register('diff', 0xeae1),
diffSidebyside: register('diff-sidebyside', 0xeae1),
discard: register('discard', 0xeae2),
editorLayout: register('editor-layout', 0xeae3),
emptyWindow: register('empty-window', 0xeae4),
exclude: register('exclude', 0xeae5),
extensions: register('extensions', 0xeae6),
eyeClosed: register('eye-closed', 0xeae7),
fileBinary: register('file-binary', 0xeae8),
fileCode: register('file-code', 0xeae9),
fileMedia: register('file-media', 0xeaea),
filePdf: register('file-pdf', 0xeaeb),
fileSubmodule: register('file-submodule', 0xeaec),
fileSymlinkDirectory: register('file-symlink-directory', 0xeaed),
fileSymlinkFile: register('file-symlink-file', 0xeaee),
fileZip: register('file-zip', 0xeaef),
files: register('files', 0xeaf0),
filter: register('filter', 0xeaf1),
flame: register('flame', 0xeaf2),
foldDown: register('fold-down', 0xeaf3),
foldUp: register('fold-up', 0xeaf4),
fold: register('fold', 0xeaf5),
folderActive: register('folder-active', 0xeaf6),
folderOpened: register('folder-opened', 0xeaf7),
gear: register('gear', 0xeaf8),
gift: register('gift', 0xeaf9),
gistSecret: register('gist-secret', 0xeafa),
gist: register('gist', 0xeafb),
gitCommit: register('git-commit', 0xeafc),
gitCompare: register('git-compare', 0xeafd),
compareChanges: register('compare-changes', 0xeafd),
gitMerge: register('git-merge', 0xeafe),
githubAction: register('github-action', 0xeaff),
githubAlt: register('github-alt', 0xeb00),
globe: register('globe', 0xeb01),
grabber: register('grabber', 0xeb02),
graph: register('graph', 0xeb03),
gripper: register('gripper', 0xeb04),
heart: register('heart', 0xeb05),
home: register('home', 0xeb06),
horizontalRule: register('horizontal-rule', 0xeb07),
hubot: register('hubot', 0xeb08),
inbox: register('inbox', 0xeb09),
issueReopened: register('issue-reopened', 0xeb0b),
issues: register('issues', 0xeb0c),
italic: register('italic', 0xeb0d),
jersey: register('jersey', 0xeb0e),
json: register('json', 0xeb0f),
kebabVertical: register('kebab-vertical', 0xeb10),
key: register('key', 0xeb11),
law: register('law', 0xeb12),
lightbulbAutofix: register('lightbulb-autofix', 0xeb13),
linkExternal: register('link-external', 0xeb14),
link: register('link', 0xeb15),
listOrdered: register('list-ordered', 0xeb16),
listUnordered: register('list-unordered', 0xeb17),
liveShare: register('live-share', 0xeb18),
loading: register('loading', 0xeb19),
location: register('location', 0xeb1a),
mailRead: register('mail-read', 0xeb1b),
mail: register('mail', 0xeb1c),
markdown: register('markdown', 0xeb1d),
megaphone: register('megaphone', 0xeb1e),
mention: register('mention', 0xeb1f),
milestone: register('milestone', 0xeb20),
gitPullRequestMilestone: register('git-pull-request-milestone', 0xeb20),
mortarBoard: register('mortar-board', 0xeb21),
move: register('move', 0xeb22),
multipleWindows: register('multiple-windows', 0xeb23),
mute: register('mute', 0xeb24),
noNewline: register('no-newline', 0xeb25),
note: register('note', 0xeb26),
octoface: register('octoface', 0xeb27),
openPreview: register('open-preview', 0xeb28),
package: register('package', 0xeb29),
paintcan: register('paintcan', 0xeb2a),
pin: register('pin', 0xeb2b),
play: register('play', 0xeb2c),
run: register('run', 0xeb2c),
plug: register('plug', 0xeb2d),
preserveCase: register('preserve-case', 0xeb2e),
preview: register('preview', 0xeb2f),
project: register('project', 0xeb30),
pulse: register('pulse', 0xeb31),
question: register('question', 0xeb32),
quote: register('quote', 0xeb33),
radioTower: register('radio-tower', 0xeb34),
reactions: register('reactions', 0xeb35),
references: register('references', 0xeb36),
refresh: register('refresh', 0xeb37),
regex: register('regex', 0xeb38),
remoteExplorer: register('remote-explorer', 0xeb39),
remote: register('remote', 0xeb3a),
remove: register('remove', 0xeb3b),
replaceAll: register('replace-all', 0xeb3c),
replace: register('replace', 0xeb3d),
repoClone: register('repo-clone', 0xeb3e),
repoForcePush: register('repo-force-push', 0xeb3f),
repoPull: register('repo-pull', 0xeb40),
repoPush: register('repo-push', 0xeb41),
report: register('report', 0xeb42),
requestChanges: register('request-changes', 0xeb43),
rocket: register('rocket', 0xeb44),
rootFolderOpened: register('root-folder-opened', 0xeb45),
rootFolder: register('root-folder', 0xeb46),
rss: register('rss', 0xeb47),
ruby: register('ruby', 0xeb48),
saveAll: register('save-all', 0xeb49),
saveAs: register('save-as', 0xeb4a),
save: register('save', 0xeb4b),
screenFull: register('screen-full', 0xeb4c),
screenNormal: register('screen-normal', 0xeb4d),
searchStop: register('search-stop', 0xeb4e),
server: register('server', 0xeb50),
settingsGear: register('settings-gear', 0xeb51),
settings: register('settings', 0xeb52),
shield: register('shield', 0xeb53),
smiley: register('smiley', 0xeb54),
sortPrecedence: register('sort-precedence', 0xeb55),
splitHorizontal: register('split-horizontal', 0xeb56),
splitVertical: register('split-vertical', 0xeb57),
squirrel: register('squirrel', 0xeb58),
starFull: register('star-full', 0xeb59),
starHalf: register('star-half', 0xeb5a),
symbolClass: register('symbol-class', 0xeb5b),
symbolColor: register('symbol-color', 0xeb5c),
symbolConstant: register('symbol-constant', 0xeb5d),
symbolEnumMember: register('symbol-enum-member', 0xeb5e),
symbolField: register('symbol-field', 0xeb5f),
symbolFile: register('symbol-file', 0xeb60),
symbolInterface: register('symbol-interface', 0xeb61),
symbolKeyword: register('symbol-keyword', 0xeb62),
symbolMisc: register('symbol-misc', 0xeb63),
symbolOperator: register('symbol-operator', 0xeb64),
symbolProperty: register('symbol-property', 0xeb65),
wrench: register('wrench', 0xeb65),
wrenchSubaction: register('wrench-subaction', 0xeb65),
symbolSnippet: register('symbol-snippet', 0xeb66),
tasklist: register('tasklist', 0xeb67),
telescope: register('telescope', 0xeb68),
textSize: register('text-size', 0xeb69),
threeBars: register('three-bars', 0xeb6a),
thumbsdown: register('thumbsdown', 0xeb6b),
thumbsup: register('thumbsup', 0xeb6c),
tools: register('tools', 0xeb6d),
triangleDown: register('triangle-down', 0xeb6e),
triangleLeft: register('triangle-left', 0xeb6f),
triangleRight: register('triangle-right', 0xeb70),
triangleUp: register('triangle-up', 0xeb71),
twitter: register('twitter', 0xeb72),
unfold: register('unfold', 0xeb73),
unlock: register('unlock', 0xeb74),
unmute: register('unmute', 0xeb75),
unverified: register('unverified', 0xeb76),
verified: register('verified', 0xeb77),
versions: register('versions', 0xeb78),
vmActive: register('vm-active', 0xeb79),
vmOutline: register('vm-outline', 0xeb7a),
vmRunning: register('vm-running', 0xeb7b),
watch: register('watch', 0xeb7c),
whitespace: register('whitespace', 0xeb7d),
wholeWord: register('whole-word', 0xeb7e),
window: register('window', 0xeb7f),
wordWrap: register('word-wrap', 0xeb80),
zoomIn: register('zoom-in', 0xeb81),
zoomOut: register('zoom-out', 0xeb82),
listFilter: register('list-filter', 0xeb83),
listFlat: register('list-flat', 0xeb84),
listSelection: register('list-selection', 0xeb85),
selection: register('selection', 0xeb85),
listTree: register('list-tree', 0xeb86),
debugBreakpointFunctionUnverified: register('debug-breakpoint-function-unverified', 0xeb87),
debugBreakpointFunction: register('debug-breakpoint-function', 0xeb88),
debugBreakpointFunctionDisabled: register('debug-breakpoint-function-disabled', 0xeb88),
debugStackframeActive: register('debug-stackframe-active', 0xeb89),
circleSmallFilled: register('circle-small-filled', 0xeb8a),
debugStackframeDot: register('debug-stackframe-dot', 0xeb8a),
terminalDecorationMark: register('terminal-decoration-mark', 0xeb8a),
debugStackframe: register('debug-stackframe', 0xeb8b),
debugStackframeFocused: register('debug-stackframe-focused', 0xeb8b),
debugBreakpointUnsupported: register('debug-breakpoint-unsupported', 0xeb8c),
symbolString: register('symbol-string', 0xeb8d),
debugReverseContinue: register('debug-reverse-continue', 0xeb8e),
debugStepBack: register('debug-step-back', 0xeb8f),
debugRestartFrame: register('debug-restart-frame', 0xeb90),
debugAlt: register('debug-alt', 0xeb91),
callIncoming: register('call-incoming', 0xeb92),
callOutgoing: register('call-outgoing', 0xeb93),
menu: register('menu', 0xeb94),
expandAll: register('expand-all', 0xeb95),
feedback: register('feedback', 0xeb96),
gitPullRequestReviewer: register('git-pull-request-reviewer', 0xeb96),
groupByRefType: register('group-by-ref-type', 0xeb97),
ungroupByRefType: register('ungroup-by-ref-type', 0xeb98),
account: register('account', 0xeb99),
gitPullRequestAssignee: register('git-pull-request-assignee', 0xeb99),
bellDot: register('bell-dot', 0xeb9a),
debugConsole: register('debug-console', 0xeb9b),
library: register('library', 0xeb9c),
output: register('output', 0xeb9d),
runAll: register('run-all', 0xeb9e),
syncIgnored: register('sync-ignored', 0xeb9f),
pinned: register('pinned', 0xeba0),
githubInverted: register('github-inverted', 0xeba1),
serverProcess: register('server-process', 0xeba2),
serverEnvironment: register('server-environment', 0xeba3),
pass: register('pass', 0xeba4),
issueClosed: register('issue-closed', 0xeba4),
stopCircle: register('stop-circle', 0xeba5),
playCircle: register('play-circle', 0xeba6),
record: register('record', 0xeba7),
debugAltSmall: register('debug-alt-small', 0xeba8),
vmConnect: register('vm-connect', 0xeba9),
cloud: register('cloud', 0xebaa),
merge: register('merge', 0xebab),
export: register('export', 0xebac),
graphLeft: register('graph-left', 0xebad),
magnet: register('magnet', 0xebae),
notebook: register('notebook', 0xebaf),
redo: register('redo', 0xebb0),
checkAll: register('check-all', 0xebb1),
pinnedDirty: register('pinned-dirty', 0xebb2),
passFilled: register('pass-filled', 0xebb3),
circleLargeFilled: register('circle-large-filled', 0xebb4),
circleLarge: register('circle-large', 0xebb5),
circleLargeOutline: register('circle-large-outline', 0xebb5),
combine: register('combine', 0xebb6),
gather: register('gather', 0xebb6),
table: register('table', 0xebb7),
variableGroup: register('variable-group', 0xebb8),
typeHierarchy: register('type-hierarchy', 0xebb9),
typeHierarchySub: register('type-hierarchy-sub', 0xebba),
typeHierarchySuper: register('type-hierarchy-super', 0xebbb),
gitPullRequestCreate: register('git-pull-request-create', 0xebbc),
runAbove: register('run-above', 0xebbd),
runBelow: register('run-below', 0xebbe),
notebookTemplate: register('notebook-template', 0xebbf),
debugRerun: register('debug-rerun', 0xebc0),
workspaceTrusted: register('workspace-trusted', 0xebc1),
workspaceUntrusted: register('workspace-untrusted', 0xebc2),
workspaceUnknown: register('workspace-unknown', 0xebc3),
terminalCmd: register('terminal-cmd', 0xebc4),
terminalDebian: register('terminal-debian', 0xebc5),
terminalLinux: register('terminal-linux', 0xebc6),
terminalPowershell: register('terminal-powershell', 0xebc7),
terminalTmux: register('terminal-tmux', 0xebc8),
terminalUbuntu: register('terminal-ubuntu', 0xebc9),
terminalBash: register('terminal-bash', 0xebca),
arrowSwap: register('arrow-swap', 0xebcb),
copy: register('copy', 0xebcc),
personAdd: register('person-add', 0xebcd),
filterFilled: register('filter-filled', 0xebce),
wand: register('wand', 0xebcf),
debugLineByLine: register('debug-line-by-line', 0xebd0),
inspect: register('inspect', 0xebd1),
layers: register('layers', 0xebd2),
layersDot: register('layers-dot', 0xebd3),
layersActive: register('layers-active', 0xebd4),
compass: register('compass', 0xebd5),
compassDot: register('compass-dot', 0xebd6),
compassActive: register('compass-active', 0xebd7),
azure: register('azure', 0xebd8),
issueDraft: register('issue-draft', 0xebd9),
gitPullRequestClosed: register('git-pull-request-closed', 0xebda),
gitPullRequestDraft: register('git-pull-request-draft', 0xebdb),
debugAll: register('debug-all', 0xebdc),
debugCoverage: register('debug-coverage', 0xebdd),
runErrors: register('run-errors', 0xebde),
folderLibrary: register('folder-library', 0xebdf),
debugContinueSmall: register('debug-continue-small', 0xebe0),
beakerStop: register('beaker-stop', 0xebe1),
graphLine: register('graph-line', 0xebe2),
graphScatter: register('graph-scatter', 0xebe3),
pieChart: register('pie-chart', 0xebe4),
bracket: register('bracket', 0xeb0f),
bracketDot: register('bracket-dot', 0xebe5),
bracketError: register('bracket-error', 0xebe6),
lockSmall: register('lock-small', 0xebe7),
azureDevops: register('azure-devops', 0xebe8),
verifiedFilled: register('verified-filled', 0xebe9),
newline: register('newline', 0xebea),
layout: register('layout', 0xebeb),
layoutActivitybarLeft: register('layout-activitybar-left', 0xebec),
layoutActivitybarRight: register('layout-activitybar-right', 0xebed),
layoutPanelLeft: register('layout-panel-left', 0xebee),
layoutPanelCenter: register('layout-panel-center', 0xebef),
layoutPanelJustify: register('layout-panel-justify', 0xebf0),
layoutPanelRight: register('layout-panel-right', 0xebf1),
layoutPanel: register('layout-panel', 0xebf2),
layoutSidebarLeft: register('layout-sidebar-left', 0xebf3),
layoutSidebarRight: register('layout-sidebar-right', 0xebf4),
layoutStatusbar: register('layout-statusbar', 0xebf5),
layoutMenubar: register('layout-menubar', 0xebf6),
layoutCentered: register('layout-centered', 0xebf7),
target: register('target', 0xebf8),
indent: register('indent', 0xebf9),
recordSmall: register('record-small', 0xebfa),
errorSmall: register('error-small', 0xebfb),
terminalDecorationError: register('terminal-decoration-error', 0xebfb),
arrowCircleDown: register('arrow-circle-down', 0xebfc),
arrowCircleLeft: register('arrow-circle-left', 0xebfd),
arrowCircleRight: register('arrow-circle-right', 0xebfe),
arrowCircleUp: register('arrow-circle-up', 0xebff),
layoutSidebarRightOff: register('layout-sidebar-right-off', 0xec00),
layoutPanelOff: register('layout-panel-off', 0xec01),
layoutSidebarLeftOff: register('layout-sidebar-left-off', 0xec02),
blank: register('blank', 0xec03),
heartFilled: register('heart-filled', 0xec04),
map: register('map', 0xec05),
mapHorizontal: register('map-horizontal', 0xec05),
foldHorizontal: register('fold-horizontal', 0xec05),
mapFilled: register('map-filled', 0xec06),
mapHorizontalFilled: register('map-horizontal-filled', 0xec06),
foldHorizontalFilled: register('fold-horizontal-filled', 0xec06),
circleSmall: register('circle-small', 0xec07),
bellSlash: register('bell-slash', 0xec08),
bellSlashDot: register('bell-slash-dot', 0xec09),
commentUnresolved: register('comment-unresolved', 0xec0a),
gitPullRequestGoToChanges: register('git-pull-request-go-to-changes', 0xec0b),
gitPullRequestNewChanges: register('git-pull-request-new-changes', 0xec0c),
searchFuzzy: register('search-fuzzy', 0xec0d),
commentDraft: register('comment-draft', 0xec0e),
send: register('send', 0xec0f),
sparkle: register('sparkle', 0xec10),
insert: register('insert', 0xec11),
mic: register('mic', 0xec12),
thumbsdownFilled: register('thumbsdown-filled', 0xec13),
thumbsupFilled: register('thumbsup-filled', 0xec14),
coffee: register('coffee', 0xec15),
snake: register('snake', 0xec16),
game: register('game', 0xec17),
vr: register('vr', 0xec18),
chip: register('chip', 0xec19),
piano: register('piano', 0xec1a),
music: register('music', 0xec1b),
micFilled: register('mic-filled', 0xec1c),
repoFetch: register('repo-fetch', 0xec1d),
copilot: register('copilot', 0xec1e),
lightbulbSparkle: register('lightbulb-sparkle', 0xec1f),
robot: register('robot', 0xec20),
sparkleFilled: register('sparkle-filled', 0xec21),
diffSingle: register('diff-single', 0xec22),
diffMultiple: register('diff-multiple', 0xec23),
surroundWith: register('surround-with', 0xec24),
share: register('share', 0xec25),
gitStash: register('git-stash', 0xec26),
gitStashApply: register('git-stash-apply', 0xec27),
gitStashPop: register('git-stash-pop', 0xec28),
vscode: register('vscode', 0xec29),
vscodeInsiders: register('vscode-insiders', 0xec2a),
codeOss: register('code-oss', 0xec2b),
runCoverage: register('run-coverage', 0xec2c),
runAllCoverage: register('run-all-coverage', 0xec2d),
coverage: register('coverage', 0xec2e),
githubProject: register('github-project', 0xec2f),
mapVertical: register('map-vertical', 0xec30),
foldVertical: register('fold-vertical', 0xec30),
mapVerticalFilled: register('map-vertical-filled', 0xec31),
foldVerticalFilled: register('fold-vertical-filled', 0xec31),
goToSearch: register('go-to-search', 0xec32),
percentage: register('percentage', 0xec33),
sortPercentage: register('sort-percentage', 0xec33),
attach: register('attach', 0xec34),
};
@@ -0,0 +1,19 @@
import { isString } from './types.js';
const _codiconFontCharacters = Object.create(null);
export function register(id, fontCharacter) {
if (isString(fontCharacter)) {
const val = _codiconFontCharacters[fontCharacter];
if (val === undefined) {
throw new Error(`${id} references an unknown codicon: ${fontCharacter}`);
}
fontCharacter = val;
}
_codiconFontCharacters[id] = fontCharacter;
return { id };
}
/**
* Only to be used by the iconRegistry.
*/
export function getCodiconFontCharacters() {
return _codiconFontCharacters;
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function diffSets(before, after) {
const removed = [];
const added = [];
for (const element of before) {
if (!after.has(element)) {
removed.push(element);
}
}
for (const element of after) {
if (!before.has(element)) {
added.push(element);
}
}
return { removed, added };
}
/**
* Computes the intersection of two sets.
*
* @param setA - The first set.
* @param setB - The second iterable.
* @returns A new set containing the elements that are in both `setA` and `setB`.
*/
export function intersection(setA, setB) {
const result = new Set();
for (const elem of setB) {
if (setA.has(elem)) {
result.add(elem);
}
}
return result;
}
+462
View File
@@ -0,0 +1,462 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function roundFloat(number, decimalPoints) {
const decimal = Math.pow(10, decimalPoints);
return Math.round(number * decimal) / decimal;
}
export class RGBA {
constructor(r, g, b, a = 1) {
this._rgbaBrand = undefined;
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, b) {
return a.r === b.r && a.g === b.g && a.b === b.b && a.a === b.a;
}
}
export class HSLA {
constructor(h, s, l, a) {
this._hslaBrand = undefined;
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, b) {
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) {
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);
}
static _hue2rgb(p, q, t) {
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) {
const h = hsla.h / 360;
const { s, l, a } = hsla;
let r, g, b;
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 {
constructor(h, s, v, a) {
this._hsvaBrand = undefined;
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, b) {
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) {
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;
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) {
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) {
return Color.Format.CSS.parseHex(hex) || Color.red;
}
static equals(a, b) {
if (!a && !b) {
return true;
}
if (!a || !b) {
return false;
}
return a.equals(b);
}
get hsla() {
if (this._hsla) {
return this._hsla;
}
else {
return HSLA.fromRGBA(this.rgba);
}
}
get hsva() {
if (this._hsva) {
return this._hsva;
}
return HSVA.fromRGBA(this.rgba);
}
constructor(arg) {
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) {
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() {
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);
}
static _relativeLuminanceForComponent(color) {
const c = color / 255;
return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4);
}
/**
* http://24ways.org/2010/calculating-color-contrast
* Return 'true' if lighter color otherwise 'false'
*/
isLighter() {
const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
return yiq >= 128;
}
isLighterThan(another) {
const lum1 = this.getRelativeLuminance();
const lum2 = another.getRelativeLuminance();
return lum1 > lum2;
}
isDarkerThan(another) {
const lum1 = this.getRelativeLuminance();
const lum2 = another.getRelativeLuminance();
return lum1 < lum2;
}
lighten(factor) {
return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l + this.hsla.l * factor, this.hsla.a));
}
darken(factor) {
return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l - this.hsla.l * factor, this.hsla.a));
}
transparent(factor) {
const { r, g, b, a } = this.rgba;
return new Color(new RGBA(r, g, b, a * factor));
}
isTransparent() {
return this.rgba.a === 0;
}
isOpaque() {
return this.rgba.a === 1;
}
opposite() {
return new Color(new RGBA(255 - this.rgba.r, 255 - this.rgba.g, 255 - this.rgba.b, this.rgba.a));
}
makeOpaque(opaqueBackground) {
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));
}
toString() {
if (!this._toString) {
this._toString = Color.Format.CSS.format(this);
}
return this._toString;
}
static getLighterColor(of, relative, factor) {
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, relative, factor) {
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 { this.white = new Color(new RGBA(255, 255, 255, 1)); }
static { this.black = new Color(new RGBA(0, 0, 0, 1)); }
static { this.red = new Color(new RGBA(255, 0, 0, 1)); }
static { this.blue = new Color(new RGBA(0, 0, 255, 1)); }
static { this.green = new Color(new RGBA(0, 255, 0, 1)); }
static { this.cyan = new Color(new RGBA(0, 255, 255, 1)); }
static { this.lightgrey = new Color(new RGBA(211, 211, 211, 1)); }
static { this.transparent = new Color(new RGBA(0, 0, 0, 0)); }
}
(function (Color) {
let Format;
(function (Format) {
let CSS;
(function (CSS) {
function formatRGB(color) {
if (color.rgba.a === 1) {
return `rgb(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b})`;
}
return Color.Format.CSS.formatRGBA(color);
}
CSS.formatRGB = formatRGB;
function formatRGBA(color) {
return `rgba(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b}, ${+(color.rgba.a).toFixed(2)})`;
}
CSS.formatRGBA = formatRGBA;
function formatHSL(color) {
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);
}
CSS.formatHSL = formatHSL;
function formatHSLA(color) {
return `hsla(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%, ${color.hsla.a.toFixed(2)})`;
}
CSS.formatHSLA = formatHSLA;
function _toTwoDigitHex(n) {
const r = n.toString(16);
return r.length !== 2 ? '0' + r : r;
}
/**
* Formats the color as #RRGGBB
*/
function formatHex(color) {
return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}`;
}
CSS.formatHex = formatHex;
/**
* Formats the color as #RRGGBBAA
* If 'compact' is set, colors without transparancy will be printed as #RRGGBB
*/
function formatHexA(color, compact = false) {
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))}`;
}
CSS.formatHexA = formatHexA;
/**
* The default format will use HEX if opaque and RGBA otherwise.
*/
function format(color) {
if (color.isOpaque()) {
return Color.Format.CSS.formatHex(color);
}
return Color.Format.CSS.formatRGBA(color);
}
CSS.format = format;
/**
* 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).
*/
function parseHex(hex) {
const length = hex.length;
if (length === 0) {
// Invalid color
return null;
}
if (hex.charCodeAt(0) !== 35 /* 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;
}
CSS.parseHex = parseHex;
function _parseHexDigit(charCode) {
switch (charCode) {
case 48 /* CharCode.Digit0 */: return 0;
case 49 /* CharCode.Digit1 */: return 1;
case 50 /* CharCode.Digit2 */: return 2;
case 51 /* CharCode.Digit3 */: return 3;
case 52 /* CharCode.Digit4 */: return 4;
case 53 /* CharCode.Digit5 */: return 5;
case 54 /* CharCode.Digit6 */: return 6;
case 55 /* CharCode.Digit7 */: return 7;
case 56 /* CharCode.Digit8 */: return 8;
case 57 /* CharCode.Digit9 */: return 9;
case 97 /* CharCode.a */: return 10;
case 65 /* CharCode.A */: return 10;
case 98 /* CharCode.b */: return 11;
case 66 /* CharCode.B */: return 11;
case 99 /* CharCode.c */: return 12;
case 67 /* CharCode.C */: return 12;
case 100 /* CharCode.d */: return 13;
case 68 /* CharCode.D */: return 13;
case 101 /* CharCode.e */: return 14;
case 69 /* CharCode.E */: return 14;
case 102 /* CharCode.f */: return 15;
case 70 /* CharCode.F */: return 15;
}
return 0;
}
})(CSS = Format.CSS || (Format.CSS = {}));
})(Format = Color.Format || (Color.Format = {}));
})(Color || (Color = {}));
@@ -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 { Lazy } from './lazy.js';
// When comparing large numbers of strings it's 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, accents or diacritics.
const intlFileNameCollatorBaseNumeric = new Lazy(() => {
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
return {
collator,
collatorIsNumeric: collator.resolvedOptions().numeric
};
});
// A collator with numeric sorting enabled.
const intlFileNameCollatorNumeric = new Lazy(() => {
const collator = new Intl.Collator(undefined, { numeric: true });
return {
collator
};
});
// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
const intlFileNameCollatorNumericCaseInsensitive = new Lazy(() => {
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' });
return {
collator
};
});
/** Compares filenames without distinguishing the name from the extension. Disambiguates by unicode comparison. */
export function compareFileNames(one, other, caseSensitive = false) {
const a = one || '';
const b = other || '';
const result = intlFileNameCollatorBaseNumeric.value.collator.compare(a, b);
// Using the numeric option will make compare(`foo1`, `foo01`) === 0. Disambiguate.
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && a !== b) {
return a < b ? -1 : 1;
}
return result;
}
export function compareAnything(one, other, lookFor) {
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, other, lookFor) {
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;
}
@@ -0,0 +1,128 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { distinct } from './arrays.js';
import { Iterable } from './iterator.js';
import { generateUuid } from './uuid.js';
export function createStringDataTransferItem(stringOrPromise) {
return {
asString: async () => stringOrPromise,
asFile: () => undefined,
value: typeof stringOrPromise === 'string' ? stringOrPromise : undefined,
};
}
export function createFileDataTransferItem(fileName, uri, data) {
const file = { id: generateUuid(), name: fileName, uri, data };
return {
asString: async () => '',
asFile: () => file,
value: undefined,
};
}
export class VSDataTransfer {
constructor() {
this._entries = new Map();
}
get size() {
let size = 0;
for (const _ of this._entries) {
size++;
}
return size;
}
has(mimeType) {
return this._entries.has(this.toKey(mimeType));
}
matches(pattern) {
const mimes = [...this._entries.keys()];
if (Iterable.some(this, ([_, item]) => item.asFile())) {
mimes.push('files');
}
return matchesMimeType_normalized(normalizeMimeType(pattern), mimes);
}
get(mimeType) {
return this._entries.get(this.toKey(mimeType))?.[0];
}
/**
* Add a new entry to this data transfer.
*
* This does not replace existing entries for `mimeType`.
*/
append(mimeType, value) {
const existing = this._entries.get(mimeType);
if (existing) {
existing.push(value);
}
else {
this._entries.set(this.toKey(mimeType), [value]);
}
}
/**
* Set the entry for a given mime type.
*
* This replaces all existing entries for `mimeType`.
*/
replace(mimeType, value) {
this._entries.set(this.toKey(mimeType), [value]);
}
/**
* Remove all entries for `mimeType`.
*/
delete(mimeType) {
this._entries.delete(this.toKey(mimeType));
}
/**
* Iterate over all `[mime, item]` pairs in this data transfer.
*
* There may be multiple entries for each mime type.
*/
*[Symbol.iterator]() {
for (const [mine, items] of this._entries) {
for (const item of items) {
yield [mine, item];
}
}
}
toKey(mimeType) {
return normalizeMimeType(mimeType);
}
}
function normalizeMimeType(mimeType) {
return mimeType.toLowerCase();
}
export function matchesMimeType(pattern, mimeTypes) {
return matchesMimeType_normalized(normalizeMimeType(pattern), mimeTypes.map(normalizeMimeType));
}
function matchesMimeType_normalized(normalizedPattern, normalizedMimeTypes) {
// Anything wildcard
if (normalizedPattern === '*/*') {
return normalizedMimeTypes.length > 0;
}
// Exact match
if (normalizedMimeTypes.includes(normalizedPattern)) {
return true;
}
// Wildcard, such as `image/*`
const wildcard = normalizedPattern.match(/^([a-z]+)\/([a-z]+|\*)$/i);
if (!wildcard) {
return false;
}
const [_, type, subtype] = wildcard;
if (subtype === '*') {
return normalizedMimeTypes.some(mime => mime.startsWith(type + '/'));
}
return false;
}
export const UriList = Object.freeze({
// http://amundsen.com/hypermedia/urilist/
create: (entries) => {
return distinct(entries.map(x => x.toString())).join('\r\n');
},
split: (str) => {
return str.split('\r\n');
},
parse: (str) => {
return UriList.split(str).filter(value => !value.startsWith('#'));
}
});
@@ -0,0 +1,30 @@
export function memoize(_target, key, descriptor) {
let fnKey = null;
let fn = 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 = `$memoize$${key}`;
descriptor[fnKey] = function (...args) {
if (!this.hasOwnProperty(memoizeKey)) {
Object.defineProperty(this, memoizeKey, {
configurable: false,
enumerable: false,
writable: false,
value: fn.apply(this, args)
});
}
return this[memoizeKey];
};
}
@@ -0,0 +1,899 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DiffChange } from './diffChange.js';
import { stringHash } from '../hash.js';
export class StringDiffSequence {
constructor(source) {
this.source = source;
}
getElements() {
const source = this.source;
const characters = new Int32Array(source.length);
for (let i = 0, len = source.length; i < len; i++) {
characters[i] = source.charCodeAt(i);
}
return characters;
}
}
export function stringDiff(original, modified, pretty) {
return new LcsDiff(new StringDiffSequence(original), new StringDiffSequence(modified)).ComputeDiff(pretty).changes;
}
//
// The code below has been ported from a C# implementation in VS
//
class Debug {
static Assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
}
class MyArray {
/**
* Copies a range of elements from an Array starting at the specified source index and pastes
* them to another Array starting at the specified destination index. The length and the indexes
* are specified as 64-bit integers.
* sourceArray:
* The Array that contains the data to copy.
* sourceIndex:
* A 64-bit integer that represents the index in the sourceArray at which copying begins.
* destinationArray:
* The Array that receives the data.
* destinationIndex:
* A 64-bit integer that represents the index in the destinationArray at which storing begins.
* length:
* A 64-bit integer that represents the number of elements to copy.
*/
static Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length) {
for (let i = 0; i < length; i++) {
destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i];
}
}
static Copy2(sourceArray, sourceIndex, destinationArray, destinationIndex, length) {
for (let i = 0; i < length; i++) {
destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i];
}
}
}
/**
* A utility class which helps to create the set of DiffChanges from
* a difference operation. This class accepts original DiffElements and
* modified DiffElements that are involved in a particular change. The
* MarkNextChange() method can be called to mark the separation between
* distinct changes. At the end, the Changes property can be called to retrieve
* the constructed changes.
*/
class DiffChangeHelper {
/**
* Constructs a new DiffChangeHelper for the given DiffSequences.
*/
constructor() {
this.m_changes = [];
this.m_originalStart = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
this.m_modifiedStart = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
this.m_originalCount = 0;
this.m_modifiedCount = 0;
}
/**
* Marks the beginning of the next change in the set of differences.
*/
MarkNextChange() {
// Only add to the list if there is something to add
if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {
// Add the new change to our list
this.m_changes.push(new DiffChange(this.m_originalStart, this.m_originalCount, this.m_modifiedStart, this.m_modifiedCount));
}
// Reset for the next change
this.m_originalCount = 0;
this.m_modifiedCount = 0;
this.m_originalStart = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
this.m_modifiedStart = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
}
/**
* Adds the original element at the given position to the elements
* affected by the current change. The modified index gives context
* to the change position with respect to the original sequence.
* @param originalIndex The index of the original element to add.
* @param modifiedIndex The index of the modified element that provides corresponding position in the modified sequence.
*/
AddOriginalElement(originalIndex, modifiedIndex) {
// The 'true' start index is the smallest of the ones we've seen
this.m_originalStart = Math.min(this.m_originalStart, originalIndex);
this.m_modifiedStart = Math.min(this.m_modifiedStart, modifiedIndex);
this.m_originalCount++;
}
/**
* Adds the modified element at the given position to the elements
* affected by the current change. The original index gives context
* to the change position with respect to the modified sequence.
* @param originalIndex The index of the original element that provides corresponding position in the original sequence.
* @param modifiedIndex The index of the modified element to add.
*/
AddModifiedElement(originalIndex, modifiedIndex) {
// The 'true' start index is the smallest of the ones we've seen
this.m_originalStart = Math.min(this.m_originalStart, originalIndex);
this.m_modifiedStart = Math.min(this.m_modifiedStart, modifiedIndex);
this.m_modifiedCount++;
}
/**
* Retrieves all of the changes marked by the class.
*/
getChanges() {
if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {
// Finish up on whatever is left
this.MarkNextChange();
}
return this.m_changes;
}
/**
* Retrieves all of the changes marked by the class in the reverse order
*/
getReverseChanges() {
if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {
// Finish up on whatever is left
this.MarkNextChange();
}
this.m_changes.reverse();
return this.m_changes;
}
}
/**
* An implementation of the difference algorithm described in
* "An O(ND) Difference Algorithm and its variations" by Eugene W. Myers
*/
export class LcsDiff {
/**
* Constructs the DiffFinder
*/
constructor(originalSequence, modifiedSequence, continueProcessingPredicate = null) {
this.ContinueProcessingPredicate = continueProcessingPredicate;
this._originalSequence = originalSequence;
this._modifiedSequence = modifiedSequence;
const [originalStringElements, originalElementsOrHash, originalHasStrings] = LcsDiff._getElements(originalSequence);
const [modifiedStringElements, modifiedElementsOrHash, modifiedHasStrings] = LcsDiff._getElements(modifiedSequence);
this._hasStrings = (originalHasStrings && modifiedHasStrings);
this._originalStringElements = originalStringElements;
this._originalElementsOrHash = originalElementsOrHash;
this._modifiedStringElements = modifiedStringElements;
this._modifiedElementsOrHash = modifiedElementsOrHash;
this.m_forwardHistory = [];
this.m_reverseHistory = [];
}
static _isStringArray(arr) {
return (arr.length > 0 && typeof arr[0] === 'string');
}
static _getElements(sequence) {
const elements = sequence.getElements();
if (LcsDiff._isStringArray(elements)) {
const hashes = new Int32Array(elements.length);
for (let i = 0, len = elements.length; i < len; i++) {
hashes[i] = stringHash(elements[i], 0);
}
return [elements, hashes, true];
}
if (elements instanceof Int32Array) {
return [[], elements, false];
}
return [[], new Int32Array(elements), false];
}
ElementsAreEqual(originalIndex, newIndex) {
if (this._originalElementsOrHash[originalIndex] !== this._modifiedElementsOrHash[newIndex]) {
return false;
}
return (this._hasStrings ? this._originalStringElements[originalIndex] === this._modifiedStringElements[newIndex] : true);
}
ElementsAreStrictEqual(originalIndex, newIndex) {
if (!this.ElementsAreEqual(originalIndex, newIndex)) {
return false;
}
const originalElement = LcsDiff._getStrictElement(this._originalSequence, originalIndex);
const modifiedElement = LcsDiff._getStrictElement(this._modifiedSequence, newIndex);
return (originalElement === modifiedElement);
}
static _getStrictElement(sequence, index) {
if (typeof sequence.getStrictElement === 'function') {
return sequence.getStrictElement(index);
}
return null;
}
OriginalElementsAreEqual(index1, index2) {
if (this._originalElementsOrHash[index1] !== this._originalElementsOrHash[index2]) {
return false;
}
return (this._hasStrings ? this._originalStringElements[index1] === this._originalStringElements[index2] : true);
}
ModifiedElementsAreEqual(index1, index2) {
if (this._modifiedElementsOrHash[index1] !== this._modifiedElementsOrHash[index2]) {
return false;
}
return (this._hasStrings ? this._modifiedStringElements[index1] === this._modifiedStringElements[index2] : true);
}
ComputeDiff(pretty) {
return this._ComputeDiff(0, this._originalElementsOrHash.length - 1, 0, this._modifiedElementsOrHash.length - 1, pretty);
}
/**
* Computes the differences between the original and modified input
* sequences on the bounded range.
* @returns An array of the differences between the two input sequences.
*/
_ComputeDiff(originalStart, originalEnd, modifiedStart, modifiedEnd, pretty) {
const quitEarlyArr = [false];
let changes = this.ComputeDiffRecursive(originalStart, originalEnd, modifiedStart, modifiedEnd, quitEarlyArr);
if (pretty) {
// We have to clean up the computed diff to be more intuitive
// but it turns out this cannot be done correctly until the entire set
// of diffs have been computed
changes = this.PrettifyChanges(changes);
}
return {
quitEarly: quitEarlyArr[0],
changes: changes
};
}
/**
* Private helper method which computes the differences on the bounded range
* recursively.
* @returns An array of the differences between the two input sequences.
*/
ComputeDiffRecursive(originalStart, originalEnd, modifiedStart, modifiedEnd, quitEarlyArr) {
quitEarlyArr[0] = false;
// Find the start of the differences
while (originalStart <= originalEnd && modifiedStart <= modifiedEnd && this.ElementsAreEqual(originalStart, modifiedStart)) {
originalStart++;
modifiedStart++;
}
// Find the end of the differences
while (originalEnd >= originalStart && modifiedEnd >= modifiedStart && this.ElementsAreEqual(originalEnd, modifiedEnd)) {
originalEnd--;
modifiedEnd--;
}
// In the special case where we either have all insertions or all deletions or the sequences are identical
if (originalStart > originalEnd || modifiedStart > modifiedEnd) {
let changes;
if (modifiedStart <= modifiedEnd) {
Debug.Assert(originalStart === originalEnd + 1, 'originalStart should only be one more than originalEnd');
// All insertions
changes = [
new DiffChange(originalStart, 0, modifiedStart, modifiedEnd - modifiedStart + 1)
];
}
else if (originalStart <= originalEnd) {
Debug.Assert(modifiedStart === modifiedEnd + 1, 'modifiedStart should only be one more than modifiedEnd');
// All deletions
changes = [
new DiffChange(originalStart, originalEnd - originalStart + 1, modifiedStart, 0)
];
}
else {
Debug.Assert(originalStart === originalEnd + 1, 'originalStart should only be one more than originalEnd');
Debug.Assert(modifiedStart === modifiedEnd + 1, 'modifiedStart should only be one more than modifiedEnd');
// Identical sequences - No differences
changes = [];
}
return changes;
}
// This problem can be solved using the Divide-And-Conquer technique.
const midOriginalArr = [0];
const midModifiedArr = [0];
const result = this.ComputeRecursionPoint(originalStart, originalEnd, modifiedStart, modifiedEnd, midOriginalArr, midModifiedArr, quitEarlyArr);
const midOriginal = midOriginalArr[0];
const midModified = midModifiedArr[0];
if (result !== null) {
// Result is not-null when there was enough memory to compute the changes while
// searching for the recursion point
return result;
}
else if (!quitEarlyArr[0]) {
// We can break the problem down recursively by finding the changes in the
// First Half: (originalStart, modifiedStart) to (midOriginal, midModified)
// Second Half: (midOriginal + 1, minModified + 1) to (originalEnd, modifiedEnd)
// NOTE: ComputeDiff() is inclusive, therefore the second range starts on the next point
const leftChanges = this.ComputeDiffRecursive(originalStart, midOriginal, modifiedStart, midModified, quitEarlyArr);
let rightChanges = [];
if (!quitEarlyArr[0]) {
rightChanges = this.ComputeDiffRecursive(midOriginal + 1, originalEnd, midModified + 1, modifiedEnd, quitEarlyArr);
}
else {
// We didn't have time to finish the first half, so we don't have time to compute this half.
// Consider the entire rest of the sequence different.
rightChanges = [
new DiffChange(midOriginal + 1, originalEnd - (midOriginal + 1) + 1, midModified + 1, modifiedEnd - (midModified + 1) + 1)
];
}
return this.ConcatenateChanges(leftChanges, rightChanges);
}
// If we hit here, we quit early, and so can't return anything meaningful
return [
new DiffChange(originalStart, originalEnd - originalStart + 1, modifiedStart, modifiedEnd - modifiedStart + 1)
];
}
WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset, diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset, forwardPoints, reversePoints, originalIndex, originalEnd, midOriginalArr, modifiedIndex, modifiedEnd, midModifiedArr, deltaIsEven, quitEarlyArr) {
let forwardChanges = null;
let reverseChanges = null;
// First, walk backward through the forward diagonals history
let changeHelper = new DiffChangeHelper();
let diagonalMin = diagonalForwardStart;
let diagonalMax = diagonalForwardEnd;
let diagonalRelative = (midOriginalArr[0] - midModifiedArr[0]) - diagonalForwardOffset;
let lastOriginalIndex = -1073741824 /* Constants.MIN_SAFE_SMALL_INTEGER */;
let historyIndex = this.m_forwardHistory.length - 1;
do {
// Get the diagonal index from the relative diagonal number
const diagonal = diagonalRelative + diagonalForwardBase;
// Figure out where we came from
if (diagonal === diagonalMin || (diagonal < diagonalMax && forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1])) {
// Vertical line (the element is an insert)
originalIndex = forwardPoints[diagonal + 1];
modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset;
if (originalIndex < lastOriginalIndex) {
changeHelper.MarkNextChange();
}
lastOriginalIndex = originalIndex;
changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex);
diagonalRelative = (diagonal + 1) - diagonalForwardBase; //Setup for the next iteration
}
else {
// Horizontal line (the element is a deletion)
originalIndex = forwardPoints[diagonal - 1] + 1;
modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset;
if (originalIndex < lastOriginalIndex) {
changeHelper.MarkNextChange();
}
lastOriginalIndex = originalIndex - 1;
changeHelper.AddOriginalElement(originalIndex, modifiedIndex + 1);
diagonalRelative = (diagonal - 1) - diagonalForwardBase; //Setup for the next iteration
}
if (historyIndex >= 0) {
forwardPoints = this.m_forwardHistory[historyIndex];
diagonalForwardBase = forwardPoints[0]; //We stored this in the first spot
diagonalMin = 1;
diagonalMax = forwardPoints.length - 1;
}
} while (--historyIndex >= -1);
// Ironically, we get the forward changes as the reverse of the
// order we added them since we technically added them backwards
forwardChanges = changeHelper.getReverseChanges();
if (quitEarlyArr[0]) {
// TODO: Calculate a partial from the reverse diagonals.
// For now, just assume everything after the midOriginal/midModified point is a diff
let originalStartPoint = midOriginalArr[0] + 1;
let modifiedStartPoint = midModifiedArr[0] + 1;
if (forwardChanges !== null && forwardChanges.length > 0) {
const lastForwardChange = forwardChanges[forwardChanges.length - 1];
originalStartPoint = Math.max(originalStartPoint, lastForwardChange.getOriginalEnd());
modifiedStartPoint = Math.max(modifiedStartPoint, lastForwardChange.getModifiedEnd());
}
reverseChanges = [
new DiffChange(originalStartPoint, originalEnd - originalStartPoint + 1, modifiedStartPoint, modifiedEnd - modifiedStartPoint + 1)
];
}
else {
// Now walk backward through the reverse diagonals history
changeHelper = new DiffChangeHelper();
diagonalMin = diagonalReverseStart;
diagonalMax = diagonalReverseEnd;
diagonalRelative = (midOriginalArr[0] - midModifiedArr[0]) - diagonalReverseOffset;
lastOriginalIndex = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
historyIndex = (deltaIsEven) ? this.m_reverseHistory.length - 1 : this.m_reverseHistory.length - 2;
do {
// Get the diagonal index from the relative diagonal number
const diagonal = diagonalRelative + diagonalReverseBase;
// Figure out where we came from
if (diagonal === diagonalMin || (diagonal < diagonalMax && reversePoints[diagonal - 1] >= reversePoints[diagonal + 1])) {
// Horizontal line (the element is a deletion))
originalIndex = reversePoints[diagonal + 1] - 1;
modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset;
if (originalIndex > lastOriginalIndex) {
changeHelper.MarkNextChange();
}
lastOriginalIndex = originalIndex + 1;
changeHelper.AddOriginalElement(originalIndex + 1, modifiedIndex + 1);
diagonalRelative = (diagonal + 1) - diagonalReverseBase; //Setup for the next iteration
}
else {
// Vertical line (the element is an insertion)
originalIndex = reversePoints[diagonal - 1];
modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset;
if (originalIndex > lastOriginalIndex) {
changeHelper.MarkNextChange();
}
lastOriginalIndex = originalIndex;
changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex + 1);
diagonalRelative = (diagonal - 1) - diagonalReverseBase; //Setup for the next iteration
}
if (historyIndex >= 0) {
reversePoints = this.m_reverseHistory[historyIndex];
diagonalReverseBase = reversePoints[0]; //We stored this in the first spot
diagonalMin = 1;
diagonalMax = reversePoints.length - 1;
}
} while (--historyIndex >= -1);
// There are cases where the reverse history will find diffs that
// are correct, but not intuitive, so we need shift them.
reverseChanges = changeHelper.getChanges();
}
return this.ConcatenateChanges(forwardChanges, reverseChanges);
}
/**
* Given the range to compute the diff on, this method finds the point:
* (midOriginal, midModified)
* that exists in the middle of the LCS of the two sequences and
* is the point at which the LCS problem may be broken down recursively.
* This method will try to keep the LCS trace in memory. If the LCS recursion
* point is calculated and the full trace is available in memory, then this method
* will return the change list.
* @param originalStart The start bound of the original sequence range
* @param originalEnd The end bound of the original sequence range
* @param modifiedStart The start bound of the modified sequence range
* @param modifiedEnd The end bound of the modified sequence range
* @param midOriginal The middle point of the original sequence range
* @param midModified The middle point of the modified sequence range
* @returns The diff changes, if available, otherwise null
*/
ComputeRecursionPoint(originalStart, originalEnd, modifiedStart, modifiedEnd, midOriginalArr, midModifiedArr, quitEarlyArr) {
let originalIndex = 0, modifiedIndex = 0;
let diagonalForwardStart = 0, diagonalForwardEnd = 0;
let diagonalReverseStart = 0, diagonalReverseEnd = 0;
// To traverse the edit graph and produce the proper LCS, our actual
// start position is just outside the given boundary
originalStart--;
modifiedStart--;
// We set these up to make the compiler happy, but they will
// be replaced before we return with the actual recursion point
midOriginalArr[0] = 0;
midModifiedArr[0] = 0;
// Clear out the history
this.m_forwardHistory = [];
this.m_reverseHistory = [];
// Each cell in the two arrays corresponds to a diagonal in the edit graph.
// The integer value in the cell represents the originalIndex of the furthest
// reaching point found so far that ends in that diagonal.
// The modifiedIndex can be computed mathematically from the originalIndex and the diagonal number.
const maxDifferences = (originalEnd - originalStart) + (modifiedEnd - modifiedStart);
const numDiagonals = maxDifferences + 1;
const forwardPoints = new Int32Array(numDiagonals);
const reversePoints = new Int32Array(numDiagonals);
// diagonalForwardBase: Index into forwardPoints of the diagonal which passes through (originalStart, modifiedStart)
// diagonalReverseBase: Index into reversePoints of the diagonal which passes through (originalEnd, modifiedEnd)
const diagonalForwardBase = (modifiedEnd - modifiedStart);
const diagonalReverseBase = (originalEnd - originalStart);
// diagonalForwardOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the
// diagonal number (relative to diagonalForwardBase)
// diagonalReverseOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the
// diagonal number (relative to diagonalReverseBase)
const diagonalForwardOffset = (originalStart - modifiedStart);
const diagonalReverseOffset = (originalEnd - modifiedEnd);
// delta: The difference between the end diagonal and the start diagonal. This is used to relate diagonal numbers
// relative to the start diagonal with diagonal numbers relative to the end diagonal.
// The Even/Oddn-ness of this delta is important for determining when we should check for overlap
const delta = diagonalReverseBase - diagonalForwardBase;
const deltaIsEven = (delta % 2 === 0);
// Here we set up the start and end points as the furthest points found so far
// in both the forward and reverse directions, respectively
forwardPoints[diagonalForwardBase] = originalStart;
reversePoints[diagonalReverseBase] = originalEnd;
// Remember if we quit early, and thus need to do a best-effort result instead of a real result.
quitEarlyArr[0] = false;
// A couple of points:
// --With this method, we iterate on the number of differences between the two sequences.
// The more differences there actually are, the longer this will take.
// --Also, as the number of differences increases, we have to search on diagonals further
// away from the reference diagonal (which is diagonalForwardBase for forward, diagonalReverseBase for reverse).
// --We extend on even diagonals (relative to the reference diagonal) only when numDifferences
// is even and odd diagonals only when numDifferences is odd.
for (let numDifferences = 1; numDifferences <= (maxDifferences / 2) + 1; numDifferences++) {
let furthestOriginalIndex = 0;
let furthestModifiedIndex = 0;
// Run the algorithm in the forward direction
diagonalForwardStart = this.ClipDiagonalBound(diagonalForwardBase - numDifferences, numDifferences, diagonalForwardBase, numDiagonals);
diagonalForwardEnd = this.ClipDiagonalBound(diagonalForwardBase + numDifferences, numDifferences, diagonalForwardBase, numDiagonals);
for (let diagonal = diagonalForwardStart; diagonal <= diagonalForwardEnd; diagonal += 2) {
// STEP 1: We extend the furthest reaching point in the present diagonal
// by looking at the diagonals above and below and picking the one whose point
// is further away from the start point (originalStart, modifiedStart)
if (diagonal === diagonalForwardStart || (diagonal < diagonalForwardEnd && forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1])) {
originalIndex = forwardPoints[diagonal + 1];
}
else {
originalIndex = forwardPoints[diagonal - 1] + 1;
}
modifiedIndex = originalIndex - (diagonal - diagonalForwardBase) - diagonalForwardOffset;
// Save the current originalIndex so we can test for false overlap in step 3
const tempOriginalIndex = originalIndex;
// STEP 2: We can continue to extend the furthest reaching point in the present diagonal
// so long as the elements are equal.
while (originalIndex < originalEnd && modifiedIndex < modifiedEnd && this.ElementsAreEqual(originalIndex + 1, modifiedIndex + 1)) {
originalIndex++;
modifiedIndex++;
}
forwardPoints[diagonal] = originalIndex;
if (originalIndex + modifiedIndex > furthestOriginalIndex + furthestModifiedIndex) {
furthestOriginalIndex = originalIndex;
furthestModifiedIndex = modifiedIndex;
}
// STEP 3: If delta is odd (overlap first happens on forward when delta is odd)
// and diagonal is in the range of reverse diagonals computed for numDifferences-1
// (the previous iteration; we haven't computed reverse diagonals for numDifferences yet)
// then check for overlap.
if (!deltaIsEven && Math.abs(diagonal - diagonalReverseBase) <= (numDifferences - 1)) {
if (originalIndex >= reversePoints[diagonal]) {
midOriginalArr[0] = originalIndex;
midModifiedArr[0] = modifiedIndex;
if (tempOriginalIndex <= reversePoints[diagonal] && 1447 /* LocalConstants.MaxDifferencesHistory */ > 0 && numDifferences <= (1447 /* LocalConstants.MaxDifferencesHistory */ + 1)) {
// BINGO! We overlapped, and we have the full trace in memory!
return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset, diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset, forwardPoints, reversePoints, originalIndex, originalEnd, midOriginalArr, modifiedIndex, modifiedEnd, midModifiedArr, deltaIsEven, quitEarlyArr);
}
else {
// Either false overlap, or we didn't have enough memory for the full trace
// Just return the recursion point
return null;
}
}
}
}
// Check to see if we should be quitting early, before moving on to the next iteration.
const matchLengthOfLongest = ((furthestOriginalIndex - originalStart) + (furthestModifiedIndex - modifiedStart) - numDifferences) / 2;
if (this.ContinueProcessingPredicate !== null && !this.ContinueProcessingPredicate(furthestOriginalIndex, matchLengthOfLongest)) {
// We can't finish, so skip ahead to generating a result from what we have.
quitEarlyArr[0] = true;
// Use the furthest distance we got in the forward direction.
midOriginalArr[0] = furthestOriginalIndex;
midModifiedArr[0] = furthestModifiedIndex;
if (matchLengthOfLongest > 0 && 1447 /* LocalConstants.MaxDifferencesHistory */ > 0 && numDifferences <= (1447 /* LocalConstants.MaxDifferencesHistory */ + 1)) {
// Enough of the history is in memory to walk it backwards
return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset, diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset, forwardPoints, reversePoints, originalIndex, originalEnd, midOriginalArr, modifiedIndex, modifiedEnd, midModifiedArr, deltaIsEven, quitEarlyArr);
}
else {
// We didn't actually remember enough of the history.
//Since we are quitting the diff early, we need to shift back the originalStart and modified start
//back into the boundary limits since we decremented their value above beyond the boundary limit.
originalStart++;
modifiedStart++;
return [
new DiffChange(originalStart, originalEnd - originalStart + 1, modifiedStart, modifiedEnd - modifiedStart + 1)
];
}
}
// Run the algorithm in the reverse direction
diagonalReverseStart = this.ClipDiagonalBound(diagonalReverseBase - numDifferences, numDifferences, diagonalReverseBase, numDiagonals);
diagonalReverseEnd = this.ClipDiagonalBound(diagonalReverseBase + numDifferences, numDifferences, diagonalReverseBase, numDiagonals);
for (let diagonal = diagonalReverseStart; diagonal <= diagonalReverseEnd; diagonal += 2) {
// STEP 1: We extend the furthest reaching point in the present diagonal
// by looking at the diagonals above and below and picking the one whose point
// is further away from the start point (originalEnd, modifiedEnd)
if (diagonal === diagonalReverseStart || (diagonal < diagonalReverseEnd && reversePoints[diagonal - 1] >= reversePoints[diagonal + 1])) {
originalIndex = reversePoints[diagonal + 1] - 1;
}
else {
originalIndex = reversePoints[diagonal - 1];
}
modifiedIndex = originalIndex - (diagonal - diagonalReverseBase) - diagonalReverseOffset;
// Save the current originalIndex so we can test for false overlap
const tempOriginalIndex = originalIndex;
// STEP 2: We can continue to extend the furthest reaching point in the present diagonal
// as long as the elements are equal.
while (originalIndex > originalStart && modifiedIndex > modifiedStart && this.ElementsAreEqual(originalIndex, modifiedIndex)) {
originalIndex--;
modifiedIndex--;
}
reversePoints[diagonal] = originalIndex;
// STEP 4: If delta is even (overlap first happens on reverse when delta is even)
// and diagonal is in the range of forward diagonals computed for numDifferences
// then check for overlap.
if (deltaIsEven && Math.abs(diagonal - diagonalForwardBase) <= numDifferences) {
if (originalIndex <= forwardPoints[diagonal]) {
midOriginalArr[0] = originalIndex;
midModifiedArr[0] = modifiedIndex;
if (tempOriginalIndex >= forwardPoints[diagonal] && 1447 /* LocalConstants.MaxDifferencesHistory */ > 0 && numDifferences <= (1447 /* LocalConstants.MaxDifferencesHistory */ + 1)) {
// BINGO! We overlapped, and we have the full trace in memory!
return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset, diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset, forwardPoints, reversePoints, originalIndex, originalEnd, midOriginalArr, modifiedIndex, modifiedEnd, midModifiedArr, deltaIsEven, quitEarlyArr);
}
else {
// Either false overlap, or we didn't have enough memory for the full trace
// Just return the recursion point
return null;
}
}
}
}
// Save current vectors to history before the next iteration
if (numDifferences <= 1447 /* LocalConstants.MaxDifferencesHistory */) {
// We are allocating space for one extra int, which we fill with
// the index of the diagonal base index
let temp = new Int32Array(diagonalForwardEnd - diagonalForwardStart + 2);
temp[0] = diagonalForwardBase - diagonalForwardStart + 1;
MyArray.Copy2(forwardPoints, diagonalForwardStart, temp, 1, diagonalForwardEnd - diagonalForwardStart + 1);
this.m_forwardHistory.push(temp);
temp = new Int32Array(diagonalReverseEnd - diagonalReverseStart + 2);
temp[0] = diagonalReverseBase - diagonalReverseStart + 1;
MyArray.Copy2(reversePoints, diagonalReverseStart, temp, 1, diagonalReverseEnd - diagonalReverseStart + 1);
this.m_reverseHistory.push(temp);
}
}
// If we got here, then we have the full trace in history. We just have to convert it to a change list
// NOTE: This part is a bit messy
return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset, diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset, forwardPoints, reversePoints, originalIndex, originalEnd, midOriginalArr, modifiedIndex, modifiedEnd, midModifiedArr, deltaIsEven, quitEarlyArr);
}
/**
* Shifts the given changes to provide a more intuitive diff.
* While the first element in a diff matches the first element after the diff,
* we shift the diff down.
*
* @param changes The list of changes to shift
* @returns The shifted changes
*/
PrettifyChanges(changes) {
// Shift all the changes down first
for (let i = 0; i < changes.length; i++) {
const change = changes[i];
const originalStop = (i < changes.length - 1) ? changes[i + 1].originalStart : this._originalElementsOrHash.length;
const modifiedStop = (i < changes.length - 1) ? changes[i + 1].modifiedStart : this._modifiedElementsOrHash.length;
const checkOriginal = change.originalLength > 0;
const checkModified = change.modifiedLength > 0;
while (change.originalStart + change.originalLength < originalStop
&& change.modifiedStart + change.modifiedLength < modifiedStop
&& (!checkOriginal || this.OriginalElementsAreEqual(change.originalStart, change.originalStart + change.originalLength))
&& (!checkModified || this.ModifiedElementsAreEqual(change.modifiedStart, change.modifiedStart + change.modifiedLength))) {
const startStrictEqual = this.ElementsAreStrictEqual(change.originalStart, change.modifiedStart);
const endStrictEqual = this.ElementsAreStrictEqual(change.originalStart + change.originalLength, change.modifiedStart + change.modifiedLength);
if (endStrictEqual && !startStrictEqual) {
// moving the change down would create an equal change, but the elements are not strict equal
break;
}
change.originalStart++;
change.modifiedStart++;
}
const mergedChangeArr = [null];
if (i < changes.length - 1 && this.ChangesOverlap(changes[i], changes[i + 1], mergedChangeArr)) {
changes[i] = mergedChangeArr[0];
changes.splice(i + 1, 1);
i--;
continue;
}
}
// Shift changes back up until we hit empty or whitespace-only lines
for (let i = changes.length - 1; i >= 0; i--) {
const change = changes[i];
let originalStop = 0;
let modifiedStop = 0;
if (i > 0) {
const prevChange = changes[i - 1];
originalStop = prevChange.originalStart + prevChange.originalLength;
modifiedStop = prevChange.modifiedStart + prevChange.modifiedLength;
}
const checkOriginal = change.originalLength > 0;
const checkModified = change.modifiedLength > 0;
let bestDelta = 0;
let bestScore = this._boundaryScore(change.originalStart, change.originalLength, change.modifiedStart, change.modifiedLength);
for (let delta = 1;; delta++) {
const originalStart = change.originalStart - delta;
const modifiedStart = change.modifiedStart - delta;
if (originalStart < originalStop || modifiedStart < modifiedStop) {
break;
}
if (checkOriginal && !this.OriginalElementsAreEqual(originalStart, originalStart + change.originalLength)) {
break;
}
if (checkModified && !this.ModifiedElementsAreEqual(modifiedStart, modifiedStart + change.modifiedLength)) {
break;
}
const touchingPreviousChange = (originalStart === originalStop && modifiedStart === modifiedStop);
const score = ((touchingPreviousChange ? 5 : 0)
+ this._boundaryScore(originalStart, change.originalLength, modifiedStart, change.modifiedLength));
if (score > bestScore) {
bestScore = score;
bestDelta = delta;
}
}
change.originalStart -= bestDelta;
change.modifiedStart -= bestDelta;
const mergedChangeArr = [null];
if (i > 0 && this.ChangesOverlap(changes[i - 1], changes[i], mergedChangeArr)) {
changes[i - 1] = mergedChangeArr[0];
changes.splice(i, 1);
i++;
continue;
}
}
// There could be multiple longest common substrings.
// Give preference to the ones containing longer lines
if (this._hasStrings) {
for (let i = 1, len = changes.length; i < len; i++) {
const aChange = changes[i - 1];
const bChange = changes[i];
const matchedLength = bChange.originalStart - aChange.originalStart - aChange.originalLength;
const aOriginalStart = aChange.originalStart;
const bOriginalEnd = bChange.originalStart + bChange.originalLength;
const abOriginalLength = bOriginalEnd - aOriginalStart;
const aModifiedStart = aChange.modifiedStart;
const bModifiedEnd = bChange.modifiedStart + bChange.modifiedLength;
const abModifiedLength = bModifiedEnd - aModifiedStart;
// Avoid wasting a lot of time with these searches
if (matchedLength < 5 && abOriginalLength < 20 && abModifiedLength < 20) {
const t = this._findBetterContiguousSequence(aOriginalStart, abOriginalLength, aModifiedStart, abModifiedLength, matchedLength);
if (t) {
const [originalMatchStart, modifiedMatchStart] = t;
if (originalMatchStart !== aChange.originalStart + aChange.originalLength || modifiedMatchStart !== aChange.modifiedStart + aChange.modifiedLength) {
// switch to another sequence that has a better score
aChange.originalLength = originalMatchStart - aChange.originalStart;
aChange.modifiedLength = modifiedMatchStart - aChange.modifiedStart;
bChange.originalStart = originalMatchStart + matchedLength;
bChange.modifiedStart = modifiedMatchStart + matchedLength;
bChange.originalLength = bOriginalEnd - bChange.originalStart;
bChange.modifiedLength = bModifiedEnd - bChange.modifiedStart;
}
}
}
}
}
return changes;
}
_findBetterContiguousSequence(originalStart, originalLength, modifiedStart, modifiedLength, desiredLength) {
if (originalLength < desiredLength || modifiedLength < desiredLength) {
return null;
}
const originalMax = originalStart + originalLength - desiredLength + 1;
const modifiedMax = modifiedStart + modifiedLength - desiredLength + 1;
let bestScore = 0;
let bestOriginalStart = 0;
let bestModifiedStart = 0;
for (let i = originalStart; i < originalMax; i++) {
for (let j = modifiedStart; j < modifiedMax; j++) {
const score = this._contiguousSequenceScore(i, j, desiredLength);
if (score > 0 && score > bestScore) {
bestScore = score;
bestOriginalStart = i;
bestModifiedStart = j;
}
}
}
if (bestScore > 0) {
return [bestOriginalStart, bestModifiedStart];
}
return null;
}
_contiguousSequenceScore(originalStart, modifiedStart, length) {
let score = 0;
for (let l = 0; l < length; l++) {
if (!this.ElementsAreEqual(originalStart + l, modifiedStart + l)) {
return 0;
}
score += this._originalStringElements[originalStart + l].length;
}
return score;
}
_OriginalIsBoundary(index) {
if (index <= 0 || index >= this._originalElementsOrHash.length - 1) {
return true;
}
return (this._hasStrings && /^\s*$/.test(this._originalStringElements[index]));
}
_OriginalRegionIsBoundary(originalStart, originalLength) {
if (this._OriginalIsBoundary(originalStart) || this._OriginalIsBoundary(originalStart - 1)) {
return true;
}
if (originalLength > 0) {
const originalEnd = originalStart + originalLength;
if (this._OriginalIsBoundary(originalEnd - 1) || this._OriginalIsBoundary(originalEnd)) {
return true;
}
}
return false;
}
_ModifiedIsBoundary(index) {
if (index <= 0 || index >= this._modifiedElementsOrHash.length - 1) {
return true;
}
return (this._hasStrings && /^\s*$/.test(this._modifiedStringElements[index]));
}
_ModifiedRegionIsBoundary(modifiedStart, modifiedLength) {
if (this._ModifiedIsBoundary(modifiedStart) || this._ModifiedIsBoundary(modifiedStart - 1)) {
return true;
}
if (modifiedLength > 0) {
const modifiedEnd = modifiedStart + modifiedLength;
if (this._ModifiedIsBoundary(modifiedEnd - 1) || this._ModifiedIsBoundary(modifiedEnd)) {
return true;
}
}
return false;
}
_boundaryScore(originalStart, originalLength, modifiedStart, modifiedLength) {
const originalScore = (this._OriginalRegionIsBoundary(originalStart, originalLength) ? 1 : 0);
const modifiedScore = (this._ModifiedRegionIsBoundary(modifiedStart, modifiedLength) ? 1 : 0);
return (originalScore + modifiedScore);
}
/**
* Concatenates the two input DiffChange lists and returns the resulting
* list.
* @param The left changes
* @param The right changes
* @returns The concatenated list
*/
ConcatenateChanges(left, right) {
const mergedChangeArr = [];
if (left.length === 0 || right.length === 0) {
return (right.length > 0) ? right : left;
}
else if (this.ChangesOverlap(left[left.length - 1], right[0], mergedChangeArr)) {
// Since we break the problem down recursively, it is possible that we
// might recurse in the middle of a change thereby splitting it into
// two changes. Here in the combining stage, we detect and fuse those
// changes back together
const result = new Array(left.length + right.length - 1);
MyArray.Copy(left, 0, result, 0, left.length - 1);
result[left.length - 1] = mergedChangeArr[0];
MyArray.Copy(right, 1, result, left.length, right.length - 1);
return result;
}
else {
const result = new Array(left.length + right.length);
MyArray.Copy(left, 0, result, 0, left.length);
MyArray.Copy(right, 0, result, left.length, right.length);
return result;
}
}
/**
* Returns true if the two changes overlap and can be merged into a single
* change
* @param left The left change
* @param right The right change
* @param mergedChange The merged change if the two overlap, null otherwise
* @returns True if the two changes overlap
*/
ChangesOverlap(left, right, mergedChangeArr) {
Debug.Assert(left.originalStart <= right.originalStart, 'Left change is not less than or equal to right change');
Debug.Assert(left.modifiedStart <= right.modifiedStart, 'Left change is not less than or equal to right change');
if (left.originalStart + left.originalLength >= right.originalStart || left.modifiedStart + left.modifiedLength >= right.modifiedStart) {
const originalStart = left.originalStart;
let originalLength = left.originalLength;
const modifiedStart = left.modifiedStart;
let modifiedLength = left.modifiedLength;
if (left.originalStart + left.originalLength >= right.originalStart) {
originalLength = right.originalStart + right.originalLength - left.originalStart;
}
if (left.modifiedStart + left.modifiedLength >= right.modifiedStart) {
modifiedLength = right.modifiedStart + right.modifiedLength - left.modifiedStart;
}
mergedChangeArr[0] = new DiffChange(originalStart, originalLength, modifiedStart, modifiedLength);
return true;
}
else {
mergedChangeArr[0] = null;
return false;
}
}
/**
* Helper method used to clip a diagonal index to the range of valid
* diagonals. This also decides whether or not the diagonal index,
* if it exceeds the boundary, should be clipped to the boundary or clipped
* one inside the boundary depending on the Even/Odd status of the boundary
* and numDifferences.
* @param diagonal The index of the diagonal to clip.
* @param numDifferences The current number of differences being iterated upon.
* @param diagonalBaseIndex The base reference diagonal.
* @param numDiagonals The total number of diagonals.
* @returns The clipped diagonal index.
*/
ClipDiagonalBound(diagonal, numDifferences, diagonalBaseIndex, numDiagonals) {
if (diagonal >= 0 && diagonal < numDiagonals) {
// Nothing to clip, its in range
return diagonal;
}
// diagonalsBelow: The number of diagonals below the reference diagonal
// diagonalsAbove: The number of diagonals above the reference diagonal
const diagonalsBelow = diagonalBaseIndex;
const diagonalsAbove = numDiagonals - diagonalBaseIndex - 1;
const diffEven = (numDifferences % 2 === 0);
if (diagonal < 0) {
const lowerBoundEven = (diagonalsBelow % 2 === 0);
return (diffEven === lowerBoundEven) ? 0 : 1;
}
else {
const upperBoundEven = (diagonalsAbove % 2 === 0);
return (diffEven === upperBoundEven) ? numDiagonals - 1 : numDiagonals - 2;
}
}
}
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* 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 {
/**
* Constructs a new DiffChange with the given sequence information
* and content.
*/
constructor(originalStart, originalLength, modifiedStart, modifiedLength) {
//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.
*/
getOriginalEnd() {
return this.originalStart + this.originalLength;
}
/**
* The end point (exclusive) of the change in the modified sequence.
*/
getModifiedEnd() {
return this.modifiedStart + this.modifiedLength;
}
}
+82
View File
@@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arrays from './arrays.js';
/**
* Compares two items for equality using strict equality.
*/
export const strictEquals = (a, b) => a === b;
/**
* Checks if the items of two arrays are equal.
* By default, strict equality is used to compare elements, but a custom equality comparer can be provided.
*/
export function itemsEquals(itemEquals = strictEquals) {
return (a, b) => arrays.equals(a, b, itemEquals);
}
/**
* Uses `item.equals(other)` to determine equality.
*/
export function itemEquals() {
return (a, b) => a.equals(b);
}
export function equalsIfDefined(equalsOrV1, v2, equals) {
if (equals !== undefined) {
const v1 = equalsOrV1;
if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) {
return v2 === v1;
}
return equals(v1, v2);
}
else {
const equals = equalsOrV1;
return (v1, v2) => {
if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) {
return v2 === v1;
}
return equals(v1, v2);
};
}
}
/**
* Drills into arrays (items ordered) and objects (keys unordered) and uses strict equality on everything else.
*/
export function structuralEquals(a, b) {
if (a === b) {
return true;
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (!structuralEquals(a[i], b[i])) {
return false;
}
}
return true;
}
if (a && typeof a === 'object' && b && typeof b === 'object') {
if (Object.getPrototypeOf(a) === Object.prototype && Object.getPrototypeOf(b) === Object.prototype) {
const aObj = a;
const bObj = b;
const keysA = Object.keys(aObj);
const keysB = Object.keys(bObj);
const keysBSet = new Set(keysB);
if (keysA.length !== keysB.length) {
return false;
}
for (const key of keysA) {
if (!keysBSet.has(key)) {
return false;
}
if (!structuralEquals(aObj[key], bObj[key])) {
return false;
}
}
return true;
}
}
return false;
}
const objIds = new WeakMap();
@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arrays from './arrays.js';
import * as types from './types.js';
import * as nls from '../../nls.js';
function exceptionToErrorMessage(exception, verbose) {
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) {
if (Array.isArray(stack)) {
return stack.join('\n');
}
return stack;
}
function detectSystemErrorMessage(exception) {
// Custom node.js error from us
if (exception.code === 'ERR_UNC_HOST_NOT_ALLOWED') {
return `${exception.message}. Please update the 'security.allowedUNCHosts' setting if you want to allow this host.`;
}
// 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 = null, verbose = false) {
if (!error) {
return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
}
if (Array.isArray(error)) {
const errors = 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.");
}
+150
View File
@@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Avoid circular dependency on EventEmitter by implementing a subset of the interface.
export class ErrorHandler {
constructor() {
this.listeners = [];
this.unexpectedErrorHandler = function (e) {
setTimeout(() => {
if (e.stack) {
if (ErrorNoTelemetry.isErrorNoTelemetry(e)) {
throw new ErrorNoTelemetry(e.message + '\n\n' + e.stack);
}
throw new Error(e.message + '\n\n' + e.stack);
}
throw e;
}, 0);
};
}
emit(e) {
this.listeners.forEach((listener) => {
listener(e);
});
}
onUnexpectedError(e) {
this.unexpectedErrorHandler(e);
this.emit(e);
}
// For external errors, we don't want the listeners to be called
onUnexpectedExternalError(e) {
this.unexpectedErrorHandler(e);
}
}
export const errorHandler = new ErrorHandler();
export function onUnexpectedError(e) {
// ignore errors from cancelled promises
if (!isCancellationError(e)) {
errorHandler.onUnexpectedError(e);
}
return undefined;
}
export function onUnexpectedExternalError(e) {
// ignore errors from cancelled promises
if (!isCancellationError(e)) {
errorHandler.onUnexpectedExternalError(e);
}
return undefined;
}
export function transformErrorForSerialization(error) {
if (error instanceof Error) {
const { name, message } = error;
const stack = error.stacktrace || error.stack;
return {
$isError: true,
name,
message,
stack,
noTelemetry: ErrorNoTelemetry.isErrorNoTelemetry(error)
};
}
// return as is
return error;
}
const canceledName = 'Canceled';
/**
* Checks if the given error is a promise in canceled state
*/
export function isCancellationError(error) {
if (error instanceof CancellationError) {
return true;
}
return error instanceof Error && error.name === canceledName && error.message === canceledName;
}
// !!!IMPORTANT!!!
// Do NOT change this class because it is also used as an API-type.
export class CancellationError extends Error {
constructor() {
super(canceledName);
this.name = this.message;
}
}
/**
* @deprecated use {@link CancellationError `new CancellationError()`} instead
*/
export function canceled() {
const error = new Error(canceledName);
error.name = error.message;
return error;
}
export function illegalArgument(name) {
if (name) {
return new Error(`Illegal argument: ${name}`);
}
else {
return new Error('Illegal argument');
}
}
export function illegalState(name) {
if (name) {
return new Error(`Illegal state: ${name}`);
}
else {
return new Error('Illegal state');
}
}
export class NotSupportedError extends Error {
constructor(message) {
super('NotSupported');
if (message) {
this.message = message;
}
}
}
/**
* Error that when thrown won't be logged in telemetry as an unhandled error.
*/
export class ErrorNoTelemetry extends Error {
constructor(msg) {
super(msg);
this.name = 'CodeExpectedError';
}
static fromError(err) {
if (err instanceof ErrorNoTelemetry) {
return err;
}
const result = new ErrorNoTelemetry();
result.message = err.message;
result.stack = err.stack;
return result;
}
static isErrorNoTelemetry(err) {
return err.name === 'CodeExpectedError';
}
}
/**
* This error indicates a bug.
* Do not throw this for invalid user input.
* Only catch this error to recover gracefully from bugs.
*/
export class BugIndicatingError extends Error {
constructor(message) {
super(message || 'An unexpected bug occurred.');
Object.setPrototypeOf(this, BugIndicatingError.prototype);
// Because we know for sure only buggy code throws this,
// we definitely want to break here and fix the bug.
// eslint-disable-next-line no-debugger
// debugger;
}
}
File diff suppressed because it is too large Load Diff
+144
View File
@@ -0,0 +1,144 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { posix, sep } from './path.js';
import { isWindows } from './platform.js';
import { startsWithIgnoreCase } from './strings.js';
export function isPathSeparator(code) {
return code === 47 /* CharCode.Slash */ || code === 92 /* 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) {
return osPath.replace(/[\\/]/g, posix.sep);
}
/**
* Takes a Windows OS path (using backward or forward slashes) and turns it into a posix path:
* - turns backward slashes into forward slashes
* - makes it absolute if it starts with a drive letter
* 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 toPosixPath(osPath) {
if (osPath.indexOf('/') === -1) {
osPath = toSlashes(osPath);
}
if (/^[a-zA-Z]:(\/|$)/.test(osPath)) { // starts with a drive letter
osPath = '/' + osPath;
}
return osPath;
}
/**
* 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, sep = posix.sep) {
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) === 58 /* 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 '';
}
/**
* @deprecated please use `IUriIdentityService.extUri.isEqualOrParent` instead. If
* you are in a context without services, consider to pass down the `extUri` from the
* outside, or use `extUriBiasedIgnorePathCase` if you know what you are doing.
*/
export function isEqualOrParent(base, parentCandidate, ignoreCase, separator = sep) {
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) {
return char0 >= 65 /* CharCode.A */ && char0 <= 90 /* CharCode.Z */ || char0 >= 97 /* CharCode.a */ && char0 <= 122 /* CharCode.z */;
}
export function hasDriveLetter(path, isWindowsOS = isWindows) {
if (isWindowsOS) {
return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === 58 /* CharCode.Colon */;
}
return false;
}
+769
View File
@@ -0,0 +1,769 @@
/*---------------------------------------------------------------------------------------------
* 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 './map.js';
import { getKoreanAltChars } from './naturalLanguage/korean.js';
import * as strings from './strings.js';
// 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) {
return function (word, wordToMatchAgainst) {
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 = _matchesPrefix.bind(undefined, false);
export const matchesPrefix = _matchesPrefix.bind(undefined, true);
function _matchesPrefix(ignoreCase, word, wordToMatchAgainst) {
if (!wordToMatchAgainst || wordToMatchAgainst.length < word.length) {
return null;
}
let matches;
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, wordToMatchAgainst) {
const index = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase());
if (index === -1) {
return null;
}
return [{ start: index, end: index + word.length }];
}
// Substring
export function matchesSubString(word, wordToMatchAgainst) {
return _matchesSubString(word.toLowerCase(), wordToMatchAgainst.toLowerCase(), 0, 0);
}
function _matchesSubString(word, wordToMatchAgainst, i, j) {
if (i === word.length) {
return [];
}
else if (j === wordToMatchAgainst.length) {
return null;
}
else {
if (word[i] === wordToMatchAgainst[j]) {
let result = 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) {
return 97 /* CharCode.a */ <= code && code <= 122 /* CharCode.z */;
}
export function isUpper(code) {
return 65 /* CharCode.A */ <= code && code <= 90 /* CharCode.Z */;
}
function isNumber(code) {
return 48 /* CharCode.Digit0 */ <= code && code <= 57 /* CharCode.Digit9 */;
}
function isWhitespace(code) {
return (code === 32 /* CharCode.Space */
|| code === 9 /* CharCode.Tab */
|| code === 10 /* CharCode.LineFeed */
|| code === 13 /* CharCode.CarriageReturn */);
}
const wordSeparators = new Set();
// These are chosen as natural word separators based on writen text.
// It is a subset of the word separators used by the monaco editor.
'()[]{}<>`\'"-/;:,.?!'
.split('')
.forEach(s => wordSeparators.add(s.charCodeAt(0)));
function isWordSeparator(code) {
return isWhitespace(code) || wordSeparators.has(code);
}
function charactersMatch(codeA, codeB) {
return (codeA === codeB) || (isWordSeparator(codeA) && isWordSeparator(codeB));
}
const alternateCharsCache = new Map();
/**
* Gets alternative codes to the character code passed in. This comes in the
* form of an array of character codes, all of which must match _in order_ to
* successfully match.
*
* @param code The character code to check.
*/
function getAlternateCodes(code) {
if (alternateCharsCache.has(code)) {
return alternateCharsCache.get(code);
}
// NOTE: This function is written in such a way that it can be extended in
// the future, but right now the return type takes into account it's only
// supported by a single "alt codes provider".
// `ArrayLike<ArrayLike<number>>` is a more appropriate type if changed.
let result;
const codes = getKoreanAltChars(code);
if (codes) {
result = codes;
}
alternateCharsCache.set(code, result);
return result;
}
function isAlphanumeric(code) {
return isLower(code) || isUpper(code) || isNumber(code);
}
function join(head, tail) {
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, start) {
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, camelCaseWord, i, j) {
if (i === word.length) {
return [];
}
else if (j === camelCaseWord.length) {
return null;
}
else if (word[i] !== camelCaseWord[j].toLowerCase()) {
return null;
}
else {
let result = 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);
}
}
// Heuristic to avoid computing camel case matcher for words that don't
// look like camelCaseWords.
function analyzeCamelCaseWord(word) {
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) {
const { upperPercent, lowerPercent } = analysis;
return lowerPercent === 0 && upperPercent > 0.6;
}
function isCamelCaseWord(analysis) {
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) {
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, camelCaseWord) {
if (!camelCaseWord) {
return null;
}
camelCaseWord = camelCaseWord.trim();
if (camelCaseWord.length === 0) {
return null;
}
if (!isCamelCasePattern(word)) {
return null;
}
// TODO: Consider removing this check
if (camelCaseWord.length > 60) {
camelCaseWord = camelCaseWord.substring(0, 60);
}
const analysis = analyzeCamelCaseWord(camelCaseWord);
if (!isCamelCaseWord(analysis)) {
if (!isUpperCaseWord(analysis)) {
return null;
}
camelCaseWord = camelCaseWord.toLowerCase();
}
let result = 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, target, contiguous = false) {
if (!target || target.length === 0) {
return null;
}
let result = null;
let targetIndex = 0;
word = word.toLowerCase();
target = target.toLowerCase();
while (targetIndex < target.length) {
result = _matchesWords(word, target, 0, targetIndex, contiguous);
if (result !== null) {
break;
}
targetIndex = nextWord(target, targetIndex + 1);
}
return result;
}
function _matchesWords(word, target, wordIndex, targetIndex, contiguous) {
let targetIndexOffset = 0;
if (wordIndex === word.length) {
return [];
}
else if (targetIndex === target.length) {
return null;
}
else if (!charactersMatch(word.charCodeAt(wordIndex), target.charCodeAt(targetIndex))) {
// Verify alternate characters before exiting
const altChars = getAlternateCodes(word.charCodeAt(wordIndex));
if (!altChars) {
return null;
}
for (let k = 0; k < altChars.length; k++) {
if (!charactersMatch(altChars[k], target.charCodeAt(targetIndex + k))) {
return null;
}
}
targetIndexOffset += altChars.length - 1;
}
let result = null;
let nextWordIndex = targetIndex + targetIndexOffset + 1;
result = _matchesWords(word, target, wordIndex + 1, nextWordIndex, contiguous);
if (!contiguous) {
while (!result && (nextWordIndex = nextWord(target, nextWordIndex)) < target.length) {
result = _matchesWords(word, target, wordIndex + 1, nextWordIndex, contiguous);
nextWordIndex++;
}
}
if (!result) {
return null;
}
// If the characters don't exactly match, then they must be word separators (see charactersMatch(...)).
// We don't want to include this in the matches but we don't want to throw the target out all together so we return `result`.
if (word.charCodeAt(wordIndex) !== target.charCodeAt(targetIndex)) {
// Verify alternate characters before exiting
const altChars = getAlternateCodes(word.charCodeAt(wordIndex));
if (!altChars) {
return result;
}
for (let k = 0; k < altChars.length; k++) {
if (altChars[k] !== target.charCodeAt(targetIndex + k)) {
return result;
}
}
}
return join({ start: targetIndex, end: targetIndex + targetIndexOffset + 1 }, result);
}
function nextWord(word, start) {
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(10000); // bounded to 10000 elements
export function matchesFuzzy(word, wordToMatchAgainst, enableSeparateSubstringMatching = false) {
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 against word in a fuzzy way. As in IntelliSense and faster and more
* powerful than `matchesFuzzy`
*/
export function matchesFuzzy2(pattern, word) {
const score = fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true });
return score ? createMatches(score) : null;
}
export function anyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos) {
const max = Math.min(13, pattern.length);
for (; patternPos < max; patternPos++) {
const result = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, { firstMatchCanBeWeak: true, boostFullMatch: true });
if (result) {
return result;
}
}
return [0, wordPos];
}
//#region --- fuzzyScore ---
export function createMatches(score) {
if (typeof score === 'undefined') {
return [];
}
const res = [];
const wordPos = score[1];
for (let i = score.length - 1; i > 1; i--) {
const pos = score[i] + wordPos;
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 = [];
const row = [];
for (let i = 0; i <= _maxLen; i++) {
row[i] = 0;
}
for (let i = 0; i <= _maxLen; i++) {
table.push(row.slice(0));
}
return table;
}
function initArr(maxLen) {
const row = [];
for (let i = 0; i <= maxLen; i++) {
row[i] = 0;
}
return row;
}
const _minWordMatchPos = initArr(2 * _maxLen); // min word position for a certain pattern position
const _maxWordMatchPos = initArr(2 * _maxLen); // max word position for a certain pattern position
const _diag = initTable(); // the length of a contiguous diagonal match
const _table = initTable();
const _arrows = initTable();
const _debug = false;
function printTable(table, pattern, patternLen, word, wordLen) {
function pad(s, n, 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, patternStart, word, wordStart) {
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(_diag, pattern, pattern.length, word, word.length));
}
function isSeparatorAtPos(value, index) {
if (index < 0 || index >= value.length) {
return false;
}
const code = value.codePointAt(index);
switch (code) {
case 95 /* CharCode.Underline */:
case 45 /* CharCode.Dash */:
case 46 /* CharCode.Period */:
case 32 /* CharCode.Space */:
case 47 /* CharCode.Slash */:
case 92 /* CharCode.Backslash */:
case 39 /* CharCode.SingleQuote */:
case 34 /* CharCode.DoubleQuote */:
case 58 /* CharCode.Colon */:
case 36 /* CharCode.DollarSign */:
case 60 /* CharCode.LessThan */:
case 62 /* CharCode.GreaterThan */:
case 40 /* CharCode.OpenParen */:
case 41 /* CharCode.CloseParen */:
case 91 /* CharCode.OpenSquareBracket */:
case 93 /* CharCode.CloseSquareBracket */:
case 123 /* CharCode.OpenCurlyBrace */:
case 125 /* CharCode.CloseCurlyBrace */:
return true;
case undefined:
return false;
default:
if (strings.isEmojiImprecise(code)) {
return true;
}
return false;
}
}
function isWhitespaceAtPos(value, index) {
if (index < 0 || index >= value.length) {
return false;
}
const code = value.charCodeAt(index);
switch (code) {
case 32 /* CharCode.Space */:
case 9 /* CharCode.Tab */:
return true;
default:
return false;
}
}
function isUpperCaseAtPos(pos, word, wordLow) {
return word[pos] !== wordLow[pos];
}
export function isPatternInWord(patternLow, patternPos, patternLen, wordLow, wordPos, wordLen, fillMinWordPosArr = false) {
while (patternPos < patternLen && wordPos < wordLen) {
if (patternLow[patternPos] === wordLow[wordPos]) {
if (fillMinWordPosArr) {
// Remember the min word position for each pattern position
_minWordMatchPos[patternPos] = wordPos;
}
patternPos += 1;
}
wordPos += 1;
}
return patternPos === patternLen; // pattern must be exhausted
}
export var FuzzyScore;
(function (FuzzyScore) {
/**
* No matches and value `-100`
*/
FuzzyScore.Default = ([-100, 0]);
function isDefault(score) {
return !score || (score.length === 2 && score[0] === -100 && score[1] === 0);
}
FuzzyScore.isDefault = isDefault;
})(FuzzyScore || (FuzzyScore = {}));
export class FuzzyScoreOptions {
static { this.default = { boostFullMatch: true, firstMatchCanBeWeak: false }; }
constructor(firstMatchCanBeWeak, boostFullMatch) {
this.firstMatchCanBeWeak = firstMatchCanBeWeak;
this.boostFullMatch = boostFullMatch;
}
}
export function fuzzyScore(pattern, patternLow, patternStart, word, wordLow, wordStart, options = FuzzyScoreOptions.default) {
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, true)) {
return undefined;
}
// Find the max matching word position for each pattern position
// NOTE: the min matching word position was filled in above, in the `isPatternInWord` call
_fillInMaxWordMatchPos(patternLen, wordLen, patternStart, wordStart, patternLow, wordLow);
let row = 1;
let column = 1;
let patternPos = patternStart;
let wordPos = wordStart;
const hasStrongFirstMatch = [false];
// There will be a match, fill in tables
for (row = 1, patternPos = patternStart; patternPos < patternLen; row++, patternPos++) {
// Reduce search space to possible matching word positions and to possible access from next row
const minWordMatchPos = _minWordMatchPos[patternPos];
const maxWordMatchPos = _maxWordMatchPos[patternPos];
const nextMaxWordMatchPos = (patternPos + 1 < patternLen ? _maxWordMatchPos[patternPos + 1] : wordLen);
for (column = minWordMatchPos - wordStart + 1, wordPos = minWordMatchPos; wordPos < nextMaxWordMatchPos; column++, wordPos++) {
let score = Number.MIN_SAFE_INTEGER;
let canComeDiag = false;
if (wordPos <= maxWordMatchPos) {
score = _doScore(pattern, patternLow, patternPos, patternStart, word, wordLow, wordPos, wordLen, wordStart, _diag[row - 1][column - 1] === 0, hasStrongFirstMatch);
}
let diagScore = 0;
if (score !== Number.MAX_SAFE_INTEGER) {
canComeDiag = true;
diagScore = score + _table[row - 1][column - 1];
}
const canComeLeft = wordPos > minWordMatchPos;
const leftScore = canComeLeft ? _table[row][column - 1] + (_diag[row][column - 1] > 0 ? -5 : 0) : 0; // penalty for a gap start
const canComeLeftLeft = wordPos > minWordMatchPos + 1 && _diag[row][column - 1] > 0;
const leftLeftScore = canComeLeftLeft ? _table[row][column - 2] + (_diag[row][column - 2] > 0 ? -5 : 0) : 0; // penalty for a gap start
if (canComeLeftLeft && (!canComeLeft || leftLeftScore >= leftScore) && (!canComeDiag || leftLeftScore >= diagScore)) {
// always prefer choosing left left to jump over a diagonal because that means a match is earlier in the word
_table[row][column] = leftLeftScore;
_arrows[row][column] = 3 /* Arrow.LeftLeft */;
_diag[row][column] = 0;
}
else if (canComeLeft && (!canComeDiag || leftScore >= diagScore)) {
// always prefer choosing left since that means a match is earlier in the word
_table[row][column] = leftScore;
_arrows[row][column] = 2 /* Arrow.Left */;
_diag[row][column] = 0;
}
else if (canComeDiag) {
_table[row][column] = diagScore;
_arrows[row][column] = 1 /* Arrow.Diag */;
_diag[row][column] = _diag[row - 1][column - 1] + 1;
}
else {
throw new Error(`not possible`);
}
}
}
if (_debug) {
printTables(pattern, patternStart, word, wordStart);
}
if (!hasStrongFirstMatch[0] && !options.firstMatchCanBeWeak) {
return undefined;
}
row--;
column--;
const result = [_table[row][column], wordStart];
let backwardsDiagLength = 0;
let maxMatchColumn = 0;
while (row >= 1) {
// Find the column where we go diagonally up
let diagColumn = column;
do {
const arrow = _arrows[row][diagColumn];
if (arrow === 3 /* Arrow.LeftLeft */) {
diagColumn = diagColumn - 2;
}
else if (arrow === 2 /* Arrow.Left */) {
diagColumn = diagColumn - 1;
}
else {
// found the diagonal
break;
}
} while (diagColumn >= 1);
// Overturn the "forwards" decision if keeping the "backwards" diagonal would give a better match
if (backwardsDiagLength > 1 // only if we would have a contiguous match of 3 characters
&& patternLow[patternStart + row - 1] === wordLow[wordStart + column - 1] // only if we can do a contiguous match diagonally
&& !isUpperCaseAtPos(diagColumn + wordStart - 1, word, wordLow) // only if the forwards chose diagonal is not an uppercase
&& backwardsDiagLength + 1 > _diag[row][diagColumn] // only if our contiguous match would be longer than the "forwards" contiguous match
) {
diagColumn = column;
}
if (diagColumn === column) {
// this is a contiguous match
backwardsDiagLength++;
}
else {
backwardsDiagLength = 1;
}
if (!maxMatchColumn) {
// remember the last matched column
maxMatchColumn = diagColumn;
}
row--;
column = diagColumn - 1;
result.push(column);
}
if (wordLen - wordStart === patternLen && options.boostFullMatch) {
// the word matches the pattern with all characters!
// giving the score a total match boost (to come up ahead other words)
result[0] += 2;
}
// Add 1 penalty for each skipped character in the word
const skippedCharsCount = maxMatchColumn - patternLen;
result[0] -= skippedCharsCount;
return result;
}
function _fillInMaxWordMatchPos(patternLen, wordLen, patternStart, wordStart, patternLow, wordLow) {
let patternPos = patternLen - 1;
let wordPos = wordLen - 1;
while (patternPos >= patternStart && wordPos >= wordStart) {
if (patternLow[patternPos] === wordLow[wordPos]) {
_maxWordMatchPos[patternPos] = wordPos;
patternPos--;
}
wordPos--;
}
}
function _doScore(pattern, patternLow, patternPos, patternStart, word, wordLow, wordPos, wordLen, wordStart, newMatchStart, outFirstMatchStrong) {
if (patternLow[patternPos] !== wordLow[wordPos]) {
return Number.MIN_SAFE_INTEGER;
}
let score = 1;
let isGapLocation = false;
if (wordPos === (patternPos - patternStart)) {
// common prefix: `foobar <-> foobaz`
// ^^^^^
score = pattern[patternPos] === word[wordPos] ? 7 : 5;
}
else if (isUpperCaseAtPos(wordPos, word, wordLow) && (wordPos === 0 || !isUpperCaseAtPos(wordPos - 1, word, wordLow))) {
// hitting upper-case: `foo <-> forOthers`
// ^^ ^
score = pattern[patternPos] === word[wordPos] ? 7 : 5;
isGapLocation = true;
}
else if (isSeparatorAtPos(wordLow, wordPos) && (wordPos === 0 || !isSeparatorAtPos(wordLow, wordPos - 1))) {
// hitting a separator: `. <-> foo.bar`
// ^
score = 5;
}
else if (isSeparatorAtPos(wordLow, wordPos - 1) || isWhitespaceAtPos(wordLow, wordPos - 1)) {
// post separator: `foo <-> bar_foo`
// ^^^
score = 5;
isGapLocation = true;
}
if (score > 1 && patternPos === patternStart) {
outFirstMatchStrong[0] = true;
}
if (!isGapLocation) {
isGapLocation = isUpperCaseAtPos(wordPos, word, wordLow) || isSeparatorAtPos(wordLow, wordPos - 1) || isWhitespaceAtPos(wordLow, wordPos - 1);
}
//
if (patternPos === patternStart) { // first character in pattern
if (wordPos > wordStart) {
// the first pattern character would match a word character that is not at the word start
// so introduce a penalty to account for the gap preceding this match
score -= isGapLocation ? 3 : 5;
}
}
else {
if (newMatchStart) {
// this would be the beginning of a new match (i.e. there would be a gap before this location)
score += isGapLocation ? 2 : 0;
}
else {
// this is part of a contiguous match, so give it a slight bonus, but do so only if it would not be a preferred gap location
score += isGapLocation ? 0 : 1;
}
}
if (wordPos + 1 === wordLen) {
// we always penalize gaps, but this gives unfair advantages to a match that would match the last character in the word
// so pretend there is a gap after the last character in the word to normalize things
score -= isGapLocation ? 3 : 5;
}
return score;
}
//#endregion
//#region --- graceful ---
export function fuzzyScoreGracefulAggressive(pattern, lowPattern, patternPos, word, lowWord, wordPos, options) {
return fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, true, options);
}
function fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, aggressive, options) {
let top = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, options);
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, options);
if (candidate) {
candidate[0] -= 3; // permutation penalty
if (!top || candidate[0] > top[0]) {
top = candidate;
}
}
}
}
}
return top;
}
function nextTypoPermutation(pattern, patternPos) {
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
@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Given a function, returns a function that is only calling that function once.
*/
export function createSingleCallFunction(fn, fnDidRunCallback) {
const _this = this;
let didCall = false;
let result;
return function () {
if (didCall) {
return result;
}
didCall = true;
if (fnDidRunCallback) {
try {
result = fn.apply(_this, arguments);
}
finally {
fnDidRunCallback();
}
}
else {
result = fn.apply(_this, arguments);
}
return result;
};
}
@@ -0,0 +1,138 @@
import { createMatches as createFuzzyMatches, fuzzyScore } from './filters.js';
import { sep } from './path.js';
import { isWindows } from './platform.js';
import { stripWildcards } from './strings.js';
const NO_SCORE2 = [undefined, []];
export function scoreFuzzy2(target, query, patternStart = 0, wordStart = 0) {
// Score: multiple inputs
const preparedQuery = query;
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, query, patternStart, wordStart) {
let totalScore = 0;
const totalMatches = [];
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, query, patternStart, wordStart) {
const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), wordStart, { firstMatchCanBeWeak: true, boostFullMatch: true });
if (!score) {
return NO_SCORE2;
}
return [score[0], createFuzzyMatches(score)];
}
const NO_ITEM_SCORE = Object.freeze({ score: 0 });
function normalizeMatches(matches) {
// 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 = [];
let currentMatch = 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, matchB) {
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;
}
/*
* If a query is wrapped in quotes, the user does not want to
* use fuzzy search for this query.
*/
function queryExpectsExactMatch(query) {
return query.startsWith('"') && query.endsWith('"');
}
/**
* 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) {
if (typeof original !== 'string') {
original = '';
}
const originalLowercase = original.toLowerCase();
const { pathNormalized, normalized, normalizedLowercase } = normalizeQuery(original);
const containsPathSeparator = pathNormalized.indexOf(sep) >= 0;
const expectExactMatch = queryExpectsExactMatch(original);
let values = undefined;
const originalSplit = original.split(MULTIPLE_QUERY_VALUES_SEPARATOR);
if (originalSplit.length > 1) {
for (const originalPiece of originalSplit) {
const expectExactMatchPiece = queryExpectsExactMatch(originalPiece);
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,
expectContiguousMatch: expectExactMatchPiece
});
}
}
}
return { original, originalLowercase, pathNormalized, normalized, normalizedLowercase, values, containsPathSeparator, expectContiguousMatch: expectExactMatch };
}
function normalizeQuery(original) {
let pathNormalized;
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
}
// we remove quotes here because quotes are used for exact match search
const normalized = stripWildcards(pathNormalized).replace(/\s|"/g, '');
return {
pathNormalized,
normalized,
normalizedLowercase: normalized.toLowerCase()
};
}
export function pieceToQuery(arg1) {
if (Array.isArray(arg1)) {
return prepareQuery(arg1.map(piece => piece.original).join(MULTIPLE_QUERY_VALUES_SEPARATOR));
}
return prepareQuery(arg1.original);
}
//#endregion
+568
View File
@@ -0,0 +1,568 @@
import { isThenable } from './async.js';
import { isEqualOrParent } from './extpath.js';
import { LRUCache } from './map.js';
import { basename, extname, posix, sep } from './path.js';
import { isLinux } from './platform.js';
import { escapeRegExpCharacters, ltrim } from './strings.js';
export const GLOBSTAR = '**';
export 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, isLastPattern) {
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) 0-many times except when it's the last pattern
// in which case also matches (Path Sep followed by Path Val)
// 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}${isLastPattern ? `|${PATH_REGEX}${NO_PATH_REGEX}+` : ''})*?`;
}
}
export function splitGlobAware(pattern, splitChar) {
if (!pattern) {
return [];
}
const segments = [];
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) {
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(segment => segment === GLOBSTAR)) {
regEx = '.*';
}
// Build regex over segments
else {
let previousSegmentWasGlobStar = false;
segments.forEach((segment, index) => {
// Treat globstar specially
if (segment === GLOBSTAR) {
// if we have more than one globstar after another, just ignore it
if (previousSegmentWasGlobStar) {
return;
}
regEx += starsToRegExp(2, index === segments.length - 1);
}
// Anything else, not globstar
else {
// 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;
// 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 = 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(choice => parseRegExp(choice)).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 += 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.
if (index < segments.length - 1 && // more segments to come after this
(segments[index + 1] !== GLOBSTAR || // next segment is not **, or...
index + 2 < segments.length // ...next segment is ** but there is more segments after that
)) {
regEx += PATH_REGEX;
}
}
// update globstar state
previousSegmentWasGlobStar = (segment === GLOBSTAR);
});
}
return regEx;
}
// regexes to check for trivial 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
const CACHE = new LRUCache(10000); // bounded to 10000 elements
const FALSE = function () {
return false;
};
const NULL = function () {
return null;
};
function parsePattern(arg1, options) {
if (!arg1) {
return NULL;
}
// Handle relative patterns
let pattern;
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 Trivials
let match;
if (T1.test(pattern)) {
parsedPattern = trivia1(pattern.substr(4), pattern); // common pattern: **/*.txt just need endsWith check
}
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, arg2) {
if (typeof arg2 === 'string') {
return parsedPattern;
}
const wrappedPattern = function (path, basename) {
if (!isEqualOrParent(path, arg2.base, !isLinux)) {
// skip glob matching if `base` is not a parent of `path`
return null;
}
// Given we have checked `base` being a parent of `path`,
// we can now remove the `base` portion of the `path`
// and only match on the remaining path components
// For that we try to extract the portion of the `path`
// that comes after the `base` portion. We have to account
// for the fact that `base` might end in a path separator
// (https://github.com/microsoft/vscode/issues/162498)
return parsedPattern(ltrim(path.substr(arg2.base.length), sep), basename);
};
// Make sure to preserve associated metadata
wrappedPattern.allBasenames = parsedPattern.allBasenames;
wrappedPattern.allPaths = parsedPattern.allPaths;
wrappedPattern.basenames = parsedPattern.basenames;
wrappedPattern.patterns = parsedPattern.patterns;
return wrappedPattern;
}
function trimForExclusions(pattern, options) {
return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later
}
// common pattern: **/*.txt just need endsWith check
function trivia1(base, pattern) {
return function (path, basename) {
return typeof path === 'string' && path.endsWith(base) ? pattern : null;
};
}
// common pattern: **/some.txt just need basename check
function trivia2(base, pattern) {
const slashBase = `/${base}`;
const backslashBase = `\\${base}`;
const parsedPattern = function (path, basename) {
if (typeof path !== 'string') {
return null;
}
if (basename) {
return basename === base ? pattern : null;
}
return path === base || path.endsWith(slashBase) || path.endsWith(backslashBase) ? pattern : null;
};
const basenames = [base];
parsedPattern.basenames = basenames;
parsedPattern.patterns = [pattern];
parsedPattern.allBasenames = basenames;
return parsedPattern;
}
// repetition of common patterns (see above) {**/*.txt,**/*.png}
function trivia3(pattern, options) {
const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1)
.split(',')
.map(pattern => parsePattern(pattern, options))
.filter(pattern => pattern !== NULL), pattern);
const patternsLength = parsedPatterns.length;
if (!patternsLength) {
return NULL;
}
if (patternsLength === 1) {
return parsedPatterns[0];
}
const parsedPattern = function (path, basename) {
for (let i = 0, n = parsedPatterns.length; i < n; i++) {
if (parsedPatterns[i](path, basename)) {
return pattern;
}
}
return null;
};
const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
if (withBasenames) {
parsedPattern.allBasenames = withBasenames.allBasenames;
}
const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, []);
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(targetPath, pattern, matchPathEnds) {
const usingPosixSep = sep === posix.sep;
const nativePath = usingPosixSep ? targetPath : targetPath.replace(ALL_FORWARD_SLASHES, sep);
const nativePathEnd = sep + nativePath;
const targetPathEnd = posix.sep + targetPath;
let parsedPattern;
if (matchPathEnds) {
parsedPattern = function (path, basename) {
return typeof path === 'string' && ((path === nativePath || path.endsWith(nativePathEnd)) || !usingPosixSep && (path === targetPath || path.endsWith(targetPathEnd))) ? pattern : null;
};
}
else {
parsedPattern = function (path, basename) {
return typeof path === 'string' && (path === nativePath || (!usingPosixSep && path === targetPath)) ? pattern : null;
};
}
parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + targetPath];
return parsedPattern;
}
function toRegExp(pattern) {
try {
const regExp = new RegExp(`^${parseRegExp(pattern)}$`);
return function (path) {
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;
}
}
export function match(arg1, path, hasSibling) {
if (!arg1 || typeof path !== 'string') {
return false;
}
return parse(arg1)(path, undefined, hasSibling);
}
export function parse(arg1, options = {}) {
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 = function (path, basename) {
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(arg1, options);
}
export function isRelativePattern(obj) {
const rp = obj;
if (!rp) {
return false;
}
return typeof rp.base === 'string' && typeof rp.pattern === 'string';
}
function parsedExpression(expression, options) {
const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression)
.map(pattern => parseExpressionPattern(pattern, expression[pattern], options))
.filter(pattern => pattern !== NULL));
const patternsLength = parsedPatterns.length;
if (!patternsLength) {
return NULL;
}
if (!parsedPatterns.some(parsedPattern => !!parsedPattern.requiresSiblings)) {
if (patternsLength === 1) {
return parsedPatterns[0];
}
const resultExpression = function (path, basename) {
let resultPromises = undefined;
for (let i = 0, n = parsedPatterns.length; i < n; i++) {
const result = parsedPatterns[i](path, basename);
if (typeof result === 'string') {
return result; // immediately return as soon as the first expression matches
}
// If the result is a promise, we have to keep it for
// later processing and await the result properly.
if (isThenable(result)) {
if (!resultPromises) {
resultPromises = [];
}
resultPromises.push(result);
}
}
// With result promises, we have to loop over each and
// await the result before we can return any result.
if (resultPromises) {
return (async () => {
for (const resultPromise of resultPromises) {
const result = await resultPromise;
if (typeof result === 'string') {
return result;
}
}
return null;
})();
}
return null;
};
const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
if (withBasenames) {
resultExpression.allBasenames = withBasenames.allBasenames;
}
const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, []);
if (allPaths.length) {
resultExpression.allPaths = allPaths;
}
return resultExpression;
}
const resultExpression = function (path, base, hasSibling) {
let name = undefined;
let resultPromises = undefined;
for (let i = 0, n = parsedPatterns.length; i < n; i++) {
// Pattern matches path
const parsedPattern = parsedPatterns[i];
if (parsedPattern.requiresSiblings && hasSibling) {
if (!base) {
base = basename(path);
}
if (!name) {
name = base.substr(0, base.length - extname(path).length);
}
}
const result = parsedPattern(path, base, name, hasSibling);
if (typeof result === 'string') {
return result; // immediately return as soon as the first expression matches
}
// If the result is a promise, we have to keep it for
// later processing and await the result properly.
if (isThenable(result)) {
if (!resultPromises) {
resultPromises = [];
}
resultPromises.push(result);
}
}
// With result promises, we have to loop over each and
// await the result before we can return any result.
if (resultPromises) {
return (async () => {
for (const resultPromise of resultPromises) {
const result = await resultPromise;
if (typeof result === 'string') {
return result;
}
}
return null;
})();
}
return null;
};
const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
if (withBasenames) {
resultExpression.allBasenames = withBasenames.allBasenames;
}
const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, []);
if (allPaths.length) {
resultExpression.allPaths = allPaths;
}
return resultExpression;
}
function parseExpressionPattern(pattern, value, options) {
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 = value.when;
if (typeof when === 'string') {
const result = (path, basename, name, hasSibling) => {
if (!hasSibling || !parsedPattern(path, basename)) {
return null;
}
const clausePattern = when.replace('$(basename)', () => name);
const matched = hasSibling(clausePattern);
return isThenable(matched) ?
matched.then(match => match ? pattern : null) :
matched ? pattern : null;
};
result.requiresSiblings = true;
return result;
}
}
// Expression is anything
return parsedPattern;
}
function aggregateBasenameMatches(parsedPatterns, result) {
const basenamePatterns = parsedPatterns.filter(parsedPattern => !!parsedPattern.basenames);
if (basenamePatterns.length < 2) {
return parsedPatterns;
}
const basenames = basenamePatterns.reduce((all, current) => {
const basenames = current.basenames;
return basenames ? all.concat(basenames) : all;
}, []);
let patterns;
if (result) {
patterns = [];
for (let i = 0, n = basenames.length; i < n; i++) {
patterns.push(result);
}
}
else {
patterns = basenamePatterns.reduce((all, current) => {
const patterns = current.patterns;
return patterns ? all.concat(patterns) : all;
}, []);
}
const aggregate = function (path, basename) {
if (typeof path !== 'string') {
return null;
}
if (!basename) {
let i;
for (i = path.length; i > 0; i--) {
const ch = path.charCodeAt(i - 1);
if (ch === 47 /* CharCode.Slash */ || ch === 92 /* 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 => !parsedPattern.basenames);
aggregatedPatterns.push(aggregate);
return aggregatedPatterns;
}
+258
View File
@@ -0,0 +1,258 @@
/*---------------------------------------------------------------------------------------------
* 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.js';
/**
* Return a hash value for an object.
*/
export function hash(obj) {
return doHash(obj, 0);
}
export function doHash(obj, hashVal) {
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);
}
}
export function numberHash(val, initialHashVal) {
return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32
}
function booleanHash(b, initialHashVal) {
return numberHash(b ? 433 : 863, initialHashVal);
}
export function stringHash(s, hashVal) {
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, initialHashVal) {
initialHashVal = numberHash(104579, initialHashVal);
return arr.reduce((hashVal, item) => doHash(item, hashVal), initialHashVal);
}
function objectHash(obj, initialHashVal) {
initialHashVal = numberHash(181387, initialHashVal);
return Object.keys(obj).sort().reduce((hashVal, key) => {
hashVal = stringHash(key, hashVal);
return doHash(obj[key], hashVal);
}, initialHashVal);
}
function leftRotate(value, bits, totalBits = 32) {
// 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, index = 0, count = dest.byteLength, value = 0) {
for (let i = 0; i < count; i++) {
dest[index + i] = value;
}
}
function leftPad(value, length, char = '0') {
while (value.length < length) {
value = char + value;
}
return value;
}
export function toHexString(bufferOrValue, bitsize = 32) {
if (bufferOrValue instanceof ArrayBuffer) {
return Array.from(new Uint8Array(bufferOrValue)).map(b => b.toString(16).padStart(2, '0')).join('');
}
return leftPad((bufferOrValue >>> 0).toString(16), bitsize / 4);
}
/**
* A SHA1 implementation that works with strings and does not allocate.
*/
export class StringSHA1 {
static { this._bigBlock32 = new DataView(new ArrayBuffer(320)); } // 80 * 4 = 320
constructor() {
this._h0 = 0x67452301;
this._h1 = 0xEFCDAB89;
this._h2 = 0x98BADCFE;
this._h3 = 0x10325476;
this._h4 = 0xC3D2E1F0;
this._buff = new Uint8Array(64 /* 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;
}
update(str) {
const strLen = str.length;
if (strLen === 0) {
return;
}
const buff = this._buff;
let buffLen = this._buffLen;
let leftoverHighSurrogate = this._leftoverHighSurrogate;
let charCode;
let offset;
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 = 65533 /* SHA1Constant.UNICODE_REPLACEMENT */;
}
}
else {
// last character is a surrogate pair
leftoverHighSurrogate = charCode;
break;
}
}
else if (strings.isLowSurrogate(charCode)) {
// illegal => unicode replacement character
codePoint = 65533 /* 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;
}
_push(buff, buffLen, codePoint) {
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 >= 64 /* SHA1Constant.BLOCK_SIZE */) {
this._step();
buffLen -= 64 /* SHA1Constant.BLOCK_SIZE */;
this._totalLen += 64 /* SHA1Constant.BLOCK_SIZE */;
// take last 3 in case of UTF8 overflow
buff[0] = buff[64 /* SHA1Constant.BLOCK_SIZE */ + 0];
buff[1] = buff[64 /* SHA1Constant.BLOCK_SIZE */ + 1];
buff[2] = buff[64 /* SHA1Constant.BLOCK_SIZE */ + 2];
}
return buffLen;
}
digest() {
if (!this._finished) {
this._finished = true;
if (this._leftoverHighSurrogate) {
// illegal => unicode replacement character
this._leftoverHighSurrogate = 0;
this._buffLen = this._push(this._buff, this._buffLen, 65533 /* 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);
}
_wrapUp() {
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();
}
_step() {
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, k;
let temp;
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;
}
}
@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class HierarchicalKind {
static { this.sep = '.'; }
static { this.None = new HierarchicalKind('@@none@@'); } // Special kind that matches nothing
static { this.Empty = new HierarchicalKind(''); }
constructor(value) {
this.value = value;
}
equals(other) {
return this.value === other.value;
}
contains(other) {
return this.equals(other) || this.value === '' || other.value.startsWith(this.value + HierarchicalKind.sep);
}
intersects(other) {
return this.contains(other) || other.contains(this);
}
append(...parts) {
return new HierarchicalKind((this.value ? [this.value, ...parts] : parts).join(HierarchicalKind.sep));
}
}
+73
View File
@@ -0,0 +1,73 @@
import { ArrayNavigator } from './navigator.js';
export class HistoryNavigator {
constructor(history = [], limit = 10) {
this._initialize(history);
this._limit = limit;
this._onChange();
}
getHistory() {
return this._elements;
}
add(t) {
this._history.delete(t);
this._history.add(t);
this._onChange();
}
next() {
// This will navigate past the end of the last element, and in that case the input should be cleared
return this._navigator.next();
}
previous() {
if (this._currentPosition() !== 0) {
return this._navigator.previous();
}
return null;
}
current() {
return this._navigator.current();
}
first() {
return this._navigator.first();
}
last() {
return this._navigator.last();
}
isLast() {
return this._currentPosition() >= this._elements.length - 1;
}
isNowhere() {
return this._navigator.current() === null;
}
has(t) {
return this._history.has(t);
}
_onChange() {
this._reduceToLimit();
const elements = this._elements;
this._navigator = new ArrayNavigator(elements, 0, elements.length, elements.length);
}
_reduceToLimit() {
const data = this._elements;
if (data.length > this._limit) {
this._initialize(data.slice(data.length - this._limit));
}
}
_currentPosition() {
const currentElement = this._navigator.current();
if (!currentElement) {
return -1;
}
return this._elements.indexOf(currentElement);
}
_initialize(history) {
this._history = new Set();
for (const entry of history) {
this._history.add(entry);
}
}
get _elements() {
const elements = [];
this._history.forEach(e => elements.push(e));
return elements;
}
}
@@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { env } from './process.js';
export function isHotReloadEnabled() {
return env && !!env['VSCODE_DEV'];
}
export function registerHotReloadHandler(handler) {
if (!isHotReloadEnabled()) {
return { dispose() { } };
}
else {
const handlers = registerGlobalHotReloadHandler();
handlers.add(handler);
return {
dispose() { handlers.delete(handler); }
};
}
}
function registerGlobalHotReloadHandler() {
if (!hotReloadHandlers) {
hotReloadHandlers = new Set();
}
const g = globalThis;
if (!g.$hotReload_applyNewExports) {
g.$hotReload_applyNewExports = args => {
const args2 = { config: { mode: undefined }, ...args };
const results = [];
for (const h of hotReloadHandlers) {
const result = h(args2);
if (result) {
results.push(result);
}
}
if (results.length > 0) {
return newExports => {
let result = false;
for (const r of results) {
if (r(newExports)) {
result = true;
}
}
return result;
};
}
return undefined;
};
}
return hotReloadHandlers;
}
let hotReloadHandlers = undefined;
if (isHotReloadEnabled()) {
// This code does not run in production.
registerHotReloadHandler(({ oldExports, newSrc, config }) => {
if (config.mode !== 'patch-prototype') {
return undefined;
}
return newExports => {
for (const key in newExports) {
const exportedItem = newExports[key];
console.log(`[hot-reload] Patching prototype methods of '${key}'`, { exportedItem });
if (typeof exportedItem === 'function' && exportedItem.prototype) {
const oldExportedItem = oldExports[key];
if (oldExportedItem) {
for (const prop of Object.getOwnPropertyNames(exportedItem.prototype)) {
const descriptor = Object.getOwnPropertyDescriptor(exportedItem.prototype, prop);
const oldDescriptor = Object.getOwnPropertyDescriptor(oldExportedItem.prototype, prop);
if (descriptor?.value?.toString() !== oldDescriptor?.value?.toString()) {
console.log(`[hot-reload] Patching prototype method '${key}.${prop}'`);
}
Object.defineProperty(oldExportedItem.prototype, prop, descriptor);
}
newExports[key] = oldExportedItem;
}
}
}
return true;
};
});
}
@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isHotReloadEnabled, registerHotReloadHandler } from './hotReload.js';
import { observableSignalFromEvent } from './observable.js';
export function readHotReloadableExport(value, reader) {
observeHotReloadableExports([value], reader);
return value;
}
export function observeHotReloadableExports(values, reader) {
if (isHotReloadEnabled()) {
const o = observableSignalFromEvent('reload', event => registerHotReloadHandler(({ oldExports }) => {
if (![...Object.values(oldExports)].some(v => values.includes(v))) {
return undefined;
}
return (_newExports) => {
event(undefined);
return true;
};
}));
o.read(reader);
}
}
@@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { illegalArgument } from './errors.js';
import { escapeIcons } from './iconLabels.js';
import { isEqual } from './resources.js';
import { escapeRegExpCharacters } from './strings.js';
import { URI } from './uri.js';
export class MarkdownString {
constructor(value = '', isTrustedOrOptions = false) {
this.value = value;
if (typeof this.value !== 'string') {
throw illegalArgument('value');
}
if (typeof isTrustedOrOptions === 'boolean') {
this.isTrusted = isTrustedOrOptions;
this.supportThemeIcons = false;
this.supportHtml = false;
}
else {
this.isTrusted = isTrustedOrOptions.isTrusted ?? undefined;
this.supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false;
this.supportHtml = isTrustedOrOptions.supportHtml ?? false;
}
}
appendText(value, newlineStyle = 0 /* MarkdownStringTextNewlineStyle.Paragraph */) {
this.value += escapeMarkdownSyntaxTokens(this.supportThemeIcons ? escapeIcons(value) : value) // CodeQL [SM02383] The Markdown is fully sanitized after being rendered.
.replace(/([ \t]+)/g, (_match, g1) => '&nbsp;'.repeat(g1.length)) // CodeQL [SM02383] The Markdown is fully sanitized after being rendered.
.replace(/\>/gm, '\\>') // CodeQL [SM02383] The Markdown is fully sanitized after being rendered.
.replace(/\n/g, newlineStyle === 1 /* MarkdownStringTextNewlineStyle.Break */ ? '\\\n' : '\n\n'); // CodeQL [SM02383] The Markdown is fully sanitized after being rendered.
return this;
}
appendMarkdown(value) {
this.value += value;
return this;
}
appendCodeblock(langId, code) {
this.value += `\n${appendEscapedMarkdownCodeBlockFence(code, langId)}\n`;
return this;
}
appendLink(target, label, title) {
this.value += '[';
this.value += this._escape(label, ']');
this.value += '](';
this.value += this._escape(String(target), ')');
if (title) {
this.value += ` "${this._escape(this._escape(title, '"'), ')')}"`;
}
this.value += ')';
return this;
}
_escape(value, ch) {
const r = new RegExp(escapeRegExpCharacters(ch), 'g');
return value.replace(r, (match, offset) => {
if (value.charAt(offset - 1) !== '\\') {
return `\\${match}`;
}
else {
return match;
}
});
}
}
export function isEmptyMarkdownString(oneOrMany) {
if (isMarkdownString(oneOrMany)) {
return !oneOrMany.value;
}
else if (Array.isArray(oneOrMany)) {
return oneOrMany.every(isEmptyMarkdownString);
}
else {
return true;
}
}
export function isMarkdownString(thing) {
if (thing instanceof MarkdownString) {
return true;
}
else if (thing && typeof thing === 'object') {
return typeof thing.value === 'string'
&& (typeof thing.isTrusted === 'boolean' || typeof thing.isTrusted === 'object' || thing.isTrusted === undefined)
&& (typeof thing.supportThemeIcons === 'boolean' || thing.supportThemeIcons === undefined);
}
return false;
}
export function markdownStringEqual(a, b) {
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
&& a.supportHtml === b.supportHtml
&& (a.baseUri === b.baseUri || !!a.baseUri && !!b.baseUri && isEqual(URI.from(a.baseUri), URI.from(b.baseUri)));
}
}
export function escapeMarkdownSyntaxTokens(text) {
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
return text.replace(/[\\`*_{}[\]()#+\-!~]/g, '\\$&'); // CodeQL [SM02383] Backslash is escaped in the character class
}
/**
* @see https://github.com/microsoft/vscode/issues/193746
*/
export function appendEscapedMarkdownCodeBlockFence(code, langId) {
const longestFenceLength = code.match(/^`+/gm)?.reduce((a, b) => (a.length > b.length ? a : b)).length ??
0;
const desiredFenceLength = longestFenceLength >= 3 ? longestFenceLength + 1 : 3;
// the markdown result
return [
`${'`'.repeat(desiredFenceLength)}${langId}`,
code,
`${'`'.repeat(desiredFenceLength)}`,
].join('\n');
}
export function escapeDoubleQuotes(input) {
return input.replace(/"/g, '&quot;');
}
export function removeMarkdownEscapes(text) {
if (!text) {
return text;
}
return text.replace(/\\([\\`*_{}[\]()#+\-.!~])/g, '$1');
}
export function parseHrefAndDimensions(href) {
const dimensions = [];
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 };
}
@@ -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.
*--------------------------------------------------------------------------------------------*/
import { matchesFuzzy } from './filters.js';
import { ltrim } from './strings.js';
import { ThemeIcon } from './themables.js';
const iconStartMarker = '$(';
const iconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameExpression}(?:${ThemeIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups
const escapeIconsRegex = new RegExp(`(\\\\)?${iconsRegex.source}`, 'g');
export function escapeIcons(text) {
return text.replace(escapeIconsRegex, (match, escaped) => escaped ? match : `\\${match}`);
}
const markdownEscapedIconsRegex = new RegExp(`\\\\${iconsRegex.source}`, 'g');
export function markdownEscapeEscapedIcons(text) {
// Need to add an extra \ for escaping in markdown
return text.replace(markdownEscapedIconsRegex, match => `\\${match}`);
}
const stripIconsRegex = new RegExp(`(\\s)?(\\\\)?${iconsRegex.source}(\\s)?`, 'g');
/**
* Takes a label with icons (`$(iconId)xyz`) and strips the icons out (`xyz`)
*/
export function stripIcons(text) {
if (text.indexOf(iconStartMarker) === -1) {
return text;
}
return text.replace(stripIconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || '');
}
/**
* Takes a label with icons (`$(iconId)xyz`), removes the icon syntax adds whitespace so that screen readers can read the text better.
*/
export function getCodiconAriaLabel(text) {
if (!text) {
return '';
}
return text.replace(/\$\((.*?)\)/g, (_match, codiconName) => ` ${codiconName} `).trim();
}
const _parseIconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameCharacter}+\\)`, 'g');
/**
* Takes a label with icons (`abc $(iconId)xyz`) and returns the text (`abc xyz`) and the offsets of the icons (`[3]`)
*/
export function parseLabelWithIcons(input) {
_parseIconsRegex.lastIndex = 0;
let text = '';
const iconOffsets = [];
let iconsOffset = 0;
while (true) {
const pos = _parseIconsRegex.lastIndex;
const match = _parseIconsRegex.exec(input);
const chars = input.substring(pos, match?.index);
if (chars.length > 0) {
text += chars;
for (let i = 0; i < chars.length; i++) {
iconOffsets.push(iconsOffset);
}
}
if (!match) {
break;
}
iconsOffset += match[0].length;
}
return { text, iconOffsets };
}
export function matchesFuzzyIconAware(query, target, enableSeparateSubstringMatching = false) {
const { text, iconOffsets } = target;
// Return early if there are no icon markers in the word to match against
if (!iconOffsets || iconOffsets.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 icon
const wordToMatchAgainstWithoutIconsTrimmed = ltrim(text, ' ');
const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutIconsTrimmed.length;
// match on value without icon
const matches = matchesFuzzy(query, wordToMatchAgainstWithoutIconsTrimmed, enableSeparateSubstringMatching);
// Map matches back to offsets with icon and trimming
if (matches) {
for (const match of matches) {
const iconOffset = iconOffsets[match.start + leadingWhitespaceOffset] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
match.start += iconOffset;
match.end += iconOffset;
}
}
return matches;
}
@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* 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 {
constructor(prefix) {
this._prefix = prefix;
this._lastId = 0;
}
nextId() {
return this._prefix + (++this._lastId);
}
}
export const defaultGenerator = new IdGenerator('id#');
+30
View File
@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from './event.js';
export class IMEImpl {
constructor() {
this._onDidChange = new Emitter();
this.onDidChange = this._onDidChange.event;
this._enabled = true;
}
get enabled() {
return this._enabled;
}
/**
* Enable IME
*/
enable() {
this._enabled = true;
this._onDidChange.fire();
}
/**
* Disable IME
*/
disable() {
this._enabled = false;
this._onDidChange.fire();
}
}
export const IME = new IMEImpl();
+148
View File
@@ -0,0 +1,148 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export var Iterable;
(function (Iterable) {
function is(thing) {
return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function';
}
Iterable.is = is;
const _empty = Object.freeze([]);
function empty() {
return _empty;
}
Iterable.empty = empty;
function* single(element) {
yield element;
}
Iterable.single = single;
function wrap(iterableOrElement) {
if (is(iterableOrElement)) {
return iterableOrElement;
}
else {
return single(iterableOrElement);
}
}
Iterable.wrap = wrap;
function from(iterable) {
return iterable || _empty;
}
Iterable.from = from;
function* reverse(array) {
for (let i = array.length - 1; i >= 0; i--) {
yield array[i];
}
}
Iterable.reverse = reverse;
function isEmpty(iterable) {
return !iterable || iterable[Symbol.iterator]().next().done === true;
}
Iterable.isEmpty = isEmpty;
function first(iterable) {
return iterable[Symbol.iterator]().next().value;
}
Iterable.first = first;
function some(iterable, predicate) {
let i = 0;
for (const element of iterable) {
if (predicate(element, i++)) {
return true;
}
}
return false;
}
Iterable.some = some;
function find(iterable, predicate) {
for (const element of iterable) {
if (predicate(element)) {
return element;
}
}
return undefined;
}
Iterable.find = find;
function* filter(iterable, predicate) {
for (const element of iterable) {
if (predicate(element)) {
yield element;
}
}
}
Iterable.filter = filter;
function* map(iterable, fn) {
let index = 0;
for (const element of iterable) {
yield fn(element, index++);
}
}
Iterable.map = map;
function* flatMap(iterable, fn) {
let index = 0;
for (const element of iterable) {
yield* fn(element, index++);
}
}
Iterable.flatMap = flatMap;
function* concat(...iterables) {
for (const iterable of iterables) {
yield* iterable;
}
}
Iterable.concat = concat;
function reduce(iterable, reducer, initialValue) {
let value = initialValue;
for (const element of iterable) {
value = reducer(value, element);
}
return value;
}
Iterable.reduce = reduce;
/**
* Returns an iterable slice of the array, with the same semantics as `array.slice()`.
*/
function* slice(arr, from, to = arr.length) {
if (from < 0) {
from += arr.length;
}
if (to < 0) {
to += arr.length;
}
else if (to > arr.length) {
to = arr.length;
}
for (; from < to; from++) {
yield arr[from];
}
}
Iterable.slice = slice;
/**
* Consumes `atMost` elements from iterable and returns the consumed elements,
* and an iterable for the rest of the elements.
*/
function consume(iterable, atMost = Number.POSITIVE_INFINITY) {
const consumed = [];
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; } }];
}
Iterable.consume = consume;
async function asyncToArray(iterable) {
const result = [];
for await (const item of iterable) {
result.push(item);
}
return Promise.resolve(result);
}
Iterable.asyncToArray = asyncToArray;
})(Iterable || (Iterable = {}));
@@ -0,0 +1,5 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export {};
+372
View File
@@ -0,0 +1,372 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class KeyCodeStrMap {
constructor() {
this._keyCodeToStr = [];
this._strToKeyCode = Object.create(null);
}
define(keyCode, str) {
this._keyCodeToStr[keyCode] = str;
this._strToKeyCode[str.toLowerCase()] = keyCode;
}
keyCodeToStr(keyCode) {
return this._keyCodeToStr[keyCode];
}
strToKeyCode(str) {
return this._strToKeyCode[str.toLowerCase()] || 0 /* KeyCode.Unknown */;
}
}
const uiMap = new KeyCodeStrMap();
const userSettingsUSMap = new KeyCodeStrMap();
const userSettingsGeneralMap = new KeyCodeStrMap();
export const EVENT_KEY_CODE_MAP = new Array(230);
export const NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE = {};
const scanCodeIntToStr = [];
const scanCodeStrToInt = Object.create(null);
const scanCodeLowerCaseStrToInt = Object.create(null);
/**
* -1 if a ScanCode => KeyCode mapping depends on kb layout.
*/
export const IMMUTABLE_CODE_TO_KEY_CODE = [];
/**
* -1 if a KeyCode => ScanCode mapping depends on kb layout.
*/
export const IMMUTABLE_KEY_CODE_TO_CODE = [];
for (let i = 0; i <= 193 /* ScanCode.MAX_VALUE */; i++) {
IMMUTABLE_CODE_TO_KEY_CODE[i] = -1 /* KeyCode.DependsOnKbLayout */;
}
for (let i = 0; i <= 132 /* KeyCode.MAX_VALUE */; i++) {
IMMUTABLE_KEY_CODE_TO_CODE[i] = -1 /* ScanCode.DependsOnKbLayout */;
}
(function () {
// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
// See https://github.com/microsoft/node-native-keymap/blob/88c0b0e5/deps/chromium/keyboard_codes_win.h
const empty = '';
const mappings = [
// immutable, scanCode, scanCodeStr, keyCode, keyCodeStr, eventKeyCode, vkey, usUserSettingsLabel, generalUserSettingsLabel
[1, 0 /* ScanCode.None */, 'None', 0 /* KeyCode.Unknown */, 'unknown', 0, 'VK_UNKNOWN', empty, empty],
[1, 1 /* ScanCode.Hyper */, 'Hyper', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 2 /* ScanCode.Super */, 'Super', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 3 /* ScanCode.Fn */, 'Fn', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 4 /* ScanCode.FnLock */, 'FnLock', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 5 /* ScanCode.Suspend */, 'Suspend', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 6 /* ScanCode.Resume */, 'Resume', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 7 /* ScanCode.Turbo */, 'Turbo', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 8 /* ScanCode.Sleep */, 'Sleep', 0 /* KeyCode.Unknown */, empty, 0, 'VK_SLEEP', empty, empty],
[1, 9 /* ScanCode.WakeUp */, 'WakeUp', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[0, 10 /* ScanCode.KeyA */, 'KeyA', 31 /* KeyCode.KeyA */, 'A', 65, 'VK_A', empty, empty],
[0, 11 /* ScanCode.KeyB */, 'KeyB', 32 /* KeyCode.KeyB */, 'B', 66, 'VK_B', empty, empty],
[0, 12 /* ScanCode.KeyC */, 'KeyC', 33 /* KeyCode.KeyC */, 'C', 67, 'VK_C', empty, empty],
[0, 13 /* ScanCode.KeyD */, 'KeyD', 34 /* KeyCode.KeyD */, 'D', 68, 'VK_D', empty, empty],
[0, 14 /* ScanCode.KeyE */, 'KeyE', 35 /* KeyCode.KeyE */, 'E', 69, 'VK_E', empty, empty],
[0, 15 /* ScanCode.KeyF */, 'KeyF', 36 /* KeyCode.KeyF */, 'F', 70, 'VK_F', empty, empty],
[0, 16 /* ScanCode.KeyG */, 'KeyG', 37 /* KeyCode.KeyG */, 'G', 71, 'VK_G', empty, empty],
[0, 17 /* ScanCode.KeyH */, 'KeyH', 38 /* KeyCode.KeyH */, 'H', 72, 'VK_H', empty, empty],
[0, 18 /* ScanCode.KeyI */, 'KeyI', 39 /* KeyCode.KeyI */, 'I', 73, 'VK_I', empty, empty],
[0, 19 /* ScanCode.KeyJ */, 'KeyJ', 40 /* KeyCode.KeyJ */, 'J', 74, 'VK_J', empty, empty],
[0, 20 /* ScanCode.KeyK */, 'KeyK', 41 /* KeyCode.KeyK */, 'K', 75, 'VK_K', empty, empty],
[0, 21 /* ScanCode.KeyL */, 'KeyL', 42 /* KeyCode.KeyL */, 'L', 76, 'VK_L', empty, empty],
[0, 22 /* ScanCode.KeyM */, 'KeyM', 43 /* KeyCode.KeyM */, 'M', 77, 'VK_M', empty, empty],
[0, 23 /* ScanCode.KeyN */, 'KeyN', 44 /* KeyCode.KeyN */, 'N', 78, 'VK_N', empty, empty],
[0, 24 /* ScanCode.KeyO */, 'KeyO', 45 /* KeyCode.KeyO */, 'O', 79, 'VK_O', empty, empty],
[0, 25 /* ScanCode.KeyP */, 'KeyP', 46 /* KeyCode.KeyP */, 'P', 80, 'VK_P', empty, empty],
[0, 26 /* ScanCode.KeyQ */, 'KeyQ', 47 /* KeyCode.KeyQ */, 'Q', 81, 'VK_Q', empty, empty],
[0, 27 /* ScanCode.KeyR */, 'KeyR', 48 /* KeyCode.KeyR */, 'R', 82, 'VK_R', empty, empty],
[0, 28 /* ScanCode.KeyS */, 'KeyS', 49 /* KeyCode.KeyS */, 'S', 83, 'VK_S', empty, empty],
[0, 29 /* ScanCode.KeyT */, 'KeyT', 50 /* KeyCode.KeyT */, 'T', 84, 'VK_T', empty, empty],
[0, 30 /* ScanCode.KeyU */, 'KeyU', 51 /* KeyCode.KeyU */, 'U', 85, 'VK_U', empty, empty],
[0, 31 /* ScanCode.KeyV */, 'KeyV', 52 /* KeyCode.KeyV */, 'V', 86, 'VK_V', empty, empty],
[0, 32 /* ScanCode.KeyW */, 'KeyW', 53 /* KeyCode.KeyW */, 'W', 87, 'VK_W', empty, empty],
[0, 33 /* ScanCode.KeyX */, 'KeyX', 54 /* KeyCode.KeyX */, 'X', 88, 'VK_X', empty, empty],
[0, 34 /* ScanCode.KeyY */, 'KeyY', 55 /* KeyCode.KeyY */, 'Y', 89, 'VK_Y', empty, empty],
[0, 35 /* ScanCode.KeyZ */, 'KeyZ', 56 /* KeyCode.KeyZ */, 'Z', 90, 'VK_Z', empty, empty],
[0, 36 /* ScanCode.Digit1 */, 'Digit1', 22 /* KeyCode.Digit1 */, '1', 49, 'VK_1', empty, empty],
[0, 37 /* ScanCode.Digit2 */, 'Digit2', 23 /* KeyCode.Digit2 */, '2', 50, 'VK_2', empty, empty],
[0, 38 /* ScanCode.Digit3 */, 'Digit3', 24 /* KeyCode.Digit3 */, '3', 51, 'VK_3', empty, empty],
[0, 39 /* ScanCode.Digit4 */, 'Digit4', 25 /* KeyCode.Digit4 */, '4', 52, 'VK_4', empty, empty],
[0, 40 /* ScanCode.Digit5 */, 'Digit5', 26 /* KeyCode.Digit5 */, '5', 53, 'VK_5', empty, empty],
[0, 41 /* ScanCode.Digit6 */, 'Digit6', 27 /* KeyCode.Digit6 */, '6', 54, 'VK_6', empty, empty],
[0, 42 /* ScanCode.Digit7 */, 'Digit7', 28 /* KeyCode.Digit7 */, '7', 55, 'VK_7', empty, empty],
[0, 43 /* ScanCode.Digit8 */, 'Digit8', 29 /* KeyCode.Digit8 */, '8', 56, 'VK_8', empty, empty],
[0, 44 /* ScanCode.Digit9 */, 'Digit9', 30 /* KeyCode.Digit9 */, '9', 57, 'VK_9', empty, empty],
[0, 45 /* ScanCode.Digit0 */, 'Digit0', 21 /* KeyCode.Digit0 */, '0', 48, 'VK_0', empty, empty],
[1, 46 /* ScanCode.Enter */, 'Enter', 3 /* KeyCode.Enter */, 'Enter', 13, 'VK_RETURN', empty, empty],
[1, 47 /* ScanCode.Escape */, 'Escape', 9 /* KeyCode.Escape */, 'Escape', 27, 'VK_ESCAPE', empty, empty],
[1, 48 /* ScanCode.Backspace */, 'Backspace', 1 /* KeyCode.Backspace */, 'Backspace', 8, 'VK_BACK', empty, empty],
[1, 49 /* ScanCode.Tab */, 'Tab', 2 /* KeyCode.Tab */, 'Tab', 9, 'VK_TAB', empty, empty],
[1, 50 /* ScanCode.Space */, 'Space', 10 /* KeyCode.Space */, 'Space', 32, 'VK_SPACE', empty, empty],
[0, 51 /* ScanCode.Minus */, 'Minus', 88 /* KeyCode.Minus */, '-', 189, 'VK_OEM_MINUS', '-', 'OEM_MINUS'],
[0, 52 /* ScanCode.Equal */, 'Equal', 86 /* KeyCode.Equal */, '=', 187, 'VK_OEM_PLUS', '=', 'OEM_PLUS'],
[0, 53 /* ScanCode.BracketLeft */, 'BracketLeft', 92 /* KeyCode.BracketLeft */, '[', 219, 'VK_OEM_4', '[', 'OEM_4'],
[0, 54 /* ScanCode.BracketRight */, 'BracketRight', 94 /* KeyCode.BracketRight */, ']', 221, 'VK_OEM_6', ']', 'OEM_6'],
[0, 55 /* ScanCode.Backslash */, 'Backslash', 93 /* KeyCode.Backslash */, '\\', 220, 'VK_OEM_5', '\\', 'OEM_5'],
[0, 56 /* ScanCode.IntlHash */, 'IntlHash', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty], // has been dropped from the w3c spec
[0, 57 /* ScanCode.Semicolon */, 'Semicolon', 85 /* KeyCode.Semicolon */, ';', 186, 'VK_OEM_1', ';', 'OEM_1'],
[0, 58 /* ScanCode.Quote */, 'Quote', 95 /* KeyCode.Quote */, '\'', 222, 'VK_OEM_7', '\'', 'OEM_7'],
[0, 59 /* ScanCode.Backquote */, 'Backquote', 91 /* KeyCode.Backquote */, '`', 192, 'VK_OEM_3', '`', 'OEM_3'],
[0, 60 /* ScanCode.Comma */, 'Comma', 87 /* KeyCode.Comma */, ',', 188, 'VK_OEM_COMMA', ',', 'OEM_COMMA'],
[0, 61 /* ScanCode.Period */, 'Period', 89 /* KeyCode.Period */, '.', 190, 'VK_OEM_PERIOD', '.', 'OEM_PERIOD'],
[0, 62 /* ScanCode.Slash */, 'Slash', 90 /* KeyCode.Slash */, '/', 191, 'VK_OEM_2', '/', 'OEM_2'],
[1, 63 /* ScanCode.CapsLock */, 'CapsLock', 8 /* KeyCode.CapsLock */, 'CapsLock', 20, 'VK_CAPITAL', empty, empty],
[1, 64 /* ScanCode.F1 */, 'F1', 59 /* KeyCode.F1 */, 'F1', 112, 'VK_F1', empty, empty],
[1, 65 /* ScanCode.F2 */, 'F2', 60 /* KeyCode.F2 */, 'F2', 113, 'VK_F2', empty, empty],
[1, 66 /* ScanCode.F3 */, 'F3', 61 /* KeyCode.F3 */, 'F3', 114, 'VK_F3', empty, empty],
[1, 67 /* ScanCode.F4 */, 'F4', 62 /* KeyCode.F4 */, 'F4', 115, 'VK_F4', empty, empty],
[1, 68 /* ScanCode.F5 */, 'F5', 63 /* KeyCode.F5 */, 'F5', 116, 'VK_F5', empty, empty],
[1, 69 /* ScanCode.F6 */, 'F6', 64 /* KeyCode.F6 */, 'F6', 117, 'VK_F6', empty, empty],
[1, 70 /* ScanCode.F7 */, 'F7', 65 /* KeyCode.F7 */, 'F7', 118, 'VK_F7', empty, empty],
[1, 71 /* ScanCode.F8 */, 'F8', 66 /* KeyCode.F8 */, 'F8', 119, 'VK_F8', empty, empty],
[1, 72 /* ScanCode.F9 */, 'F9', 67 /* KeyCode.F9 */, 'F9', 120, 'VK_F9', empty, empty],
[1, 73 /* ScanCode.F10 */, 'F10', 68 /* KeyCode.F10 */, 'F10', 121, 'VK_F10', empty, empty],
[1, 74 /* ScanCode.F11 */, 'F11', 69 /* KeyCode.F11 */, 'F11', 122, 'VK_F11', empty, empty],
[1, 75 /* ScanCode.F12 */, 'F12', 70 /* KeyCode.F12 */, 'F12', 123, 'VK_F12', empty, empty],
[1, 76 /* ScanCode.PrintScreen */, 'PrintScreen', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 77 /* ScanCode.ScrollLock */, 'ScrollLock', 84 /* KeyCode.ScrollLock */, 'ScrollLock', 145, 'VK_SCROLL', empty, empty],
[1, 78 /* ScanCode.Pause */, 'Pause', 7 /* KeyCode.PauseBreak */, 'PauseBreak', 19, 'VK_PAUSE', empty, empty],
[1, 79 /* ScanCode.Insert */, 'Insert', 19 /* KeyCode.Insert */, 'Insert', 45, 'VK_INSERT', empty, empty],
[1, 80 /* ScanCode.Home */, 'Home', 14 /* KeyCode.Home */, 'Home', 36, 'VK_HOME', empty, empty],
[1, 81 /* ScanCode.PageUp */, 'PageUp', 11 /* KeyCode.PageUp */, 'PageUp', 33, 'VK_PRIOR', empty, empty],
[1, 82 /* ScanCode.Delete */, 'Delete', 20 /* KeyCode.Delete */, 'Delete', 46, 'VK_DELETE', empty, empty],
[1, 83 /* ScanCode.End */, 'End', 13 /* KeyCode.End */, 'End', 35, 'VK_END', empty, empty],
[1, 84 /* ScanCode.PageDown */, 'PageDown', 12 /* KeyCode.PageDown */, 'PageDown', 34, 'VK_NEXT', empty, empty],
[1, 85 /* ScanCode.ArrowRight */, 'ArrowRight', 17 /* KeyCode.RightArrow */, 'RightArrow', 39, 'VK_RIGHT', 'Right', empty],
[1, 86 /* ScanCode.ArrowLeft */, 'ArrowLeft', 15 /* KeyCode.LeftArrow */, 'LeftArrow', 37, 'VK_LEFT', 'Left', empty],
[1, 87 /* ScanCode.ArrowDown */, 'ArrowDown', 18 /* KeyCode.DownArrow */, 'DownArrow', 40, 'VK_DOWN', 'Down', empty],
[1, 88 /* ScanCode.ArrowUp */, 'ArrowUp', 16 /* KeyCode.UpArrow */, 'UpArrow', 38, 'VK_UP', 'Up', empty],
[1, 89 /* ScanCode.NumLock */, 'NumLock', 83 /* KeyCode.NumLock */, 'NumLock', 144, 'VK_NUMLOCK', empty, empty],
[1, 90 /* ScanCode.NumpadDivide */, 'NumpadDivide', 113 /* KeyCode.NumpadDivide */, 'NumPad_Divide', 111, 'VK_DIVIDE', empty, empty],
[1, 91 /* ScanCode.NumpadMultiply */, 'NumpadMultiply', 108 /* KeyCode.NumpadMultiply */, 'NumPad_Multiply', 106, 'VK_MULTIPLY', empty, empty],
[1, 92 /* ScanCode.NumpadSubtract */, 'NumpadSubtract', 111 /* KeyCode.NumpadSubtract */, 'NumPad_Subtract', 109, 'VK_SUBTRACT', empty, empty],
[1, 93 /* ScanCode.NumpadAdd */, 'NumpadAdd', 109 /* KeyCode.NumpadAdd */, 'NumPad_Add', 107, 'VK_ADD', empty, empty],
[1, 94 /* ScanCode.NumpadEnter */, 'NumpadEnter', 3 /* KeyCode.Enter */, empty, 0, empty, empty, empty],
[1, 95 /* ScanCode.Numpad1 */, 'Numpad1', 99 /* KeyCode.Numpad1 */, 'NumPad1', 97, 'VK_NUMPAD1', empty, empty],
[1, 96 /* ScanCode.Numpad2 */, 'Numpad2', 100 /* KeyCode.Numpad2 */, 'NumPad2', 98, 'VK_NUMPAD2', empty, empty],
[1, 97 /* ScanCode.Numpad3 */, 'Numpad3', 101 /* KeyCode.Numpad3 */, 'NumPad3', 99, 'VK_NUMPAD3', empty, empty],
[1, 98 /* ScanCode.Numpad4 */, 'Numpad4', 102 /* KeyCode.Numpad4 */, 'NumPad4', 100, 'VK_NUMPAD4', empty, empty],
[1, 99 /* ScanCode.Numpad5 */, 'Numpad5', 103 /* KeyCode.Numpad5 */, 'NumPad5', 101, 'VK_NUMPAD5', empty, empty],
[1, 100 /* ScanCode.Numpad6 */, 'Numpad6', 104 /* KeyCode.Numpad6 */, 'NumPad6', 102, 'VK_NUMPAD6', empty, empty],
[1, 101 /* ScanCode.Numpad7 */, 'Numpad7', 105 /* KeyCode.Numpad7 */, 'NumPad7', 103, 'VK_NUMPAD7', empty, empty],
[1, 102 /* ScanCode.Numpad8 */, 'Numpad8', 106 /* KeyCode.Numpad8 */, 'NumPad8', 104, 'VK_NUMPAD8', empty, empty],
[1, 103 /* ScanCode.Numpad9 */, 'Numpad9', 107 /* KeyCode.Numpad9 */, 'NumPad9', 105, 'VK_NUMPAD9', empty, empty],
[1, 104 /* ScanCode.Numpad0 */, 'Numpad0', 98 /* KeyCode.Numpad0 */, 'NumPad0', 96, 'VK_NUMPAD0', empty, empty],
[1, 105 /* ScanCode.NumpadDecimal */, 'NumpadDecimal', 112 /* KeyCode.NumpadDecimal */, 'NumPad_Decimal', 110, 'VK_DECIMAL', empty, empty],
[0, 106 /* ScanCode.IntlBackslash */, 'IntlBackslash', 97 /* KeyCode.IntlBackslash */, 'OEM_102', 226, 'VK_OEM_102', empty, empty],
[1, 107 /* ScanCode.ContextMenu */, 'ContextMenu', 58 /* KeyCode.ContextMenu */, 'ContextMenu', 93, empty, empty, empty],
[1, 108 /* ScanCode.Power */, 'Power', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 109 /* ScanCode.NumpadEqual */, 'NumpadEqual', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 110 /* ScanCode.F13 */, 'F13', 71 /* KeyCode.F13 */, 'F13', 124, 'VK_F13', empty, empty],
[1, 111 /* ScanCode.F14 */, 'F14', 72 /* KeyCode.F14 */, 'F14', 125, 'VK_F14', empty, empty],
[1, 112 /* ScanCode.F15 */, 'F15', 73 /* KeyCode.F15 */, 'F15', 126, 'VK_F15', empty, empty],
[1, 113 /* ScanCode.F16 */, 'F16', 74 /* KeyCode.F16 */, 'F16', 127, 'VK_F16', empty, empty],
[1, 114 /* ScanCode.F17 */, 'F17', 75 /* KeyCode.F17 */, 'F17', 128, 'VK_F17', empty, empty],
[1, 115 /* ScanCode.F18 */, 'F18', 76 /* KeyCode.F18 */, 'F18', 129, 'VK_F18', empty, empty],
[1, 116 /* ScanCode.F19 */, 'F19', 77 /* KeyCode.F19 */, 'F19', 130, 'VK_F19', empty, empty],
[1, 117 /* ScanCode.F20 */, 'F20', 78 /* KeyCode.F20 */, 'F20', 131, 'VK_F20', empty, empty],
[1, 118 /* ScanCode.F21 */, 'F21', 79 /* KeyCode.F21 */, 'F21', 132, 'VK_F21', empty, empty],
[1, 119 /* ScanCode.F22 */, 'F22', 80 /* KeyCode.F22 */, 'F22', 133, 'VK_F22', empty, empty],
[1, 120 /* ScanCode.F23 */, 'F23', 81 /* KeyCode.F23 */, 'F23', 134, 'VK_F23', empty, empty],
[1, 121 /* ScanCode.F24 */, 'F24', 82 /* KeyCode.F24 */, 'F24', 135, 'VK_F24', empty, empty],
[1, 122 /* ScanCode.Open */, 'Open', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 123 /* ScanCode.Help */, 'Help', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 124 /* ScanCode.Select */, 'Select', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 125 /* ScanCode.Again */, 'Again', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 126 /* ScanCode.Undo */, 'Undo', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 127 /* ScanCode.Cut */, 'Cut', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 128 /* ScanCode.Copy */, 'Copy', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 129 /* ScanCode.Paste */, 'Paste', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 130 /* ScanCode.Find */, 'Find', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 131 /* ScanCode.AudioVolumeMute */, 'AudioVolumeMute', 117 /* KeyCode.AudioVolumeMute */, 'AudioVolumeMute', 173, 'VK_VOLUME_MUTE', empty, empty],
[1, 132 /* ScanCode.AudioVolumeUp */, 'AudioVolumeUp', 118 /* KeyCode.AudioVolumeUp */, 'AudioVolumeUp', 175, 'VK_VOLUME_UP', empty, empty],
[1, 133 /* ScanCode.AudioVolumeDown */, 'AudioVolumeDown', 119 /* KeyCode.AudioVolumeDown */, 'AudioVolumeDown', 174, 'VK_VOLUME_DOWN', empty, empty],
[1, 134 /* ScanCode.NumpadComma */, 'NumpadComma', 110 /* KeyCode.NUMPAD_SEPARATOR */, 'NumPad_Separator', 108, 'VK_SEPARATOR', empty, empty],
[0, 135 /* ScanCode.IntlRo */, 'IntlRo', 115 /* KeyCode.ABNT_C1 */, 'ABNT_C1', 193, 'VK_ABNT_C1', empty, empty],
[1, 136 /* ScanCode.KanaMode */, 'KanaMode', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[0, 137 /* ScanCode.IntlYen */, 'IntlYen', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 138 /* ScanCode.Convert */, 'Convert', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 139 /* ScanCode.NonConvert */, 'NonConvert', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 140 /* ScanCode.Lang1 */, 'Lang1', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 141 /* ScanCode.Lang2 */, 'Lang2', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 142 /* ScanCode.Lang3 */, 'Lang3', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 143 /* ScanCode.Lang4 */, 'Lang4', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 144 /* ScanCode.Lang5 */, 'Lang5', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 145 /* ScanCode.Abort */, 'Abort', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 146 /* ScanCode.Props */, 'Props', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 147 /* ScanCode.NumpadParenLeft */, 'NumpadParenLeft', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 148 /* ScanCode.NumpadParenRight */, 'NumpadParenRight', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 149 /* ScanCode.NumpadBackspace */, 'NumpadBackspace', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 150 /* ScanCode.NumpadMemoryStore */, 'NumpadMemoryStore', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 151 /* ScanCode.NumpadMemoryRecall */, 'NumpadMemoryRecall', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 152 /* ScanCode.NumpadMemoryClear */, 'NumpadMemoryClear', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 153 /* ScanCode.NumpadMemoryAdd */, 'NumpadMemoryAdd', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 154 /* ScanCode.NumpadMemorySubtract */, 'NumpadMemorySubtract', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 155 /* ScanCode.NumpadClear */, 'NumpadClear', 131 /* KeyCode.Clear */, 'Clear', 12, 'VK_CLEAR', empty, empty],
[1, 156 /* ScanCode.NumpadClearEntry */, 'NumpadClearEntry', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 0 /* ScanCode.None */, empty, 5 /* KeyCode.Ctrl */, 'Ctrl', 17, 'VK_CONTROL', empty, empty],
[1, 0 /* ScanCode.None */, empty, 4 /* KeyCode.Shift */, 'Shift', 16, 'VK_SHIFT', empty, empty],
[1, 0 /* ScanCode.None */, empty, 6 /* KeyCode.Alt */, 'Alt', 18, 'VK_MENU', empty, empty],
[1, 0 /* ScanCode.None */, empty, 57 /* KeyCode.Meta */, 'Meta', 91, 'VK_COMMAND', empty, empty],
[1, 157 /* ScanCode.ControlLeft */, 'ControlLeft', 5 /* KeyCode.Ctrl */, empty, 0, 'VK_LCONTROL', empty, empty],
[1, 158 /* ScanCode.ShiftLeft */, 'ShiftLeft', 4 /* KeyCode.Shift */, empty, 0, 'VK_LSHIFT', empty, empty],
[1, 159 /* ScanCode.AltLeft */, 'AltLeft', 6 /* KeyCode.Alt */, empty, 0, 'VK_LMENU', empty, empty],
[1, 160 /* ScanCode.MetaLeft */, 'MetaLeft', 57 /* KeyCode.Meta */, empty, 0, 'VK_LWIN', empty, empty],
[1, 161 /* ScanCode.ControlRight */, 'ControlRight', 5 /* KeyCode.Ctrl */, empty, 0, 'VK_RCONTROL', empty, empty],
[1, 162 /* ScanCode.ShiftRight */, 'ShiftRight', 4 /* KeyCode.Shift */, empty, 0, 'VK_RSHIFT', empty, empty],
[1, 163 /* ScanCode.AltRight */, 'AltRight', 6 /* KeyCode.Alt */, empty, 0, 'VK_RMENU', empty, empty],
[1, 164 /* ScanCode.MetaRight */, 'MetaRight', 57 /* KeyCode.Meta */, empty, 0, 'VK_RWIN', empty, empty],
[1, 165 /* ScanCode.BrightnessUp */, 'BrightnessUp', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 166 /* ScanCode.BrightnessDown */, 'BrightnessDown', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 167 /* ScanCode.MediaPlay */, 'MediaPlay', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 168 /* ScanCode.MediaRecord */, 'MediaRecord', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 169 /* ScanCode.MediaFastForward */, 'MediaFastForward', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 170 /* ScanCode.MediaRewind */, 'MediaRewind', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 171 /* ScanCode.MediaTrackNext */, 'MediaTrackNext', 124 /* KeyCode.MediaTrackNext */, 'MediaTrackNext', 176, 'VK_MEDIA_NEXT_TRACK', empty, empty],
[1, 172 /* ScanCode.MediaTrackPrevious */, 'MediaTrackPrevious', 125 /* KeyCode.MediaTrackPrevious */, 'MediaTrackPrevious', 177, 'VK_MEDIA_PREV_TRACK', empty, empty],
[1, 173 /* ScanCode.MediaStop */, 'MediaStop', 126 /* KeyCode.MediaStop */, 'MediaStop', 178, 'VK_MEDIA_STOP', empty, empty],
[1, 174 /* ScanCode.Eject */, 'Eject', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 175 /* ScanCode.MediaPlayPause */, 'MediaPlayPause', 127 /* KeyCode.MediaPlayPause */, 'MediaPlayPause', 179, 'VK_MEDIA_PLAY_PAUSE', empty, empty],
[1, 176 /* ScanCode.MediaSelect */, 'MediaSelect', 128 /* KeyCode.LaunchMediaPlayer */, 'LaunchMediaPlayer', 181, 'VK_MEDIA_LAUNCH_MEDIA_SELECT', empty, empty],
[1, 177 /* ScanCode.LaunchMail */, 'LaunchMail', 129 /* KeyCode.LaunchMail */, 'LaunchMail', 180, 'VK_MEDIA_LAUNCH_MAIL', empty, empty],
[1, 178 /* ScanCode.LaunchApp2 */, 'LaunchApp2', 130 /* KeyCode.LaunchApp2 */, 'LaunchApp2', 183, 'VK_MEDIA_LAUNCH_APP2', empty, empty],
[1, 179 /* ScanCode.LaunchApp1 */, 'LaunchApp1', 0 /* KeyCode.Unknown */, empty, 0, 'VK_MEDIA_LAUNCH_APP1', empty, empty],
[1, 180 /* ScanCode.SelectTask */, 'SelectTask', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 181 /* ScanCode.LaunchScreenSaver */, 'LaunchScreenSaver', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 182 /* ScanCode.BrowserSearch */, 'BrowserSearch', 120 /* KeyCode.BrowserSearch */, 'BrowserSearch', 170, 'VK_BROWSER_SEARCH', empty, empty],
[1, 183 /* ScanCode.BrowserHome */, 'BrowserHome', 121 /* KeyCode.BrowserHome */, 'BrowserHome', 172, 'VK_BROWSER_HOME', empty, empty],
[1, 184 /* ScanCode.BrowserBack */, 'BrowserBack', 122 /* KeyCode.BrowserBack */, 'BrowserBack', 166, 'VK_BROWSER_BACK', empty, empty],
[1, 185 /* ScanCode.BrowserForward */, 'BrowserForward', 123 /* KeyCode.BrowserForward */, 'BrowserForward', 167, 'VK_BROWSER_FORWARD', empty, empty],
[1, 186 /* ScanCode.BrowserStop */, 'BrowserStop', 0 /* KeyCode.Unknown */, empty, 0, 'VK_BROWSER_STOP', empty, empty],
[1, 187 /* ScanCode.BrowserRefresh */, 'BrowserRefresh', 0 /* KeyCode.Unknown */, empty, 0, 'VK_BROWSER_REFRESH', empty, empty],
[1, 188 /* ScanCode.BrowserFavorites */, 'BrowserFavorites', 0 /* KeyCode.Unknown */, empty, 0, 'VK_BROWSER_FAVORITES', empty, empty],
[1, 189 /* ScanCode.ZoomToggle */, 'ZoomToggle', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 190 /* ScanCode.MailReply */, 'MailReply', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 191 /* ScanCode.MailForward */, 'MailForward', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
[1, 192 /* ScanCode.MailSend */, 'MailSend', 0 /* KeyCode.Unknown */, empty, 0, empty, empty, empty],
// See https://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html
// If an Input Method Editor is processing key input and the event is keydown, return 229.
[1, 0 /* ScanCode.None */, empty, 114 /* KeyCode.KEY_IN_COMPOSITION */, 'KeyInComposition', 229, empty, empty, empty],
[1, 0 /* ScanCode.None */, empty, 116 /* KeyCode.ABNT_C2 */, 'ABNT_C2', 194, 'VK_ABNT_C2', empty, empty],
[1, 0 /* ScanCode.None */, empty, 96 /* KeyCode.OEM_8 */, 'OEM_8', 223, 'VK_OEM_8', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_KANA', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_HANGUL', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_JUNJA', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_FINAL', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_HANJA', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_KANJI', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_CONVERT', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_NONCONVERT', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_ACCEPT', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_MODECHANGE', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_SELECT', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_PRINT', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_EXECUTE', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_SNAPSHOT', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_HELP', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_APPS', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_PROCESSKEY', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_PACKET', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_DBE_SBCSCHAR', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_DBE_DBCSCHAR', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_ATTN', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_CRSEL', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_EXSEL', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_EREOF', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_PLAY', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_ZOOM', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_NONAME', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_PA1', empty, empty],
[1, 0 /* ScanCode.None */, empty, 0 /* KeyCode.Unknown */, empty, 0, 'VK_OEM_CLEAR', empty, empty],
];
const seenKeyCode = [];
const seenScanCode = [];
for (const mapping of mappings) {
const [immutable, scanCode, scanCodeStr, keyCode, keyCodeStr, eventKeyCode, vkey, usUserSettingsLabel, generalUserSettingsLabel] = mapping;
if (!seenScanCode[scanCode]) {
seenScanCode[scanCode] = true;
scanCodeIntToStr[scanCode] = scanCodeStr;
scanCodeStrToInt[scanCodeStr] = scanCode;
scanCodeLowerCaseStrToInt[scanCodeStr.toLowerCase()] = scanCode;
if (immutable) {
IMMUTABLE_CODE_TO_KEY_CODE[scanCode] = keyCode;
if ((keyCode !== 0 /* KeyCode.Unknown */)
&& (keyCode !== 3 /* KeyCode.Enter */)
&& (keyCode !== 5 /* KeyCode.Ctrl */)
&& (keyCode !== 4 /* KeyCode.Shift */)
&& (keyCode !== 6 /* KeyCode.Alt */)
&& (keyCode !== 57 /* KeyCode.Meta */)) {
IMMUTABLE_KEY_CODE_TO_CODE[keyCode] = scanCode;
}
}
}
if (!seenKeyCode[keyCode]) {
seenKeyCode[keyCode] = true;
if (!keyCodeStr) {
throw new Error(`String representation missing for key code ${keyCode} around scan code ${scanCodeStr}`);
}
uiMap.define(keyCode, keyCodeStr);
userSettingsUSMap.define(keyCode, usUserSettingsLabel || keyCodeStr);
userSettingsGeneralMap.define(keyCode, generalUserSettingsLabel || usUserSettingsLabel || keyCodeStr);
}
if (eventKeyCode) {
EVENT_KEY_CODE_MAP[eventKeyCode] = keyCode;
}
if (vkey) {
NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE[vkey] = keyCode;
}
}
// Manually added due to the exclusion above (due to duplication with NumpadEnter)
IMMUTABLE_KEY_CODE_TO_CODE[3 /* KeyCode.Enter */] = 46 /* ScanCode.Enter */;
})();
export var KeyCodeUtils;
(function (KeyCodeUtils) {
function toString(keyCode) {
return uiMap.keyCodeToStr(keyCode);
}
KeyCodeUtils.toString = toString;
function fromString(key) {
return uiMap.strToKeyCode(key);
}
KeyCodeUtils.fromString = fromString;
function toUserSettingsUS(keyCode) {
return userSettingsUSMap.keyCodeToStr(keyCode);
}
KeyCodeUtils.toUserSettingsUS = toUserSettingsUS;
function toUserSettingsGeneral(keyCode) {
return userSettingsGeneralMap.keyCodeToStr(keyCode);
}
KeyCodeUtils.toUserSettingsGeneral = toUserSettingsGeneral;
function fromUserSettings(key) {
return userSettingsUSMap.strToKeyCode(key) || userSettingsGeneralMap.strToKeyCode(key);
}
KeyCodeUtils.fromUserSettings = fromUserSettings;
function toElectronAccelerator(keyCode) {
if (keyCode >= 98 /* KeyCode.Numpad0 */ && keyCode <= 113 /* KeyCode.NumpadDivide */) {
// [Electron Accelerators] Electron is able to parse numpad keys, but unfortunately it
// renders them just as regular keys in menus. For example, num0 is rendered as "0",
// numdiv is rendered as "/", numsub is rendered as "-".
//
// This can lead to incredible confusion, as it makes numpad based keybindings indistinguishable
// from keybindings based on regular keys.
//
// We therefore need to fall back to custom rendering for numpad keys.
return null;
}
switch (keyCode) {
case 16 /* KeyCode.UpArrow */:
return 'Up';
case 18 /* KeyCode.DownArrow */:
return 'Down';
case 15 /* KeyCode.LeftArrow */:
return 'Left';
case 17 /* KeyCode.RightArrow */:
return 'Right';
}
return uiMap.keyCodeToStr(keyCode);
}
KeyCodeUtils.toElectronAccelerator = toElectronAccelerator;
})(KeyCodeUtils || (KeyCodeUtils = {}));
export function KeyChord(firstPart, secondPart) {
const chordPart = ((secondPart & 0x0000FFFF) << 16) >>> 0;
return (firstPart | chordPart) >>> 0;
}
@@ -0,0 +1,136 @@
/*---------------------------------------------------------------------------------------------
* 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 '../../nls.js';
export class ModifierLabelProvider {
constructor(mac, windows, linux = windows) {
this.modifierLabels = [null]; // index 0 will never me accessed.
this.modifierLabels[2 /* OperatingSystem.Macintosh */] = mac;
this.modifierLabels[1 /* OperatingSystem.Windows */] = windows;
this.modifierLabels[3 /* OperatingSystem.Linux */] = linux;
}
toLabel(OS, chords, keyLabelProvider) {
if (chords.length === 0) {
return null;
}
const result = [];
for (let i = 0, len = chords.length; i < len; i++) {
const chord = chords[i];
const keyLabel = keyLabelProvider(chord);
if (keyLabel === null) {
// this keybinding cannot be expressed...
return null;
}
result[i] = _simpleAsString(chord, 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: '\u2303',
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: 'optKey.long', comment: ['This is the long form for the Alt/Option key on the keyboard'] }, "Option"),
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, key, labels) {
if (key === null) {
return '';
}
const result = [];
// 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);
}
@@ -0,0 +1,123 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { illegalArgument } from './errors.js';
export function decodeKeybinding(keybinding, OS) {
if (typeof keybinding === 'number') {
if (keybinding === 0) {
return null;
}
const firstChord = (keybinding & 0x0000FFFF) >>> 0;
const secondChord = (keybinding & 0xFFFF0000) >>> 16;
if (secondChord !== 0) {
return new Keybinding([
createSimpleKeybinding(firstChord, OS),
createSimpleKeybinding(secondChord, OS)
]);
}
return new Keybinding([createSimpleKeybinding(firstChord, OS)]);
}
else {
const chords = [];
for (let i = 0; i < keybinding.length; i++) {
chords.push(createSimpleKeybinding(keybinding[i], OS));
}
return new Keybinding(chords);
}
}
export function createSimpleKeybinding(keybinding, OS) {
const ctrlCmd = (keybinding & 2048 /* BinaryKeybindingsMask.CtrlCmd */ ? true : false);
const winCtrl = (keybinding & 256 /* BinaryKeybindingsMask.WinCtrl */ ? true : false);
const ctrlKey = (OS === 2 /* OperatingSystem.Macintosh */ ? winCtrl : ctrlCmd);
const shiftKey = (keybinding & 1024 /* BinaryKeybindingsMask.Shift */ ? true : false);
const altKey = (keybinding & 512 /* BinaryKeybindingsMask.Alt */ ? true : false);
const metaKey = (OS === 2 /* OperatingSystem.Macintosh */ ? ctrlCmd : winCtrl);
const keyCode = (keybinding & 255 /* BinaryKeybindingsMask.KeyCode */);
return new KeyCodeChord(ctrlKey, shiftKey, altKey, metaKey, keyCode);
}
/**
* Represents a chord which uses the `keyCode` field of keyboard events.
* A chord is a combination of keys pressed simultaneously.
*/
export class KeyCodeChord {
constructor(ctrlKey, shiftKey, altKey, metaKey, keyCode) {
this.ctrlKey = ctrlKey;
this.shiftKey = shiftKey;
this.altKey = altKey;
this.metaKey = metaKey;
this.keyCode = keyCode;
}
equals(other) {
return (other instanceof KeyCodeChord
&& this.ctrlKey === other.ctrlKey
&& this.shiftKey === other.shiftKey
&& this.altKey === other.altKey
&& this.metaKey === other.metaKey
&& this.keyCode === other.keyCode);
}
isModifierKey() {
return (this.keyCode === 0 /* KeyCode.Unknown */
|| this.keyCode === 5 /* KeyCode.Ctrl */
|| this.keyCode === 57 /* KeyCode.Meta */
|| this.keyCode === 6 /* KeyCode.Alt */
|| this.keyCode === 4 /* KeyCode.Shift */);
}
/**
* Does this keybinding refer to the key code of a modifier and it also has the modifier flag?
*/
isDuplicateModifierCase() {
return ((this.ctrlKey && this.keyCode === 5 /* KeyCode.Ctrl */)
|| (this.shiftKey && this.keyCode === 4 /* KeyCode.Shift */)
|| (this.altKey && this.keyCode === 6 /* KeyCode.Alt */)
|| (this.metaKey && this.keyCode === 57 /* KeyCode.Meta */));
}
}
/**
* Represents a chord which uses the `code` field of keyboard events.
* A chord is a combination of keys pressed simultaneously.
*/
export class ScanCodeChord {
constructor(ctrlKey, shiftKey, altKey, metaKey, scanCode) {
this.ctrlKey = ctrlKey;
this.shiftKey = shiftKey;
this.altKey = altKey;
this.metaKey = metaKey;
this.scanCode = scanCode;
}
/**
* Does this keybinding refer to the key code of a modifier and it also has the modifier flag?
*/
isDuplicateModifierCase() {
return ((this.ctrlKey && (this.scanCode === 157 /* ScanCode.ControlLeft */ || this.scanCode === 161 /* ScanCode.ControlRight */))
|| (this.shiftKey && (this.scanCode === 158 /* ScanCode.ShiftLeft */ || this.scanCode === 162 /* ScanCode.ShiftRight */))
|| (this.altKey && (this.scanCode === 159 /* ScanCode.AltLeft */ || this.scanCode === 163 /* ScanCode.AltRight */))
|| (this.metaKey && (this.scanCode === 160 /* ScanCode.MetaLeft */ || this.scanCode === 164 /* ScanCode.MetaRight */)));
}
}
/**
* A keybinding is a sequence of chords.
*/
export class Keybinding {
constructor(chords) {
if (chords.length === 0) {
throw illegalArgument(`chords`);
}
this.chords = chords;
}
}
export class ResolvedChord {
constructor(ctrlKey, shiftKey, altKey, metaKey, keyLabel, keyAriaLabel) {
this.ctrlKey = ctrlKey;
this.shiftKey = shiftKey;
this.altKey = altKey;
this.metaKey = metaKey;
this.keyLabel = keyLabel;
this.keyAriaLabel = keyAriaLabel;
}
}
/**
* A resolved keybinding. Consists of one or multiple chords.
*/
export class ResolvedKeybinding {
}
@@ -0,0 +1,9 @@
import { hasDriveLetter } from './extpath.js';
import { isWindows } from './platform.js';
export function normalizeDriveLetter(path, isWindowsOS = isWindows) {
if (hasDriveLetter(path, isWindowsOS)) {
return path.charAt(0).toUpperCase() + path.slice(1);
}
return path;
}
let normalizedUserHomeCached = Object.create(null);
+37
View File
@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class Lazy {
constructor(executor) {
this.executor = executor;
this._didRun = false;
}
/**
* 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
*/
get value() {
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() { return this._value; }
}
@@ -0,0 +1,355 @@
import { createSingleCallFunction } from './functional.js';
import { Iterable } from './iterator.js';
// #region Disposable Tracking
/**
* 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;
let disposableTracker = null;
export function setDisposableTracker(tracker) {
disposableTracker = tracker;
}
if (TRACK_DISPOSABLES) {
const __is_disposable_tracked__ = '__is_disposable_tracked__';
setDisposableTracker(new class {
trackDisposable(x) {
const stack = new Error('Potentially leaked disposable').stack;
setTimeout(() => {
if (!x[__is_disposable_tracked__]) {
console.log(stack);
}
}, 3000);
}
setParent(child, parent) {
if (child && child !== Disposable.None) {
try {
child[__is_disposable_tracked__] = true;
}
catch {
// noop
}
}
}
markAsDisposed(disposable) {
if (disposable && disposable !== Disposable.None) {
try {
disposable[__is_disposable_tracked__] = true;
}
catch {
// noop
}
}
}
markAsSingleton(disposable) { }
});
}
export function trackDisposable(x) {
disposableTracker?.trackDisposable(x);
return x;
}
export function markAsDisposed(disposable) {
disposableTracker?.markAsDisposed(disposable);
}
function setParentOfDisposable(child, parent) {
disposableTracker?.setParent(child, parent);
}
function setParentOfDisposables(children, parent) {
if (!disposableTracker) {
return;
}
for (const child of children) {
disposableTracker.setParent(child, parent);
}
}
/**
* Indicates that the given object is a singleton which does not need to be disposed.
*/
export function markAsSingleton(singleton) {
disposableTracker?.markAsSingleton(singleton);
return singleton;
}
/**
* Check if `thing` is {@link IDisposable disposable}.
*/
export function isDisposable(thing) {
return typeof thing === 'object' && thing !== null && typeof thing.dispose === 'function' && thing.dispose.length === 0;
}
export function dispose(arg) {
if (Iterable.is(arg)) {
const errors = [];
for (const d of arg) {
if (d) {
try {
d.dispose();
}
catch (e) {
errors.push(e);
}
}
}
if (errors.length === 1) {
throw errors[0];
}
else if (errors.length > 1) {
throw new AggregateError(errors, 'Encountered errors while disposing of store');
}
return Array.isArray(arg) ? [] : arg;
}
else if (arg) {
arg.dispose();
return arg;
}
}
/**
* Combine multiple disposable values into a single {@link IDisposable}.
*/
export function combinedDisposable(...disposables) {
const parent = toDisposable(() => dispose(disposables));
setParentOfDisposables(disposables, parent);
return parent;
}
/**
* Turn a function that implements dispose into an {@link IDisposable}.
*
* @param fn Clean up function, guaranteed to be called only **once**.
*/
export function toDisposable(fn) {
const self = trackDisposable({
dispose: createSingleCallFunction(() => {
markAsDisposed(self);
fn();
})
});
return self;
}
/**
* Manages a collection of disposable values.
*
* This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an
* `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a
* store that has already been disposed of.
*/
export class DisposableStore {
static { this.DISABLE_DISPOSED_WARNING = false; }
constructor() {
this._toDispose = new Set();
this._isDisposed = false;
trackDisposable(this);
}
/**
* Dispose of all registered disposables and mark this object as disposed.
*
* Any future disposables added to this object will be disposed of on `add`.
*/
dispose() {
if (this._isDisposed) {
return;
}
markAsDisposed(this);
this._isDisposed = true;
this.clear();
}
/**
* @return `true` if this object has been disposed of.
*/
get isDisposed() {
return this._isDisposed;
}
/**
* Dispose of all registered disposables but do not mark this object as disposed.
*/
clear() {
if (this._toDispose.size === 0) {
return;
}
try {
dispose(this._toDispose);
}
finally {
this._toDispose.clear();
}
}
/**
* Add a new {@link IDisposable disposable} to the collection.
*/
add(o) {
if (!o) {
return o;
}
if (o === this) {
throw new Error('Cannot register a disposable on itself!');
}
setParentOfDisposable(o, this);
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(o);
}
return o;
}
/**
* Deletes the value from the store, but does not dispose it.
*/
deleteAndLeak(o) {
if (!o) {
return;
}
if (this._toDispose.has(o)) {
this._toDispose.delete(o);
setParentOfDisposable(o, null);
}
}
}
/**
* Abstract base class for a {@link IDisposable disposable} object.
*
* Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of.
*/
export class Disposable {
/**
* A disposable that does nothing when it is disposed of.
*
* TODO: This should not be a static property.
*/
static { this.None = Object.freeze({ dispose() { } }); }
constructor() {
this._store = new DisposableStore();
trackDisposable(this);
setParentOfDisposable(this._store, this);
}
dispose() {
markAsDisposed(this);
this._store.dispose();
}
/**
* Adds `o` to the collection of disposables managed by this object.
*/
_register(o) {
if (o === this) {
throw new Error('Cannot register a disposable on itself!');
}
return this._store.add(o);
}
}
/**
* 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 {
constructor() {
this._isDisposed = false;
trackDisposable(this);
}
get value() {
return this._isDisposed ? undefined : this._value;
}
set value(value) {
if (this._isDisposed || value === this._value) {
return;
}
this._value?.dispose();
if (value) {
setParentOfDisposable(value, this);
}
this._value = value;
}
/**
* Resets the stored value and disposed of the previously stored value.
*/
clear() {
this.value = undefined;
}
dispose() {
this._isDisposed = true;
markAsDisposed(this);
this._value?.dispose();
this._value = undefined;
}
}
export class RefCountedDisposable {
constructor(_disposable) {
this._disposable = _disposable;
this._counter = 1;
}
acquire() {
this._counter++;
return this;
}
release() {
if (--this._counter === 0) {
this._disposable.dispose();
}
return this;
}
}
export class ImmortalReference {
constructor(object) {
this.object = object;
}
dispose() { }
}
/**
* A map the manages the lifecycle of the values that it stores.
*/
export class DisposableMap {
constructor() {
this._store = new Map();
this._isDisposed = false;
trackDisposable(this);
}
/**
* Disposes of all stored values and mark this object as disposed.
*
* Trying to use this object after it has been disposed of is an error.
*/
dispose() {
markAsDisposed(this);
this._isDisposed = true;
this.clearAndDisposeAll();
}
/**
* Disposes of all stored values and clear the map, but DO NOT mark this object as disposed.
*/
clearAndDisposeAll() {
if (!this._store.size) {
return;
}
try {
dispose(this._store.values());
}
finally {
this._store.clear();
}
}
get(key) {
return this._store.get(key);
}
set(key, value, skipDisposeOnOverwrite = false) {
if (this._isDisposed) {
console.warn(new Error('Trying to add a disposable to a DisposableMap that has already been disposed of. The added object will be leaked!').stack);
}
if (!skipDisposeOnOverwrite) {
this._store.get(key)?.dispose();
}
this._store.set(key, value);
}
/**
* Delete the value stored for `key` from this map and also dispose of it.
*/
deleteAndDispose(key) {
this._store.get(key)?.dispose();
this._store.delete(key);
}
[Symbol.iterator]() {
return this._store[Symbol.iterator]();
}
}
@@ -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.
*--------------------------------------------------------------------------------------------*/
class Node {
static { this.Undefined = new Node(undefined); }
constructor(element) {
this.element = element;
this.next = Node.Undefined;
this.prev = Node.Undefined;
}
}
export class LinkedList {
constructor() {
this._first = Node.Undefined;
this._last = Node.Undefined;
this._size = 0;
}
get size() {
return this._size;
}
isEmpty() {
return this._first === Node.Undefined;
}
clear() {
let node = this._first;
while (node !== Node.Undefined) {
const next = node.next;
node.prev = Node.Undefined;
node.next = Node.Undefined;
node = next;
}
this._first = Node.Undefined;
this._last = Node.Undefined;
this._size = 0;
}
unshift(element) {
return this._insert(element, false);
}
push(element) {
return this._insert(element, true);
}
_insert(element, atTheEnd) {
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() {
if (this._first === Node.Undefined) {
return undefined;
}
else {
const res = this._first.element;
this._remove(this._first);
return res;
}
}
pop() {
if (this._last === Node.Undefined) {
return undefined;
}
else {
const res = this._last.element;
this._remove(this._last);
return res;
}
}
_remove(node) {
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]() {
let node = this._first;
while (node !== Node.Undefined) {
yield node.element;
node = node.next;
}
}
}
@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { memoize } from './decorators.js';
export class LinkedText {
constructor(nodes) {
this.nodes = nodes;
}
toString() {
return this.nodes.map(node => typeof node === 'string' ? node : node.label).join('');
}
}
__decorate([
memoize
], LinkedText.prototype, "toString", null);
const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:|file:)[^\)\s]+)(?: (["'])(.+?)(\3))?\)/gi;
export function parseLinkedText(text) {
const result = [];
let index = 0;
let match;
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);
}
+573
View File
@@ -0,0 +1,573 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var _a, _b;
class ResourceMapEntry {
constructor(uri, value) {
this.uri = uri;
this.value = value;
}
}
function isEntries(arg) {
return Array.isArray(arg);
}
export class ResourceMap {
static { this.defaultToKey = (resource) => resource.toString(); }
constructor(arg, toKey) {
this[_a] = 'ResourceMap';
if (arg instanceof ResourceMap) {
this.map = new Map(arg.map);
this.toKey = toKey ?? ResourceMap.defaultToKey;
}
else if (isEntries(arg)) {
this.map = new Map();
this.toKey = toKey ?? ResourceMap.defaultToKey;
for (const [resource, value] of arg) {
this.set(resource, value);
}
}
else {
this.map = new Map();
this.toKey = arg ?? ResourceMap.defaultToKey;
}
}
set(resource, value) {
this.map.set(this.toKey(resource), new ResourceMapEntry(resource, value));
return this;
}
get(resource) {
return this.map.get(this.toKey(resource))?.value;
}
has(resource) {
return this.map.has(this.toKey(resource));
}
get size() {
return this.map.size;
}
clear() {
this.map.clear();
}
delete(resource) {
return this.map.delete(this.toKey(resource));
}
forEach(clb, thisArg) {
if (typeof thisArg !== 'undefined') {
clb = clb.bind(thisArg);
}
for (const [_, entry] of this.map) {
clb(entry.value, entry.uri, this);
}
}
*values() {
for (const entry of this.map.values()) {
yield entry.value;
}
}
*keys() {
for (const entry of this.map.values()) {
yield entry.uri;
}
}
*entries() {
for (const entry of this.map.values()) {
yield [entry.uri, entry.value];
}
}
*[(_a = Symbol.toStringTag, Symbol.iterator)]() {
for (const [, entry] of this.map) {
yield [entry.uri, entry.value];
}
}
}
export class LinkedMap {
constructor() {
this[_b] = 'LinkedMap';
this._map = new Map();
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state = 0;
}
clear() {
this._map.clear();
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state++;
}
isEmpty() {
return !this._head && !this._tail;
}
get size() {
return this._size;
}
get first() {
return this._head?.value;
}
get last() {
return this._tail?.value;
}
has(key) {
return this._map.has(key);
}
get(key, touch = 0 /* Touch.None */) {
const item = this._map.get(key);
if (!item) {
return undefined;
}
if (touch !== 0 /* Touch.None */) {
this.touch(item, touch);
}
return item.value;
}
set(key, value, touch = 0 /* Touch.None */) {
let item = this._map.get(key);
if (item) {
item.value = value;
if (touch !== 0 /* Touch.None */) {
this.touch(item, touch);
}
}
else {
item = { key, value, next: undefined, previous: undefined };
switch (touch) {
case 0 /* Touch.None */:
this.addItemLast(item);
break;
case 1 /* Touch.AsOld */:
this.addItemFirst(item);
break;
case 2 /* Touch.AsNew */:
this.addItemLast(item);
break;
default:
this.addItemLast(item);
break;
}
this._map.set(key, item);
this._size++;
}
return this;
}
delete(key) {
return !!this.remove(key);
}
remove(key) {
const item = this._map.get(key);
if (!item) {
return undefined;
}
this._map.delete(key);
this.removeItem(item);
this._size--;
return item.value;
}
shift() {
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, thisArg) {
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() {
const map = this;
const state = this._state;
let current = this._head;
const iterator = {
[Symbol.iterator]() {
return iterator;
},
next() {
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() {
const map = this;
const state = this._state;
let current = this._head;
const iterator = {
[Symbol.iterator]() {
return iterator;
},
next() {
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() {
const map = this;
const state = this._state;
let current = this._head;
const iterator = {
[Symbol.iterator]() {
return iterator;
},
next() {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result = { value: [current.key, current.value], done: false };
current = current.next;
return result;
}
else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
[(_b = Symbol.toStringTag, Symbol.iterator)]() {
return this.entries();
}
trimOld(newSize) {
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++;
}
trimNew(newSize) {
if (newSize >= this.size) {
return;
}
if (newSize === 0) {
this.clear();
return;
}
let current = this._tail;
let currentSize = this.size;
while (current && currentSize > newSize) {
this._map.delete(current.key);
current = current.previous;
currentSize--;
}
this._tail = current;
this._size = currentSize;
if (current) {
current.next = undefined;
}
this._state++;
}
addItemFirst(item) {
// 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++;
}
addItemLast(item) {
// 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++;
}
removeItem(item) {
if (item === this._head && item === this._tail) {
this._head = undefined;
this._tail = undefined;
}
else if (item === this._head) {
// This can only happen if size === 1 which is handled
// 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 happen if size === 1 which is handled
// 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++;
}
touch(item, touch) {
if (!this._head || !this._tail) {
throw new Error('Invalid list');
}
if ((touch !== 1 /* Touch.AsOld */ && touch !== 2 /* Touch.AsNew */)) {
return;
}
if (touch === 1 /* 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 === 2 /* 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() {
const data = [];
this.forEach((value, key) => {
data.push([key, value]);
});
return data;
}
fromJSON(data) {
this.clear();
for (const [key, value] of data) {
this.set(key, value);
}
}
}
class Cache extends LinkedMap {
constructor(limit, ratio = 1) {
super();
this._limit = limit;
this._ratio = Math.min(Math.max(0, ratio), 1);
}
get limit() {
return this._limit;
}
set limit(limit) {
this._limit = limit;
this.checkTrim();
}
get(key, touch = 2 /* Touch.AsNew */) {
return super.get(key, touch);
}
peek(key) {
return super.get(key, 0 /* Touch.None */);
}
set(key, value) {
super.set(key, value, 2 /* Touch.AsNew */);
return this;
}
checkTrim() {
if (this.size > this._limit) {
this.trim(Math.round(this._limit * this._ratio));
}
}
}
export class LRUCache extends Cache {
constructor(limit, ratio = 1) {
super(limit, ratio);
}
trim(newSize) {
this.trimOld(newSize);
}
set(key, value) {
super.set(key, value);
this.checkTrim();
return this;
}
}
/**
* A map that allows access both by keys and values.
* **NOTE**: values need to be unique.
*/
export class BidirectionalMap {
constructor(entries) {
this._m1 = new Map();
this._m2 = new Map();
if (entries) {
for (const [key, value] of entries) {
this.set(key, value);
}
}
}
clear() {
this._m1.clear();
this._m2.clear();
}
set(key, value) {
this._m1.set(key, value);
this._m2.set(value, key);
}
get(key) {
return this._m1.get(key);
}
getKey(value) {
return this._m2.get(value);
}
delete(key) {
const value = this._m1.get(key);
if (value === undefined) {
return false;
}
this._m1.delete(key);
this._m2.delete(value);
return true;
}
keys() {
return this._m1.keys();
}
values() {
return this._m1.values();
}
}
export class SetMap {
constructor() {
this.map = new Map();
}
add(key, value) {
let values = this.map.get(key);
if (!values) {
values = new Set();
this.map.set(key, values);
}
values.add(value);
}
delete(key, value) {
const values = this.map.get(key);
if (!values) {
return;
}
values.delete(value);
if (values.size === 0) {
this.map.delete(key);
}
}
forEach(key, fn) {
const values = this.map.get(key);
if (!values) {
return;
}
values.forEach(fn);
}
get(key) {
const values = this.map.get(key);
if (!values) {
return new Set();
}
return values;
}
}
File diff suppressed because one or more lines are too long
@@ -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 { VSBuffer } from './buffer.js';
import { URI } from './uri.js';
export function stringify(obj) {
return JSON.stringify(obj, replacer);
}
export function parse(text) {
let data = JSON.parse(text);
data = revive(data);
return data;
}
function replacer(key, value) {
// URI is done via toJSON-member
if (value instanceof RegExp) {
return {
$mid: 2 /* MarshalledId.Regexp */,
source: value.source,
flags: value.flags,
};
}
return value;
}
export function revive(obj, depth = 0) {
if (!obj || depth > 200) {
return obj;
}
if (typeof obj === 'object') {
switch (obj.$mid) {
case 1 /* MarshalledId.Uri */: return URI.revive(obj);
case 2 /* MarshalledId.Regexp */: return new RegExp(obj.source, obj.flags);
case 17 /* MarshalledId.Date */: return new Date(obj.source);
}
if (obj instanceof VSBuffer
|| obj instanceof Uint8Array) {
return 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;
}
@@ -0,0 +1,5 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export {};
+8
View File
@@ -0,0 +1,8 @@
export const Mimes = Object.freeze({
text: 'text/plain',
binary: 'application/octet-stream',
unknown: 'application/unknown',
markdown: 'text/markdown',
latex: 'text/latex',
uriList: 'text/uri-list',
});
@@ -0,0 +1,319 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// allow-any-unicode-comment-file
/**
* Gets alternative Korean characters for the character code. This will return the ascii
* character code(s) that a Hangul character may have been input with using a qwerty layout.
*
* This only aims to cover modern (not archaic) Hangul syllables.
*
* @param code The character code to get alternate characters for
*/
export function getKoreanAltChars(code) {
const result = disassembleKorean(code);
if (result && result.length > 0) {
return new Uint32Array(result);
}
return undefined;
}
let codeBufferLength = 0;
const codeBuffer = new Uint32Array(10);
function disassembleKorean(code) {
codeBufferLength = 0;
// Initial consonants (초성)
getCodesFromArray(code, modernConsonants, 4352 /* HangulRangeStartCode.InitialConsonant */);
if (codeBufferLength > 0) {
return codeBuffer.subarray(0, codeBufferLength);
}
// Vowels (중성)
getCodesFromArray(code, modernVowels, 4449 /* HangulRangeStartCode.Vowel */);
if (codeBufferLength > 0) {
return codeBuffer.subarray(0, codeBufferLength);
}
// Final consonants (종성)
getCodesFromArray(code, modernFinalConsonants, 4520 /* HangulRangeStartCode.FinalConsonant */);
if (codeBufferLength > 0) {
return codeBuffer.subarray(0, codeBufferLength);
}
// Hangul Compatibility Jamo
getCodesFromArray(code, compatibilityJamo, 12593 /* HangulRangeStartCode.CompatibilityJamo */);
if (codeBufferLength) {
return codeBuffer.subarray(0, codeBufferLength);
}
// Hangul Syllables
if (code >= 0xAC00 && code <= 0xD7A3) {
const hangulIndex = code - 0xAC00;
const vowelAndFinalConsonantProduct = hangulIndex % 588;
// 0-based starting at 0x1100
const initialConsonantIndex = Math.floor(hangulIndex / 588);
// 0-based starting at 0x1161
const vowelIndex = Math.floor(vowelAndFinalConsonantProduct / 28);
// 0-based starting at 0x11A8
// Subtract 1 as the standard algorithm uses the 0 index to represent no
// final consonant
const finalConsonantIndex = vowelAndFinalConsonantProduct % 28 - 1;
if (initialConsonantIndex < modernConsonants.length) {
getCodesFromArray(initialConsonantIndex, modernConsonants, 0);
}
else if (4352 /* HangulRangeStartCode.InitialConsonant */ + initialConsonantIndex - 12593 /* HangulRangeStartCode.CompatibilityJamo */ < compatibilityJamo.length) {
getCodesFromArray(4352 /* HangulRangeStartCode.InitialConsonant */ + initialConsonantIndex, compatibilityJamo, 12593 /* HangulRangeStartCode.CompatibilityJamo */);
}
if (vowelIndex < modernVowels.length) {
getCodesFromArray(vowelIndex, modernVowels, 0);
}
else if (4449 /* HangulRangeStartCode.Vowel */ + vowelIndex - 12593 /* HangulRangeStartCode.CompatibilityJamo */ < compatibilityJamo.length) {
getCodesFromArray(4449 /* HangulRangeStartCode.Vowel */ + vowelIndex - 12593 /* HangulRangeStartCode.CompatibilityJamo */, compatibilityJamo, 12593 /* HangulRangeStartCode.CompatibilityJamo */);
}
if (finalConsonantIndex >= 0) {
if (finalConsonantIndex < modernFinalConsonants.length) {
getCodesFromArray(finalConsonantIndex, modernFinalConsonants, 0);
}
else if (4520 /* HangulRangeStartCode.FinalConsonant */ + finalConsonantIndex - 12593 /* HangulRangeStartCode.CompatibilityJamo */ < compatibilityJamo.length) {
getCodesFromArray(4520 /* HangulRangeStartCode.FinalConsonant */ + finalConsonantIndex - 12593 /* HangulRangeStartCode.CompatibilityJamo */, compatibilityJamo, 12593 /* HangulRangeStartCode.CompatibilityJamo */);
}
}
if (codeBufferLength > 0) {
return codeBuffer.subarray(0, codeBufferLength);
}
}
return undefined;
}
function getCodesFromArray(code, array, arrayStartIndex) {
// Verify the code is within the array's range
if (code >= arrayStartIndex && code < arrayStartIndex + array.length) {
addCodesToBuffer(array[code - arrayStartIndex]);
}
}
function addCodesToBuffer(codes) {
// NUL is ignored, this is used for archaic characters to avoid using a Map
// for the data
if (codes === 0 /* AsciiCode.NUL */) {
return;
}
// Number stored in format: OptionalThirdCode << 16 | OptionalSecondCode << 8 | Code
codeBuffer[codeBufferLength++] = codes & 0xFF;
if (codes >> 8) {
codeBuffer[codeBufferLength++] = (codes >> 8) & 0xFF;
}
if (codes >> 16) {
codeBuffer[codeBufferLength++] = (codes >> 16) & 0xFF;
}
}
/**
* Hangul Jamo - Modern consonants #1
*
* Range U+1100..U+1112
*
* | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
* |--------|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
* | U+110x | ᄀ | ᄁ | ᄂ | ᄃ | ᄄ | ᄅ | ᄆ | ᄇ | ᄈ | ᄉ | ᄊ | ᄋ | ᄌ | ᄍ | ᄎ | ᄏ |
* | U+111x | ᄐ | ᄑ | ᄒ |
*/
const modernConsonants = new Uint8Array([
114 /* AsciiCode.r */, // ㄱ
82 /* AsciiCode.R */, // ㄲ
115 /* AsciiCode.s */, // ㄴ
101 /* AsciiCode.e */, // ㄷ
69 /* AsciiCode.E */, // ㄸ
102 /* AsciiCode.f */, // ㄹ
97 /* AsciiCode.a */, // ㅁ
113 /* AsciiCode.q */, // ㅂ
81 /* AsciiCode.Q */, // ㅃ
116 /* AsciiCode.t */, // ㅅ
84 /* AsciiCode.T */, // ㅆ
100 /* AsciiCode.d */, // ㅇ
119 /* AsciiCode.w */, // ㅈ
87 /* AsciiCode.W */, // ㅉ
99 /* AsciiCode.c */, // ㅊ
122 /* AsciiCode.z */, // ㅋ
120 /* AsciiCode.x */, // ㅌ
118 /* AsciiCode.v */, // ㅍ
103 /* AsciiCode.g */, // ㅎ
]);
/**
* Hangul Jamo - Modern Vowels
*
* Range U+1161..U+1175
*
* | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
* |--------|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
* | U+116x | | ᅡ | ᅢ | ᅣ | ᅤ | ᅥ | ᅦ | ᅧ | ᅨ | ᅩ | ᅪ | ᅫ | ᅬ | ᅭ | ᅮ | ᅯ |
* | U+117x | ᅰ | ᅱ | ᅲ | ᅳ | ᅴ | ᅵ |
*/
const modernVowels = new Uint16Array([
107 /* AsciiCode.k */, // -> ㅏ
111 /* AsciiCode.o */, // -> ㅐ
105 /* AsciiCode.i */, // -> ㅑ
79 /* AsciiCode.O */, // -> ㅒ
106 /* AsciiCode.j */, // -> ㅓ
112 /* AsciiCode.p */, // -> ㅔ
117 /* AsciiCode.u */, // -> ㅕ
80 /* AsciiCode.P */, // -> ㅖ
104 /* AsciiCode.h */, // -> ㅗ
27496 /* AsciiCodeCombo.hk */, // -> ㅘ
28520 /* AsciiCodeCombo.ho */, // -> ㅙ
27752 /* AsciiCodeCombo.hl */, // -> ㅚ
121 /* AsciiCode.y */, // -> ㅛ
110 /* AsciiCode.n */, // -> ㅜ
27246 /* AsciiCodeCombo.nj */, // -> ㅝ
28782 /* AsciiCodeCombo.np */, // -> ㅞ
27758 /* AsciiCodeCombo.nl */, // -> ㅟ
98 /* AsciiCode.b */, // -> ㅠ
109 /* AsciiCode.m */, // -> ㅡ
27757 /* AsciiCodeCombo.ml */, // -> ㅢ
108 /* AsciiCode.l */, // -> ㅣ
]);
/**
* Hangul Jamo - Modern Consonants #2
*
* Range U+11A8..U+11C2
*
* | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
* |--------|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
* | U+11Ax | | | | | | | | | ᆨ | ᆩ | ᆪ | ᆫ | ᆬ | ᆭ | ᆮ | ᆯ |
* | U+11Bx | ᆰ | ᆱ | ᆲ | ᆳ | ᆴ | ᆵ | ᆶ | ᆷ | ᆸ | ᆹ | ᆺ | ᆻ | ᆼ | ᆽ | ᆾ | ᆿ |
* | U+11Cx | ᇀ | ᇁ | ᇂ |
*/
const modernFinalConsonants = new Uint16Array([
114 /* AsciiCode.r */, // ㄱ
82 /* AsciiCode.R */, // ㄲ
29810 /* AsciiCodeCombo.rt */, // ㄳ
115 /* AsciiCode.s */, // ㄴ
30579 /* AsciiCodeCombo.sw */, // ㄵ
26483 /* AsciiCodeCombo.sg */, // ㄶ
101 /* AsciiCode.e */, // ㄷ
102 /* AsciiCode.f */, // ㄹ
29286 /* AsciiCodeCombo.fr */, // ㄺ
24934 /* AsciiCodeCombo.fa */, // ㄻ
29030 /* AsciiCodeCombo.fq */, // ㄼ
29798 /* AsciiCodeCombo.ft */, // ㄽ
30822 /* AsciiCodeCombo.fx */, // ㄾ
30310 /* AsciiCodeCombo.fv */, // ㄿ
26470 /* AsciiCodeCombo.fg */, // ㅀ
97 /* AsciiCode.a */, // ㅁ
113 /* AsciiCode.q */, // ㅂ
29809 /* AsciiCodeCombo.qt */, // ㅄ
116 /* AsciiCode.t */, // ㅅ
84 /* AsciiCode.T */, // ㅆ
100 /* AsciiCode.d */, // ㅇ
119 /* AsciiCode.w */, // ㅈ
99 /* AsciiCode.c */, // ㅊ
122 /* AsciiCode.z */, // ㅋ
120 /* AsciiCode.x */, // ㅌ
118 /* AsciiCode.v */, // ㅍ
103 /* AsciiCode.g */, // ㅎ
]);
/**
* Hangul Compatibility Jamo
*
* Range U+3131..U+318F
*
* This includes range includes archaic jamo which we don't consider, these are
* given the NUL character code in order to be ignored.
*
* | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
* |--------|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
* | U+313x | | ㄱ | ㄲ | ㄳ | ㄴ | ㄵ | ㄶ | ㄷ | ㄸ | ㄹ | ㄺ | ㄻ | ㄼ | ㄽ | ㄾ | ㄿ |
* | U+314x | ㅀ | ㅁ | ㅂ | ㅃ | ㅄ | ㅅ | ㅆ | ㅇ | ㅈ | ㅉ | ㅊ | ㅋ | ㅌ | ㅍ | ㅎ | ㅏ |
* | U+315x | ㅐ | ㅑ | ㅒ | ㅓ | ㅔ | ㅕ | ㅖ | ㅗ | ㅘ | ㅙ | ㅚ | ㅛ | ㅜ | ㅝ | ㅞ | ㅟ |
* | U+316x | ㅠ | ㅡ | ㅢ | ㅣ | HF | ㅥ | ㅦ | ㅧ | ㅨ | ㅩ | ㅪ | ㅫ | ㅬ | ㅭ | ㅮ | ㅯ |
* | U+317x | ㅰ | ㅱ | ㅲ | ㅳ | ㅴ | ㅵ | ㅶ | ㅷ | ㅸ | ㅹ | ㅺ | ㅻ | ㅼ | ㅽ | ㅾ | ㅿ |
* | U+318x | ㆀ | ㆁ | ㆂ | ㆃ | ㆄ | ㆅ | ㆆ | ㆇ | ㆈ | ㆉ | ㆊ | ㆋ | ㆌ | ㆍ | ㆎ |
*/
const compatibilityJamo = new Uint16Array([
114 /* AsciiCode.r */, // ㄱ
82 /* AsciiCode.R */, // ㄲ
29810 /* AsciiCodeCombo.rt */, // ㄳ
115 /* AsciiCode.s */, // ㄴ
30579 /* AsciiCodeCombo.sw */, // ㄵ
26483 /* AsciiCodeCombo.sg */, // ㄶ
101 /* AsciiCode.e */, // ㄷ
69 /* AsciiCode.E */, // ㄸ
102 /* AsciiCode.f */, // ㄹ
29286 /* AsciiCodeCombo.fr */, // ㄺ
24934 /* AsciiCodeCombo.fa */, // ㄻ
29030 /* AsciiCodeCombo.fq */, // ㄼ
29798 /* AsciiCodeCombo.ft */, // ㄽ
30822 /* AsciiCodeCombo.fx */, // ㄾ
30310 /* AsciiCodeCombo.fv */, // ㄿ
26470 /* AsciiCodeCombo.fg */, // ㅀ
97 /* AsciiCode.a */, // ㅁ
113 /* AsciiCode.q */, // ㅂ
81 /* AsciiCode.Q */, // ㅃ
29809 /* AsciiCodeCombo.qt */, // ㅄ
116 /* AsciiCode.t */, // ㅅ
84 /* AsciiCode.T */, // ㅆ
100 /* AsciiCode.d */, // ㅇ
119 /* AsciiCode.w */, // ㅈ
87 /* AsciiCode.W */, // ㅉ
99 /* AsciiCode.c */, // ㅊ
122 /* AsciiCode.z */, // ㅋ
120 /* AsciiCode.x */, // ㅌ
118 /* AsciiCode.v */, // ㅍ
103 /* AsciiCode.g */, // ㅎ
107 /* AsciiCode.k */, // ㅏ
111 /* AsciiCode.o */, // ㅐ
105 /* AsciiCode.i */, // ㅑ
79 /* AsciiCode.O */, // ㅒ
106 /* AsciiCode.j */, // ㅓ
112 /* AsciiCode.p */, // ㅔ
117 /* AsciiCode.u */, // ㅕ
80 /* AsciiCode.P */, // ㅖ
104 /* AsciiCode.h */, // ㅗ
27496 /* AsciiCodeCombo.hk */, // ㅘ
28520 /* AsciiCodeCombo.ho */, // ㅙ
27752 /* AsciiCodeCombo.hl */, // ㅚ
121 /* AsciiCode.y */, // ㅛ
110 /* AsciiCode.n */, // ㅜ
27246 /* AsciiCodeCombo.nj */, // ㅝ
28782 /* AsciiCodeCombo.np */, // ㅞ
27758 /* AsciiCodeCombo.nl */, // ㅟ
98 /* AsciiCode.b */, // ㅠ
109 /* AsciiCode.m */, // ㅡ
27757 /* AsciiCodeCombo.ml */, // ㅢ
108 /* AsciiCode.l */, // ㅣ
// HF: Hangul Filler (everything after this is archaic)
// ㅥ
// ㅦ
// ㅧ
// ㅨ
// ㅩ
// ㅪ
// ㅫ
// ㅬ
// ㅮ
// ㅯ
// ㅰ
// ㅱ
// ㅲ
// ㅳ
// ㅴ
// ㅵ
// ㅶ
// ㅷ
// ㅸ
// ㅹ
// ㅺ
// ㅻ
// ㅼ
// ㅽ
// ㅾ
// ㅿ
// ㆀ
// ㆁ
// ㆂ
// ㆃ
// ㆄ
// ㆅ
// ㆆ
// ㆇ
// ㆈ
// ㆉ
// ㆊ
// ㆋ
// ㆌ
// ㆍ
// ㆎ
]);
@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class ArrayNavigator {
constructor(items, start = 0, end = items.length, index = start - 1) {
this.items = items;
this.start = start;
this.end = end;
this.index = index;
}
current() {
if (this.index === this.start - 1 || this.index === this.end) {
return null;
}
return this.items[this.index];
}
next() {
this.index = Math.min(this.index + 1, this.end);
return this.current();
}
previous() {
this.index = Math.max(this.index - 1, this.start - 1);
return this.current();
}
first() {
this.index = this.start;
return this.current();
}
last() {
this.index = this.end - 1;
return this.current();
}
}
+243
View File
@@ -0,0 +1,243 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as errors from './errors.js';
import * as platform from './platform.js';
import { equalsIgnoreCase, startsWithIgnoreCase } from './strings.js';
import { URI } from './uri.js';
import * as paths from './path.js';
export var Schemas;
(function (Schemas) {
/**
* A schema that is used for models that exist in memory
* only and that have no correspondence on a server or such.
*/
Schemas.inMemory = 'inmemory';
/**
* A schema that is used for setting files
*/
Schemas.vscode = 'vscode';
/**
* A schema that is used for internal private files
*/
Schemas.internal = 'private';
/**
* A walk-through document.
*/
Schemas.walkThrough = 'walkThrough';
/**
* An embedded code snippet.
*/
Schemas.walkThroughSnippet = 'walkThroughSnippet';
Schemas.http = 'http';
Schemas.https = 'https';
Schemas.file = 'file';
Schemas.mailto = 'mailto';
Schemas.untitled = 'untitled';
Schemas.data = 'data';
Schemas.command = 'command';
Schemas.vscodeRemote = 'vscode-remote';
Schemas.vscodeRemoteResource = 'vscode-remote-resource';
Schemas.vscodeManagedRemoteResource = 'vscode-managed-remote-resource';
Schemas.vscodeUserData = 'vscode-userdata';
Schemas.vscodeCustomEditor = 'vscode-custom-editor';
Schemas.vscodeNotebookCell = 'vscode-notebook-cell';
Schemas.vscodeNotebookCellMetadata = 'vscode-notebook-cell-metadata';
Schemas.vscodeNotebookCellOutput = 'vscode-notebook-cell-output';
Schemas.vscodeInteractiveInput = 'vscode-interactive-input';
Schemas.vscodeSettings = 'vscode-settings';
Schemas.vscodeWorkspaceTrust = 'vscode-workspace-trust';
Schemas.vscodeTerminal = 'vscode-terminal';
/** Scheme used for code blocks in chat. */
Schemas.vscodeChatCodeBlock = 'vscode-chat-code-block';
/**
* Scheme used for backing documents created by copilot for chat.
*/
Schemas.vscodeCopilotBackingChatCodeBlock = 'vscode-copilot-chat-code-block';
/** Scheme used for LHS of code compare (aka diff) blocks in chat. */
Schemas.vscodeChatCodeCompareBlock = 'vscode-chat-code-compare-block';
/** Scheme used for the chat input editor. */
Schemas.vscodeChatSesssion = 'vscode-chat-editor';
/**
* Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors)
*/
Schemas.webviewPanel = 'webview-panel';
/**
* Scheme used for loading the wrapper html and script in webviews.
*/
Schemas.vscodeWebview = 'vscode-webview';
/**
* Scheme used for extension pages
*/
Schemas.extension = 'extension';
/**
* Scheme used as a replacement of `file` scheme to load
* files with our custom protocol handler (desktop only).
*/
Schemas.vscodeFileResource = 'vscode-file';
/**
* Scheme used for temporary resources
*/
Schemas.tmp = 'tmp';
/**
* Scheme used vs live share
*/
Schemas.vsls = 'vsls';
/**
* Scheme used for the Source Control commit input's text document
*/
Schemas.vscodeSourceControl = 'vscode-scm';
/**
* Scheme used for input box for creating comments.
*/
Schemas.commentsInput = 'comment';
/**
* Scheme used for special rendering of settings in the release notes
*/
Schemas.codeSetting = 'code-setting';
})(Schemas || (Schemas = {}));
export function matchesScheme(target, scheme) {
if (URI.isUri(target)) {
return equalsIgnoreCase(target.scheme, scheme);
}
else {
return startsWithIgnoreCase(target, scheme + ':');
}
}
export function matchesSomeScheme(target, ...schemes) {
return schemes.some(scheme => matchesScheme(target, scheme));
}
export const connectionTokenQueryName = 'tkn';
class RemoteAuthoritiesImpl {
constructor() {
this._hosts = Object.create(null);
this._ports = Object.create(null);
this._connectionTokens = Object.create(null);
this._preferredWebSchema = 'http';
this._delegate = null;
this._serverRootPath = '/';
}
setPreferredWebSchema(schema) {
this._preferredWebSchema = schema;
}
get _remoteResourcesPath() {
return paths.posix.join(this._serverRootPath, Schemas.vscodeRemoteResource);
}
rewrite(uri) {
if (this._delegate) {
try {
return this._delegate(uri);
}
catch (err) {
errors.onUnexpectedError(err);
return uri;
}
}
const authority = uri.authority;
let host = this._hosts[authority];
if (host && host.indexOf(':') !== -1 && 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 += `&${connectionTokenQueryName}=${encodeURIComponent(connectionToken)}`;
}
return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
authority: `${host}:${port}`,
path: this._remoteResourcesPath,
query
});
}
}
export const RemoteAuthorities = new RemoteAuthoritiesImpl();
export const VSCODE_AUTHORITY = 'vscode-app';
class FileAccessImpl {
static { this.FALLBACK_AUTHORITY = VSCODE_AUTHORITY; }
/**
* 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.
*/
uriToBrowserUri(uri) {
// Handle remote URIs via `RemoteAuthorities`
if (uri.scheme === Schemas.vscodeRemote) {
return RemoteAuthorities.rewrite(uri);
}
// Convert to `vscode-file` resource..
if (
// ...only ever for `file` resources
uri.scheme === Schemas.file &&
(
// ...and we run in native environments
platform.isNative ||
// ...or web worker extensions on desktop
(platform.webWorkerOrigin === `${Schemas.vscodeFileResource}://${FileAccessImpl.FALLBACK_AUTHORITY}`))) {
return uri.with({
scheme: Schemas.vscodeFileResource,
// We need to provide an authority here so that it can serve
// as origin for network and loading matters in chromium.
// If the URI is not coming with an authority already, we
// add our own
authority: uri.authority || FileAccessImpl.FALLBACK_AUTHORITY,
query: null,
fragment: null
});
}
return uri;
}
}
export const FileAccess = new FileAccessImpl();
export var COI;
(function (COI) {
const coiHeaders = new Map([
['1', { 'Cross-Origin-Opener-Policy': 'same-origin' }],
['2', { 'Cross-Origin-Embedder-Policy': 'require-corp' }],
['3', { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' }],
]);
COI.CoopAndCoep = Object.freeze(coiHeaders.get('3'));
const coiSearchParamName = 'vscode-coi';
/**
* Extract desired headers from `vscode-coi` invocation
*/
function getHeadersFromQuery(url) {
let params;
if (typeof url === 'string') {
params = new URL(url).searchParams;
}
else if (url instanceof URL) {
params = url.searchParams;
}
else if (URI.isUri(url)) {
params = new URL(url.toString(true)).searchParams;
}
const value = params?.get(coiSearchParamName);
if (!value) {
return undefined;
}
return coiHeaders.get(value);
}
COI.getHeadersFromQuery = getHeadersFromQuery;
/**
* Add the `vscode-coi` query attribute based on wanting `COOP` and `COEP`. Will be a noop when `crossOriginIsolated`
* isn't enabled the current context
*/
function addSearchParam(urlOrSearch, coop, coep) {
if (!globalThis.crossOriginIsolated) {
// depends on the current context being COI
return;
}
const value = coop && coep ? '3' : coep ? '2' : '1';
if (urlOrSearch instanceof URLSearchParams) {
urlOrSearch.set(coiSearchParamName, value);
}
else {
urlOrSearch[coiSearchParamName] = value;
}
}
COI.addSearchParam = addSearchParam;
})(COI || (COI = {}));
+47
View File
@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* 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, min, max) {
return Math.min(Math.max(value, min), max);
}
export class MovingAverage {
constructor() {
this._n = 1;
this._val = 0;
}
update(value) {
this._val = this._val + (value - this._val) / this._n;
this._n += 1;
return this._val;
}
get value() {
return this._val;
}
}
export class SlidingWindowAverage {
constructor(size) {
this._n = 0;
this._val = 0;
this._values = [];
this._index = 0;
this._sum = 0;
this._values = new Array(size);
this._values.fill(0, 0, size);
}
update(value) {
const oldValue = this._values[this._index];
this._values[this._index] = value;
this._index = (this._index + 1) % this._values.length;
this._sum -= oldValue;
this._sum += value;
if (this._n < this._values.length) {
this._n += 1;
}
this._val = this._sum / this._n;
return this._val;
}
get value() {
return this._val;
}
}
+179
View File
@@ -0,0 +1,179 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isTypedArray, isObject, isUndefinedOrNull } from './types.js';
export function deepClone(obj) {
if (!obj || typeof obj !== 'object') {
return obj;
}
if (obj instanceof RegExp) {
return obj;
}
const result = Array.isArray(obj) ? [] : {};
Object.entries(obj).forEach(([key, value]) => {
result[key] = value && typeof value === 'object' ? deepClone(value) : value;
});
return result;
}
export function deepFreeze(obj) {
if (!obj || typeof obj !== 'object') {
return obj;
}
const stack = [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) && !isTypedArray(prop)) {
stack.push(prop);
}
}
}
}
return obj;
}
const _hasOwnProperty = Object.prototype.hasOwnProperty;
export function cloneAndChange(obj, changer) {
return _cloneAndChange(obj, changer, new Set());
}
function _cloneAndChange(obj, changer, seen) {
if (isUndefinedOrNull(obj)) {
return obj;
}
const changed = changer(obj);
if (typeof changed !== 'undefined') {
return changed;
}
if (Array.isArray(obj)) {
const r1 = [];
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 (const i2 in obj) {
if (_hasOwnProperty.call(obj, i2)) {
r2[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, source, overwrite = true) {
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, other) {
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;
let key;
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 = [];
for (key in one) {
oneKeys.push(key);
}
oneKeys.sort();
const otherKeys = [];
for (key in other) {
otherKeys.push(key);
}
otherKeys.sort();
if (!equals(oneKeys, otherKeys)) {
return false;
}
for (i = 0; i < oneKeys.length; i++) {
if (!equals(one[oneKeys[i]], other[oneKeys[i]])) {
return false;
}
}
}
return true;
}
export function getAllPropertyNames(obj) {
let res = [];
while (Object.prototype !== obj) {
res = res.concat(Object.getOwnPropertyNames(obj));
obj = Object.getPrototypeOf(obj);
}
return res;
}
export function getAllMethodNames(obj) {
const methods = [];
for (const prop of getAllPropertyNames(obj)) {
if (typeof obj[prop] === 'function') {
methods.push(prop);
}
}
return methods;
}
export function createProxyObject(methodNames, invoke) {
const createProxyMethod = (method) => {
return function () {
const args = Array.prototype.slice.call(arguments, 0);
return invoke(method, args);
};
};
const result = {};
for (const methodName of methodNames) {
result[methodName] = createProxyMethod(methodName);
}
return result;
}
@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export { observableValue, disposableObservableValue, transaction, subtransaction } from './observableInternal/base.js';
export { derived, derivedOpts, derivedHandleChanges, derivedWithStore } from './observableInternal/derived.js';
export { autorun, autorunHandleChanges, autorunWithStore, autorunOpts, autorunWithStoreHandleChanges } from './observableInternal/autorun.js';
export { constObservable, derivedObservableWithCache, derivedObservableWithWritableCache, keepObserved, recomputeInitiallyAndOnChange, observableFromEvent, observableSignal, observableSignalFromEvent } from './observableInternal/utils.js';
export { ObservablePromise, PromiseResult, waitForState } from './observableInternal/promise.js';
export { observableValueOpts } from './observableInternal/api.js';
import { ConsoleObservableLogger, setLogger } from './observableInternal/logging.js';
// Remove "//" in the next line to enable logging
const enableLogging = false;
if (enableLogging) {
setLogger(new ConsoleObservableLogger());
}
@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { strictEquals } from '../equals.js';
import { ObservableValue } from './base.js';
import { DebugNameData } from './debugName.js';
import { LazyObservableValue } from './lazyObservableValue.js';
export function observableValueOpts(options, initialValue) {
if (options.lazy) {
return new LazyObservableValue(new DebugNameData(options.owner, options.debugName, undefined), initialValue, options.equalsFn ?? strictEquals);
}
return new ObservableValue(new DebugNameData(options.owner, options.debugName, undefined), initialValue, options.equalsFn ?? strictEquals);
}
@@ -0,0 +1,192 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { assertFn } from '../assert.js';
import { DisposableStore, markAsDisposed, toDisposable, trackDisposable } from '../lifecycle.js';
import { DebugNameData } from './debugName.js';
import { getLogger } from './logging.js';
/**
* Runs immediately and whenever a transaction ends and an observed observable changed.
* {@link fn} should start with a JS Doc using `@description` to name the autorun.
*/
export function autorun(fn) {
return new AutorunObserver(new DebugNameData(undefined, undefined, fn), fn, undefined, undefined);
}
/**
* Runs immediately and whenever a transaction ends and an observed observable changed.
* {@link fn} should start with a JS Doc using `@description` to name the autorun.
*/
export function autorunOpts(options, fn) {
return new AutorunObserver(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), fn, undefined, undefined);
}
/**
* Runs immediately and whenever a transaction ends and an observed observable changed.
* {@link fn} should start with a JS Doc using `@description` to name the autorun.
*
* Use `createEmptyChangeSummary` to create a "change summary" that can collect the changes.
* Use `handleChange` to add a reported change to the change summary.
* The run function is given the last change summary.
* The change summary is discarded after the run function was called.
*
* @see autorun
*/
export function autorunHandleChanges(options, fn) {
return new AutorunObserver(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), fn, options.createEmptyChangeSummary, options.handleChange);
}
/**
* @see autorunHandleChanges (but with a disposable store that is cleared before the next run or on dispose)
*/
export function autorunWithStoreHandleChanges(options, fn) {
const store = new DisposableStore();
const disposable = autorunHandleChanges({
owner: options.owner,
debugName: options.debugName,
debugReferenceFn: options.debugReferenceFn ?? fn,
createEmptyChangeSummary: options.createEmptyChangeSummary,
handleChange: options.handleChange,
}, (reader, changeSummary) => {
store.clear();
fn(reader, changeSummary, store);
});
return toDisposable(() => {
disposable.dispose();
store.dispose();
});
}
/**
* @see autorun (but with a disposable store that is cleared before the next run or on dispose)
*/
export function autorunWithStore(fn) {
const store = new DisposableStore();
const disposable = autorunOpts({
owner: undefined,
debugName: undefined,
debugReferenceFn: fn,
}, reader => {
store.clear();
fn(reader, store);
});
return toDisposable(() => {
disposable.dispose();
store.dispose();
});
}
export class AutorunObserver {
get debugName() {
return this._debugNameData.getDebugName(this) ?? '(anonymous)';
}
constructor(_debugNameData, _runFn, createChangeSummary, _handleChange) {
this._debugNameData = _debugNameData;
this._runFn = _runFn;
this.createChangeSummary = createChangeSummary;
this._handleChange = _handleChange;
this.state = 2 /* AutorunState.stale */;
this.updateCount = 0;
this.disposed = false;
this.dependencies = new Set();
this.dependenciesToBeRemoved = new Set();
this.changeSummary = this.createChangeSummary?.();
getLogger()?.handleAutorunCreated(this);
this._runIfNeeded();
trackDisposable(this);
}
dispose() {
this.disposed = true;
for (const o of this.dependencies) {
o.removeObserver(this);
}
this.dependencies.clear();
markAsDisposed(this);
}
_runIfNeeded() {
if (this.state === 3 /* AutorunState.upToDate */) {
return;
}
const emptySet = this.dependenciesToBeRemoved;
this.dependenciesToBeRemoved = this.dependencies;
this.dependencies = emptySet;
this.state = 3 /* AutorunState.upToDate */;
const isDisposed = this.disposed;
try {
if (!isDisposed) {
getLogger()?.handleAutorunTriggered(this);
const changeSummary = this.changeSummary;
this.changeSummary = this.createChangeSummary?.();
this._runFn(this, changeSummary);
}
}
finally {
if (!isDisposed) {
getLogger()?.handleAutorunFinished(this);
}
// We don't want our observed observables to think that they are (not even temporarily) not being observed.
// Thus, we only unsubscribe from observables that are definitely not read anymore.
for (const o of this.dependenciesToBeRemoved) {
o.removeObserver(this);
}
this.dependenciesToBeRemoved.clear();
}
}
toString() {
return `Autorun<${this.debugName}>`;
}
// IObserver implementation
beginUpdate() {
if (this.state === 3 /* AutorunState.upToDate */) {
this.state = 1 /* AutorunState.dependenciesMightHaveChanged */;
}
this.updateCount++;
}
endUpdate() {
if (this.updateCount === 1) {
do {
if (this.state === 1 /* AutorunState.dependenciesMightHaveChanged */) {
this.state = 3 /* AutorunState.upToDate */;
for (const d of this.dependencies) {
d.reportChanges();
if (this.state === 2 /* AutorunState.stale */) {
// The other dependencies will refresh on demand
break;
}
}
}
this._runIfNeeded();
} while (this.state !== 3 /* AutorunState.upToDate */);
}
this.updateCount--;
assertFn(() => this.updateCount >= 0);
}
handlePossibleChange(observable) {
if (this.state === 3 /* AutorunState.upToDate */ && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) {
this.state = 1 /* AutorunState.dependenciesMightHaveChanged */;
}
}
handleChange(observable, change) {
if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) {
const shouldReact = this._handleChange ? this._handleChange({
changedObservable: observable,
change,
didChange: (o) => o === observable,
}, this.changeSummary) : true;
if (shouldReact) {
this.state = 2 /* AutorunState.stale */;
}
}
}
// IReader implementation
readObservable(observable) {
// In case the run action disposes the autorun
if (this.disposed) {
return observable.get();
}
observable.addObserver(this);
const value = observable.get();
this.dependencies.add(observable);
this.dependenciesToBeRemoved.delete(observable);
return value;
}
}
(function (autorun) {
autorun.Observer = AutorunObserver;
})(autorun || (autorun = {}));
@@ -0,0 +1,268 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { strictEquals } from '../equals.js';
import { DebugNameData, getFunctionName } from './debugName.js';
import { getLogger } from './logging.js';
let _recomputeInitiallyAndOnChange;
export function _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange) {
_recomputeInitiallyAndOnChange = recomputeInitiallyAndOnChange;
}
let _keepObserved;
export function _setKeepObserved(keepObserved) {
_keepObserved = keepObserved;
}
let _derived;
/**
* @internal
* This is to allow splitting files.
*/
export function _setDerivedOpts(derived) {
_derived = derived;
}
export class ConvenientObservable {
get TChange() { return null; }
reportChanges() {
this.get();
}
/** @sealed */
read(reader) {
if (reader) {
return reader.readObservable(this);
}
else {
return this.get();
}
}
map(fnOrOwner, fnOrUndefined) {
const owner = fnOrUndefined === undefined ? undefined : fnOrOwner;
const fn = fnOrUndefined === undefined ? fnOrOwner : fnOrUndefined;
return _derived({
owner,
debugName: () => {
const name = getFunctionName(fn);
if (name !== undefined) {
return name;
}
// regexp to match `x => x.y` or `x => x?.y` where x and y can be arbitrary identifiers (uses backref):
const regexp = /^\s*\(?\s*([a-zA-Z_$][a-zA-Z_$0-9]*)\s*\)?\s*=>\s*\1(?:\??)\.([a-zA-Z_$][a-zA-Z_$0-9]*)\s*$/;
const match = regexp.exec(fn.toString());
if (match) {
return `${this.debugName}.${match[2]}`;
}
if (!owner) {
return `${this.debugName} (mapped)`;
}
return undefined;
},
debugReferenceFn: fn,
}, (reader) => fn(this.read(reader), reader));
}
/**
* @sealed
* Converts an observable of an observable value into a direct observable of the value.
*/
flatten() {
return _derived({
owner: undefined,
debugName: () => `${this.debugName} (flattened)`,
}, (reader) => this.read(reader).read(reader));
}
recomputeInitiallyAndOnChange(store, handleValue) {
store.add(_recomputeInitiallyAndOnChange(this, handleValue));
return this;
}
/**
* Ensures that this observable is observed. This keeps the cache alive.
* However, in case of deriveds, it does not force eager evaluation (only when the value is read/get).
* Use `recomputeInitiallyAndOnChange` for eager evaluation.
*/
keepObserved(store) {
store.add(_keepObserved(this));
return this;
}
}
export class BaseObservable extends ConvenientObservable {
constructor() {
super(...arguments);
this.observers = new Set();
}
addObserver(observer) {
const len = this.observers.size;
this.observers.add(observer);
if (len === 0) {
this.onFirstObserverAdded();
}
}
removeObserver(observer) {
const deleted = this.observers.delete(observer);
if (deleted && this.observers.size === 0) {
this.onLastObserverRemoved();
}
}
onFirstObserverAdded() { }
onLastObserverRemoved() { }
}
/**
* Starts a transaction in which many observables can be changed at once.
* {@link fn} should start with a JS Doc using `@description` to give the transaction a debug name.
* Reaction run on demand or when the transaction ends.
*/
export function transaction(fn, getDebugName) {
const tx = new TransactionImpl(fn, getDebugName);
try {
fn(tx);
}
finally {
tx.finish();
}
}
let _globalTransaction = undefined;
export function globalTransaction(fn) {
if (_globalTransaction) {
fn(_globalTransaction);
}
else {
const tx = new TransactionImpl(fn, undefined);
_globalTransaction = tx;
try {
fn(tx);
}
finally {
tx.finish(); // During finish, more actions might be added to the transaction.
// Which is why we only clear the global transaction after finish.
_globalTransaction = undefined;
}
}
}
export async function asyncTransaction(fn, getDebugName) {
const tx = new TransactionImpl(fn, getDebugName);
try {
await fn(tx);
}
finally {
tx.finish();
}
}
/**
* Allows to chain transactions.
*/
export function subtransaction(tx, fn, getDebugName) {
if (!tx) {
transaction(fn, getDebugName);
}
else {
fn(tx);
}
}
export class TransactionImpl {
constructor(_fn, _getDebugName) {
this._fn = _fn;
this._getDebugName = _getDebugName;
this.updatingObservers = [];
getLogger()?.handleBeginTransaction(this);
}
getDebugName() {
if (this._getDebugName) {
return this._getDebugName();
}
return getFunctionName(this._fn);
}
updateObserver(observer, observable) {
// When this gets called while finish is active, they will still get considered
this.updatingObservers.push({ observer, observable });
observer.beginUpdate(observable);
}
finish() {
const updatingObservers = this.updatingObservers;
for (let i = 0; i < updatingObservers.length; i++) {
const { observer, observable } = updatingObservers[i];
observer.endUpdate(observable);
}
// Prevent anyone from updating observers from now on.
this.updatingObservers = null;
getLogger()?.handleEndTransaction();
}
}
export function observableValue(nameOrOwner, initialValue) {
let debugNameData;
if (typeof nameOrOwner === 'string') {
debugNameData = new DebugNameData(undefined, nameOrOwner, undefined);
}
else {
debugNameData = new DebugNameData(nameOrOwner, undefined, undefined);
}
return new ObservableValue(debugNameData, initialValue, strictEquals);
}
export class ObservableValue extends BaseObservable {
get debugName() {
return this._debugNameData.getDebugName(this) ?? 'ObservableValue';
}
constructor(_debugNameData, initialValue, _equalityComparator) {
super();
this._debugNameData = _debugNameData;
this._equalityComparator = _equalityComparator;
this._value = initialValue;
}
get() {
return this._value;
}
set(value, tx, change) {
if (change === undefined && this._equalityComparator(this._value, value)) {
return;
}
let _tx;
if (!tx) {
tx = _tx = new TransactionImpl(() => { }, () => `Setting ${this.debugName}`);
}
try {
const oldValue = this._value;
this._setValue(value);
getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true, hadValue: true });
for (const observer of this.observers) {
tx.updateObserver(observer, this);
observer.handleChange(this, change);
}
}
finally {
if (_tx) {
_tx.finish();
}
}
}
toString() {
return `${this.debugName}: ${this._value}`;
}
_setValue(newValue) {
this._value = newValue;
}
}
/**
* A disposable observable. When disposed, its value is also disposed.
* When a new value is set, the previous value is disposed.
*/
export function disposableObservableValue(nameOrOwner, initialValue) {
let debugNameData;
if (typeof nameOrOwner === 'string') {
debugNameData = new DebugNameData(undefined, nameOrOwner, undefined);
}
else {
debugNameData = new DebugNameData(nameOrOwner, undefined, undefined);
}
return new DisposableObservableValue(debugNameData, initialValue, strictEquals);
}
export class DisposableObservableValue extends ObservableValue {
_setValue(newValue) {
if (this._value === newValue) {
return;
}
if (this._value) {
this._value.dispose();
}
this._value = newValue;
}
dispose() {
this._value?.dispose();
}
}
@@ -0,0 +1,104 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class DebugNameData {
constructor(owner, debugNameSource, referenceFn) {
this.owner = owner;
this.debugNameSource = debugNameSource;
this.referenceFn = referenceFn;
}
getDebugName(target) {
return getDebugName(target, this);
}
}
const countPerName = new Map();
const cachedDebugName = new WeakMap();
export function getDebugName(target, data) {
const cached = cachedDebugName.get(target);
if (cached) {
return cached;
}
const dbgName = computeDebugName(target, data);
if (dbgName) {
let count = countPerName.get(dbgName) ?? 0;
count++;
countPerName.set(dbgName, count);
const result = count === 1 ? dbgName : `${dbgName}#${count}`;
cachedDebugName.set(target, result);
return result;
}
return undefined;
}
function computeDebugName(self, data) {
const cached = cachedDebugName.get(self);
if (cached) {
return cached;
}
const ownerStr = data.owner ? formatOwner(data.owner) + `.` : '';
let result;
const debugNameSource = data.debugNameSource;
if (debugNameSource !== undefined) {
if (typeof debugNameSource === 'function') {
result = debugNameSource();
if (result !== undefined) {
return ownerStr + result;
}
}
else {
return ownerStr + debugNameSource;
}
}
const referenceFn = data.referenceFn;
if (referenceFn !== undefined) {
result = getFunctionName(referenceFn);
if (result !== undefined) {
return ownerStr + result;
}
}
if (data.owner !== undefined) {
const key = findKey(data.owner, self);
if (key !== undefined) {
return ownerStr + key;
}
}
return undefined;
}
function findKey(obj, value) {
for (const key in obj) {
if (obj[key] === value) {
return key;
}
}
return undefined;
}
const countPerClassName = new Map();
const ownerId = new WeakMap();
function formatOwner(owner) {
const id = ownerId.get(owner);
if (id) {
return id;
}
const className = getClassName(owner);
let count = countPerClassName.get(className) ?? 0;
count++;
countPerClassName.set(className, count);
const result = count === 1 ? className : `${className}#${count}`;
ownerId.set(owner, result);
return result;
}
function getClassName(obj) {
const ctor = obj.constructor;
if (ctor) {
return ctor.name;
}
return 'Object';
}
export function getFunctionName(fn) {
const fnSrc = fn.toString();
// Pattern: /** @description ... */
const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//;
const match = regexp.exec(fnSrc);
const result = match ? match[1] : undefined;
return result?.trim();
}
@@ -0,0 +1,286 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { assertFn } from '../assert.js';
import { strictEquals } from '../equals.js';
import { DisposableStore } from '../lifecycle.js';
import { BaseObservable, _setDerivedOpts } from './base.js';
import { DebugNameData } from './debugName.js';
import { getLogger } from './logging.js';
export function derived(computeFnOrOwner, computeFn) {
if (computeFn !== undefined) {
return new Derived(new DebugNameData(computeFnOrOwner, undefined, computeFn), computeFn, undefined, undefined, undefined, strictEquals);
}
return new Derived(new DebugNameData(undefined, undefined, computeFnOrOwner), computeFnOrOwner, undefined, undefined, undefined, strictEquals);
}
export function derivedWithSetter(owner, computeFn, setter) {
return new DerivedWithSetter(new DebugNameData(owner, undefined, computeFn), computeFn, undefined, undefined, undefined, strictEquals, setter);
}
export function derivedOpts(options, computeFn) {
return new Derived(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn), computeFn, undefined, undefined, options.onLastObserverRemoved, options.equalsFn ?? strictEquals);
}
_setDerivedOpts(derivedOpts);
/**
* Represents an observable that is derived from other observables.
* The value is only recomputed when absolutely needed.
*
* {@link computeFn} should start with a JS Doc using `@description` to name the derived.
*
* Use `createEmptyChangeSummary` to create a "change summary" that can collect the changes.
* Use `handleChange` to add a reported change to the change summary.
* The compute function is given the last change summary.
* The change summary is discarded after the compute function was called.
*
* @see derived
*/
export function derivedHandleChanges(options, computeFn) {
return new Derived(new DebugNameData(options.owner, options.debugName, undefined), computeFn, options.createEmptyChangeSummary, options.handleChange, undefined, options.equalityComparer ?? strictEquals);
}
export function derivedWithStore(computeFnOrOwner, computeFnOrUndefined) {
let computeFn;
let owner;
if (computeFnOrUndefined === undefined) {
computeFn = computeFnOrOwner;
owner = undefined;
}
else {
owner = computeFnOrOwner;
computeFn = computeFnOrUndefined;
}
const store = new DisposableStore();
return new Derived(new DebugNameData(owner, undefined, computeFn), r => {
store.clear();
return computeFn(r, store);
}, undefined, undefined, () => store.dispose(), strictEquals);
}
export function derivedDisposable(computeFnOrOwner, computeFnOrUndefined) {
let computeFn;
let owner;
if (computeFnOrUndefined === undefined) {
computeFn = computeFnOrOwner;
owner = undefined;
}
else {
owner = computeFnOrOwner;
computeFn = computeFnOrUndefined;
}
let store = undefined;
return new Derived(new DebugNameData(owner, undefined, computeFn), r => {
if (!store) {
store = new DisposableStore();
}
else {
store.clear();
}
const result = computeFn(r);
if (result) {
store.add(result);
}
return result;
}, undefined, undefined, () => {
if (store) {
store.dispose();
store = undefined;
}
}, strictEquals);
}
export class Derived extends BaseObservable {
get debugName() {
return this._debugNameData.getDebugName(this) ?? '(anonymous)';
}
constructor(_debugNameData, _computeFn, createChangeSummary, _handleChange, _handleLastObserverRemoved = undefined, _equalityComparator) {
super();
this._debugNameData = _debugNameData;
this._computeFn = _computeFn;
this.createChangeSummary = createChangeSummary;
this._handleChange = _handleChange;
this._handleLastObserverRemoved = _handleLastObserverRemoved;
this._equalityComparator = _equalityComparator;
this.state = 0 /* DerivedState.initial */;
this.value = undefined;
this.updateCount = 0;
this.dependencies = new Set();
this.dependenciesToBeRemoved = new Set();
this.changeSummary = undefined;
this.changeSummary = this.createChangeSummary?.();
getLogger()?.handleDerivedCreated(this);
}
onLastObserverRemoved() {
/**
* We are not tracking changes anymore, thus we have to assume
* that our cache is invalid.
*/
this.state = 0 /* DerivedState.initial */;
this.value = undefined;
for (const d of this.dependencies) {
d.removeObserver(this);
}
this.dependencies.clear();
this._handleLastObserverRemoved?.();
}
get() {
if (this.observers.size === 0) {
// Without observers, we don't know when to clean up stuff.
// Thus, we don't cache anything to prevent memory leaks.
const result = this._computeFn(this, this.createChangeSummary?.());
// Clear new dependencies
this.onLastObserverRemoved();
return result;
}
else {
do {
// We might not get a notification for a dependency that changed while it is updating,
// thus we also have to ask all our depedencies if they changed in this case.
if (this.state === 1 /* DerivedState.dependenciesMightHaveChanged */) {
for (const d of this.dependencies) {
/** might call {@link handleChange} indirectly, which could make us stale */
d.reportChanges();
if (this.state === 2 /* DerivedState.stale */) {
// The other dependencies will refresh on demand, so early break
break;
}
}
}
// We called report changes of all dependencies.
// If we are still not stale, we can assume to be up to date again.
if (this.state === 1 /* DerivedState.dependenciesMightHaveChanged */) {
this.state = 3 /* DerivedState.upToDate */;
}
this._recomputeIfNeeded();
// In case recomputation changed one of our dependencies, we need to recompute again.
} while (this.state !== 3 /* DerivedState.upToDate */);
return this.value;
}
}
_recomputeIfNeeded() {
if (this.state === 3 /* DerivedState.upToDate */) {
return;
}
const emptySet = this.dependenciesToBeRemoved;
this.dependenciesToBeRemoved = this.dependencies;
this.dependencies = emptySet;
const hadValue = this.state !== 0 /* DerivedState.initial */;
const oldValue = this.value;
this.state = 3 /* DerivedState.upToDate */;
const changeSummary = this.changeSummary;
this.changeSummary = this.createChangeSummary?.();
try {
/** might call {@link handleChange} indirectly, which could invalidate us */
this.value = this._computeFn(this, changeSummary);
}
finally {
// We don't want our observed observables to think that they are (not even temporarily) not being observed.
// Thus, we only unsubscribe from observables that are definitely not read anymore.
for (const o of this.dependenciesToBeRemoved) {
o.removeObserver(this);
}
this.dependenciesToBeRemoved.clear();
}
const didChange = hadValue && !(this._equalityComparator(oldValue, this.value));
getLogger()?.handleDerivedRecomputed(this, {
oldValue,
newValue: this.value,
change: undefined,
didChange,
hadValue,
});
if (didChange) {
for (const r of this.observers) {
r.handleChange(this, undefined);
}
}
}
toString() {
return `LazyDerived<${this.debugName}>`;
}
// IObserver Implementation
beginUpdate(_observable) {
this.updateCount++;
const propagateBeginUpdate = this.updateCount === 1;
if (this.state === 3 /* DerivedState.upToDate */) {
this.state = 1 /* DerivedState.dependenciesMightHaveChanged */;
// If we propagate begin update, that will already signal a possible change.
if (!propagateBeginUpdate) {
for (const r of this.observers) {
r.handlePossibleChange(this);
}
}
}
if (propagateBeginUpdate) {
for (const r of this.observers) {
r.beginUpdate(this); // This signals a possible change
}
}
}
endUpdate(_observable) {
this.updateCount--;
if (this.updateCount === 0) {
// End update could change the observer list.
const observers = [...this.observers];
for (const r of observers) {
r.endUpdate(this);
}
}
assertFn(() => this.updateCount >= 0);
}
handlePossibleChange(observable) {
// In all other states, observers already know that we might have changed.
if (this.state === 3 /* DerivedState.upToDate */ && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) {
this.state = 1 /* DerivedState.dependenciesMightHaveChanged */;
for (const r of this.observers) {
r.handlePossibleChange(this);
}
}
}
handleChange(observable, change) {
if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) {
const shouldReact = this._handleChange ? this._handleChange({
changedObservable: observable,
change,
didChange: (o) => o === observable,
}, this.changeSummary) : true;
const wasUpToDate = this.state === 3 /* DerivedState.upToDate */;
if (shouldReact && (this.state === 1 /* DerivedState.dependenciesMightHaveChanged */ || wasUpToDate)) {
this.state = 2 /* DerivedState.stale */;
if (wasUpToDate) {
for (const r of this.observers) {
r.handlePossibleChange(this);
}
}
}
}
}
// IReader Implementation
readObservable(observable) {
// Subscribe before getting the value to enable caching
observable.addObserver(this);
/** This might call {@link handleChange} indirectly, which could invalidate us */
const value = observable.get();
// Which is why we only add the observable to the dependencies now.
this.dependencies.add(observable);
this.dependenciesToBeRemoved.delete(observable);
return value;
}
addObserver(observer) {
const shouldCallBeginUpdate = !this.observers.has(observer) && this.updateCount > 0;
super.addObserver(observer);
if (shouldCallBeginUpdate) {
observer.beginUpdate(this);
}
}
removeObserver(observer) {
const shouldCallEndUpdate = this.observers.has(observer) && this.updateCount > 0;
super.removeObserver(observer);
if (shouldCallEndUpdate) {
// Calling end update after removing the observer makes sure endUpdate cannot be called twice here.
observer.endUpdate(this);
}
}
}
export class DerivedWithSetter extends Derived {
constructor(debugNameData, computeFn, createChangeSummary, handleChange, handleLastObserverRemoved = undefined, equalityComparator, set) {
super(debugNameData, computeFn, createChangeSummary, handleChange, handleLastObserverRemoved, equalityComparator);
this.set = set;
}
}
@@ -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.
*--------------------------------------------------------------------------------------------*/
import { BaseObservable, TransactionImpl } from './base.js';
/**
* Holds off updating observers until the value is actually read.
*/
export class LazyObservableValue extends BaseObservable {
get debugName() {
return this._debugNameData.getDebugName(this) ?? 'LazyObservableValue';
}
constructor(_debugNameData, initialValue, _equalityComparator) {
super();
this._debugNameData = _debugNameData;
this._equalityComparator = _equalityComparator;
this._isUpToDate = true;
this._deltas = [];
this._updateCounter = 0;
this._value = initialValue;
}
get() {
this._update();
return this._value;
}
_update() {
if (this._isUpToDate) {
return;
}
this._isUpToDate = true;
if (this._deltas.length > 0) {
for (const observer of this.observers) {
for (const change of this._deltas) {
observer.handleChange(this, change);
}
}
this._deltas.length = 0;
}
else {
for (const observer of this.observers) {
observer.handleChange(this, undefined);
}
}
}
_beginUpdate() {
this._updateCounter++;
if (this._updateCounter === 1) {
for (const observer of this.observers) {
observer.beginUpdate(this);
}
}
}
_endUpdate() {
this._updateCounter--;
if (this._updateCounter === 0) {
this._update();
// End update could change the observer list.
const observers = [...this.observers];
for (const r of observers) {
r.endUpdate(this);
}
}
}
addObserver(observer) {
const shouldCallBeginUpdate = !this.observers.has(observer) && this._updateCounter > 0;
super.addObserver(observer);
if (shouldCallBeginUpdate) {
observer.beginUpdate(this);
}
}
removeObserver(observer) {
const shouldCallEndUpdate = this.observers.has(observer) && this._updateCounter > 0;
super.removeObserver(observer);
if (shouldCallEndUpdate) {
// Calling end update after removing the observer makes sure endUpdate cannot be called twice here.
observer.endUpdate(this);
}
}
set(value, tx, change) {
if (change === undefined && this._equalityComparator(this._value, value)) {
return;
}
let _tx;
if (!tx) {
tx = _tx = new TransactionImpl(() => { }, () => `Setting ${this.debugName}`);
}
try {
this._isUpToDate = false;
this._setValue(value);
if (change !== undefined) {
this._deltas.push(change);
}
tx.updateObserver({
beginUpdate: () => this._beginUpdate(),
endUpdate: () => this._endUpdate(),
handleChange: (observable, change) => { },
handlePossibleChange: (observable) => { },
}, this);
if (this._updateCounter > 1) {
// We already started begin/end update, so we need to manually call handlePossibleChange
for (const observer of this.observers) {
observer.handlePossibleChange(this);
}
}
}
finally {
if (_tx) {
_tx.finish();
}
}
}
toString() {
return `${this.debugName}: ${this._value}`;
}
_setValue(newValue) {
this._value = newValue;
}
}
@@ -0,0 +1,258 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
let globalObservableLogger;
export function setLogger(logger) {
globalObservableLogger = logger;
}
export function getLogger() {
return globalObservableLogger;
}
export class ConsoleObservableLogger {
constructor() {
this.indentation = 0;
this.changedObservablesSets = new WeakMap();
}
textToConsoleArgs(text) {
return consoleTextToArgs([
normalText(repeat('| ', this.indentation)),
text,
]);
}
formatInfo(info) {
if (!info.hadValue) {
return [
normalText(` `),
styled(formatValue(info.newValue, 60), {
color: 'green',
}),
normalText(` (initial)`),
];
}
return info.didChange
? [
normalText(` `),
styled(formatValue(info.oldValue, 70), {
color: 'red',
strikeThrough: true,
}),
normalText(` `),
styled(formatValue(info.newValue, 60), {
color: 'green',
}),
]
: [normalText(` (unchanged)`)];
}
handleObservableChanged(observable, info) {
console.log(...this.textToConsoleArgs([
formatKind('observable value changed'),
styled(observable.debugName, { color: 'BlueViolet' }),
...this.formatInfo(info),
]));
}
formatChanges(changes) {
if (changes.size === 0) {
return undefined;
}
return styled(' (changed deps: ' +
[...changes].map((o) => o.debugName).join(', ') +
')', { color: 'gray' });
}
handleDerivedCreated(derived) {
const existingHandleChange = derived.handleChange;
this.changedObservablesSets.set(derived, new Set());
derived.handleChange = (observable, change) => {
this.changedObservablesSets.get(derived).add(observable);
return existingHandleChange.apply(derived, [observable, change]);
};
}
handleDerivedRecomputed(derived, info) {
const changedObservables = this.changedObservablesSets.get(derived);
console.log(...this.textToConsoleArgs([
formatKind('derived recomputed'),
styled(derived.debugName, { color: 'BlueViolet' }),
...this.formatInfo(info),
this.formatChanges(changedObservables),
{ data: [{ fn: derived._debugNameData.referenceFn ?? derived._computeFn }] }
]));
changedObservables.clear();
}
handleFromEventObservableTriggered(observable, info) {
console.log(...this.textToConsoleArgs([
formatKind('observable from event triggered'),
styled(observable.debugName, { color: 'BlueViolet' }),
...this.formatInfo(info),
{ data: [{ fn: observable._getValue }] }
]));
}
handleAutorunCreated(autorun) {
const existingHandleChange = autorun.handleChange;
this.changedObservablesSets.set(autorun, new Set());
autorun.handleChange = (observable, change) => {
this.changedObservablesSets.get(autorun).add(observable);
return existingHandleChange.apply(autorun, [observable, change]);
};
}
handleAutorunTriggered(autorun) {
const changedObservables = this.changedObservablesSets.get(autorun);
console.log(...this.textToConsoleArgs([
formatKind('autorun'),
styled(autorun.debugName, { color: 'BlueViolet' }),
this.formatChanges(changedObservables),
{ data: [{ fn: autorun._debugNameData.referenceFn ?? autorun._runFn }] }
]));
changedObservables.clear();
this.indentation++;
}
handleAutorunFinished(autorun) {
this.indentation--;
}
handleBeginTransaction(transaction) {
let transactionName = transaction.getDebugName();
if (transactionName === undefined) {
transactionName = '';
}
console.log(...this.textToConsoleArgs([
formatKind('transaction'),
styled(transactionName, { color: 'BlueViolet' }),
{ data: [{ fn: transaction._fn }] }
]));
this.indentation++;
}
handleEndTransaction() {
this.indentation--;
}
}
function consoleTextToArgs(text) {
const styles = new Array();
const data = [];
let firstArg = '';
function process(t) {
if ('length' in t) {
for (const item of t) {
if (item) {
process(item);
}
}
}
else if ('text' in t) {
firstArg += `%c${t.text}`;
styles.push(t.style);
if (t.data) {
data.push(...t.data);
}
}
else if ('data' in t) {
data.push(...t.data);
}
}
process(text);
const result = [firstArg, ...styles];
result.push(...data);
return result;
}
function normalText(text) {
return styled(text, { color: 'black' });
}
function formatKind(kind) {
return styled(padStr(`${kind}: `, 10), { color: 'black', bold: true });
}
function styled(text, options = {
color: 'black',
}) {
function objToCss(styleObj) {
return Object.entries(styleObj).reduce((styleString, [propName, propValue]) => {
return `${styleString}${propName}:${propValue};`;
}, '');
}
const style = {
color: options.color,
};
if (options.strikeThrough) {
style['text-decoration'] = 'line-through';
}
if (options.bold) {
style['font-weight'] = 'bold';
}
return {
text,
style: objToCss(style),
};
}
function formatValue(value, availableLen) {
switch (typeof value) {
case 'number':
return '' + value;
case 'string':
if (value.length + 2 <= availableLen) {
return `"${value}"`;
}
return `"${value.substr(0, availableLen - 7)}"+...`;
case 'boolean':
return value ? 'true' : 'false';
case 'undefined':
return 'undefined';
case 'object':
if (value === null) {
return 'null';
}
if (Array.isArray(value)) {
return formatArray(value, availableLen);
}
return formatObject(value, availableLen);
case 'symbol':
return value.toString();
case 'function':
return `[[Function${value.name ? ' ' + value.name : ''}]]`;
default:
return '' + value;
}
}
function formatArray(value, availableLen) {
let result = '[ ';
let first = true;
for (const val of value) {
if (!first) {
result += ', ';
}
if (result.length - 5 > availableLen) {
result += '...';
break;
}
first = false;
result += `${formatValue(val, availableLen - result.length)}`;
}
result += ' ]';
return result;
}
function formatObject(value, availableLen) {
let result = '{ ';
let first = true;
for (const [key, val] of Object.entries(value)) {
if (!first) {
result += ', ';
}
if (result.length - 5 > availableLen) {
result += '...';
break;
}
first = false;
result += `${key}: ${formatValue(val, availableLen - result.length)}`;
}
result += ' }';
return result;
}
function repeat(str, count) {
let result = '';
for (let i = 1; i <= count; i++) {
result += str;
}
return result;
}
function padStr(str, length) {
while (str.length < length) {
str += ' ';
}
return str;
}
@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { autorun } from './autorun.js';
import { observableValue, transaction } from './base.js';
import { CancellationError } from '../errors.js';
/**
* A promise whose state is observable.
*/
export class ObservablePromise {
static fromFn(fn) {
return new ObservablePromise(fn());
}
constructor(promise) {
this._value = observableValue(this, undefined);
/**
* The current state of the promise.
* Is `undefined` if the promise didn't resolve yet.
*/
this.promiseResult = this._value;
this.promise = promise.then(value => {
transaction(tx => {
/** @description onPromiseResolved */
this._value.set(new PromiseResult(value, undefined), tx);
});
return value;
}, error => {
transaction(tx => {
/** @description onPromiseRejected */
this._value.set(new PromiseResult(undefined, error), tx);
});
throw error;
});
}
}
export class PromiseResult {
constructor(
/**
* The value of the resolved promise.
* Undefined if the promise rejected.
*/
data,
/**
* The error in case of a rejected promise.
* Undefined if the promise resolved.
*/
error) {
this.data = data;
this.error = error;
}
}
export function waitForState(observable, predicate, isError, cancellationToken) {
if (!predicate) {
predicate = state => state !== null && state !== undefined;
}
return new Promise((resolve, reject) => {
let isImmediateRun = true;
let shouldDispose = false;
const stateObs = observable.map(state => {
/** @description waitForState.state */
return {
isFinished: predicate(state),
error: isError ? isError(state) : false,
state
};
});
const d = autorun(reader => {
/** @description waitForState */
const { isFinished, error, state } = stateObs.read(reader);
if (isFinished || error) {
if (isImmediateRun) {
// The variable `d` is not initialized yet
shouldDispose = true;
}
else {
d.dispose();
}
if (error) {
reject(error === true ? state : error);
}
else {
resolve(state);
}
}
});
if (cancellationToken) {
const dc = cancellationToken.onCancellationRequested(() => {
d.dispose();
dc.dispose();
reject(new CancellationError());
});
if (cancellationToken.isCancellationRequested) {
d.dispose();
dc.dispose();
reject(new CancellationError());
return;
}
}
isImmediateRun = false;
if (shouldDispose) {
d.dispose();
}
});
}
@@ -0,0 +1,366 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from '../event.js';
import { DisposableStore, toDisposable } from '../lifecycle.js';
import { BaseObservable, ConvenientObservable, _setKeepObserved, _setRecomputeInitiallyAndOnChange, subtransaction, transaction } from './base.js';
import { DebugNameData } from './debugName.js';
import { derived, derivedOpts } from './derived.js';
import { getLogger } from './logging.js';
import { strictEquals } from '../equals.js';
/**
* Represents an efficient observable whose value never changes.
*/
export function constObservable(value) {
return new ConstObservable(value);
}
class ConstObservable extends ConvenientObservable {
constructor(value) {
super();
this.value = value;
}
get debugName() {
return this.toString();
}
get() {
return this.value;
}
addObserver(observer) {
// NO OP
}
removeObserver(observer) {
// NO OP
}
toString() {
return `Const: ${this.value}`;
}
}
export function observableFromEvent(...args) {
let owner;
let event;
let getValue;
if (args.length === 3) {
[owner, event, getValue] = args;
}
else {
[event, getValue] = args;
}
return new FromEventObservable(new DebugNameData(owner, undefined, getValue), event, getValue, () => FromEventObservable.globalTransaction, strictEquals);
}
export function observableFromEventOpts(options, event, getValue) {
return new FromEventObservable(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? getValue), event, getValue, () => FromEventObservable.globalTransaction, options.equalsFn ?? strictEquals);
}
export class FromEventObservable extends BaseObservable {
constructor(_debugNameData, event, _getValue, _getTransaction, _equalityComparator) {
super();
this._debugNameData = _debugNameData;
this.event = event;
this._getValue = _getValue;
this._getTransaction = _getTransaction;
this._equalityComparator = _equalityComparator;
this.hasValue = false;
this.handleEvent = (args) => {
const newValue = this._getValue(args);
const oldValue = this.value;
const didChange = !this.hasValue || !(this._equalityComparator(oldValue, newValue));
let didRunTransaction = false;
if (didChange) {
this.value = newValue;
if (this.hasValue) {
didRunTransaction = true;
subtransaction(this._getTransaction(), (tx) => {
getLogger()?.handleFromEventObservableTriggered(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue });
for (const o of this.observers) {
tx.updateObserver(o, this);
o.handleChange(this, undefined);
}
}, () => {
const name = this.getDebugName();
return 'Event fired' + (name ? `: ${name}` : '');
});
}
this.hasValue = true;
}
if (!didRunTransaction) {
getLogger()?.handleFromEventObservableTriggered(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue });
}
};
}
getDebugName() {
return this._debugNameData.getDebugName(this);
}
get debugName() {
const name = this.getDebugName();
return 'From Event' + (name ? `: ${name}` : '');
}
onFirstObserverAdded() {
this.subscription = this.event(this.handleEvent);
}
onLastObserverRemoved() {
this.subscription.dispose();
this.subscription = undefined;
this.hasValue = false;
this.value = undefined;
}
get() {
if (this.subscription) {
if (!this.hasValue) {
this.handleEvent(undefined);
}
return this.value;
}
else {
// no cache, as there are no subscribers to keep it updated
const value = this._getValue(undefined);
return value;
}
}
}
(function (observableFromEvent) {
observableFromEvent.Observer = FromEventObservable;
function batchEventsGlobally(tx, fn) {
let didSet = false;
if (FromEventObservable.globalTransaction === undefined) {
FromEventObservable.globalTransaction = tx;
didSet = true;
}
try {
fn();
}
finally {
if (didSet) {
FromEventObservable.globalTransaction = undefined;
}
}
}
observableFromEvent.batchEventsGlobally = batchEventsGlobally;
})(observableFromEvent || (observableFromEvent = {}));
export function observableSignalFromEvent(debugName, event) {
return new FromEventObservableSignal(debugName, event);
}
class FromEventObservableSignal extends BaseObservable {
constructor(debugName, event) {
super();
this.debugName = debugName;
this.event = event;
this.handleEvent = () => {
transaction((tx) => {
for (const o of this.observers) {
tx.updateObserver(o, this);
o.handleChange(this, undefined);
}
}, () => this.debugName);
};
}
onFirstObserverAdded() {
this.subscription = this.event(this.handleEvent);
}
onLastObserverRemoved() {
this.subscription.dispose();
this.subscription = undefined;
}
get() {
// NO OP
}
}
export function observableSignal(debugNameOrOwner) {
if (typeof debugNameOrOwner === 'string') {
return new ObservableSignal(debugNameOrOwner);
}
else {
return new ObservableSignal(undefined, debugNameOrOwner);
}
}
class ObservableSignal extends BaseObservable {
get debugName() {
return new DebugNameData(this._owner, this._debugName, undefined).getDebugName(this) ?? 'Observable Signal';
}
toString() {
return this.debugName;
}
constructor(_debugName, _owner) {
super();
this._debugName = _debugName;
this._owner = _owner;
}
trigger(tx, change) {
if (!tx) {
transaction(tx => {
this.trigger(tx, change);
}, () => `Trigger signal ${this.debugName}`);
return;
}
for (const o of this.observers) {
tx.updateObserver(o, this);
o.handleChange(this, change);
}
}
get() {
// NO OP
}
}
/**
* This makes sure the observable is being observed and keeps its cache alive.
*/
export function keepObserved(observable) {
const o = new KeepAliveObserver(false, undefined);
observable.addObserver(o);
return toDisposable(() => {
observable.removeObserver(o);
});
}
_setKeepObserved(keepObserved);
/**
* This converts the given observable into an autorun.
*/
export function recomputeInitiallyAndOnChange(observable, handleValue) {
const o = new KeepAliveObserver(true, handleValue);
observable.addObserver(o);
if (handleValue) {
handleValue(observable.get());
}
else {
observable.reportChanges();
}
return toDisposable(() => {
observable.removeObserver(o);
});
}
_setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange);
export class KeepAliveObserver {
constructor(_forceRecompute, _handleValue) {
this._forceRecompute = _forceRecompute;
this._handleValue = _handleValue;
this._counter = 0;
}
beginUpdate(observable) {
this._counter++;
}
endUpdate(observable) {
this._counter--;
if (this._counter === 0 && this._forceRecompute) {
if (this._handleValue) {
this._handleValue(observable.get());
}
else {
observable.reportChanges();
}
}
}
handlePossibleChange(observable) {
// NO OP
}
handleChange(observable, change) {
// NO OP
}
}
export function derivedObservableWithCache(owner, computeFn) {
let lastValue = undefined;
const observable = derivedOpts({ owner, debugReferenceFn: computeFn }, reader => {
lastValue = computeFn(reader, lastValue);
return lastValue;
});
return observable;
}
export function derivedObservableWithWritableCache(owner, computeFn) {
let lastValue = undefined;
const onChange = observableSignal('derivedObservableWithWritableCache');
const observable = derived(owner, reader => {
onChange.read(reader);
lastValue = computeFn(reader, lastValue);
return lastValue;
});
return Object.assign(observable, {
clearCache: (tx) => {
lastValue = undefined;
onChange.trigger(tx);
},
setCache: (newValue, tx) => {
lastValue = newValue;
onChange.trigger(tx);
}
});
}
/**
* When the items array changes, referential equal items are not mapped again.
*/
export function mapObservableArrayCached(owner, items, map, keySelector) {
let m = new ArrayMap(map, keySelector);
const self = derivedOpts({
debugReferenceFn: map,
owner,
onLastObserverRemoved: () => {
m.dispose();
m = new ArrayMap(map);
}
}, (reader) => {
m.setItems(items.read(reader));
return m.getItems();
});
return self;
}
class ArrayMap {
constructor(_map, _keySelector) {
this._map = _map;
this._keySelector = _keySelector;
this._cache = new Map();
this._items = [];
}
dispose() {
this._cache.forEach(entry => entry.store.dispose());
this._cache.clear();
}
setItems(items) {
const newItems = [];
const itemsToRemove = new Set(this._cache.keys());
for (const item of items) {
const key = this._keySelector ? this._keySelector(item) : item;
let entry = this._cache.get(key);
if (!entry) {
const store = new DisposableStore();
const out = this._map(item, store);
entry = { out, store };
this._cache.set(key, entry);
}
else {
itemsToRemove.delete(key);
}
newItems.push(entry.out);
}
for (const item of itemsToRemove) {
const entry = this._cache.get(item);
entry.store.dispose();
this._cache.delete(item);
}
this._items = newItems;
}
getItems() {
return this._items;
}
}
export class ValueWithChangeEventFromObservable {
constructor(observable) {
this.observable = observable;
}
get onDidChange() {
return Event.fromObservableLight(this.observable);
}
get value() {
return this.observable.get();
}
}
export function observableFromValueWithChangeEvent(owner, value) {
if (value instanceof ValueWithChangeEventFromObservable) {
return value.observable;
}
return observableFromEvent(owner, value.onDidChange, () => value.value);
}
/**
* Works like a derived.
* However, if the value is not undefined, it is cached and will not be recomputed anymore.
* In that case, the derived will unsubscribe from its dependencies.
*/
export function derivedConstOnceDefined(owner, fn) {
return derivedObservableWithCache(owner, (reader, lastValue) => lastValue ?? fn(reader));
}
@@ -0,0 +1 @@
export {};
File diff suppressed because it is too large Load Diff
+154
View File
@@ -0,0 +1,154 @@
/*---------------------------------------------------------------------------------------------
* 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 '../../nls.js';
export const LANGUAGE_DEFAULT = 'en';
let _isWindows = false;
let _isMacintosh = false;
let _isLinux = false;
let _isLinuxSnap = false;
let _isNative = false;
let _isWeb = false;
let _isElectron = false;
let _isIOS = false;
let _isCI = false;
let _isMobile = false;
let _locale = undefined;
let _language = LANGUAGE_DEFAULT;
let _platformLocale = LANGUAGE_DEFAULT;
let _translationsConfigFile = undefined;
let _userAgent = undefined;
const $globalThis = globalThis;
let nodeProcess = undefined;
if (typeof $globalThis.vscode !== 'undefined' && typeof $globalThis.vscode.process !== 'undefined') {
// Native environment (sandboxed)
nodeProcess = $globalThis.vscode.process;
}
else if (typeof process !== 'undefined' && typeof process?.versions?.node === 'string') {
// Native environment (non-sandboxed)
nodeProcess = process;
}
const isElectronProcess = typeof nodeProcess?.versions?.electron === 'string';
const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer';
// Native environment
if (typeof nodeProcess === 'object') {
_isWindows = (nodeProcess.platform === 'win32');
_isMacintosh = (nodeProcess.platform === 'darwin');
_isLinux = (nodeProcess.platform === 'linux');
_isLinuxSnap = _isLinux && !!nodeProcess.env['SNAP'] && !!nodeProcess.env['SNAP_REVISION'];
_isElectron = isElectronProcess;
_isCI = !!nodeProcess.env['CI'] || !!nodeProcess.env['BUILD_ARTIFACTSTAGINGDIRECTORY'];
_locale = LANGUAGE_DEFAULT;
_language = LANGUAGE_DEFAULT;
const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG'];
if (rawNlsConfig) {
try {
const nlsConfig = JSON.parse(rawNlsConfig);
_locale = nlsConfig.userLocale;
_platformLocale = nlsConfig.osLocale;
_language = nlsConfig.resolvedLanguage || LANGUAGE_DEFAULT;
_translationsConfigFile = nlsConfig.languagePack?.translationsConfigFile;
}
catch (e) {
}
}
_isNative = true;
}
// Web environment
else 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;
_isMobile = _userAgent?.indexOf('Mobi') >= 0;
_isWeb = true;
// VSCODE_GLOBALS: NLS
_language = nls.getNLSLanguage() || LANGUAGE_DEFAULT;
_locale = navigator.language.toLowerCase();
_platformLocale = _locale;
}
// Unknown environment
else {
console.error('Unable to resolve platform.');
}
let _platform = 0 /* Platform.Web */;
if (_isMacintosh) {
_platform = 1 /* Platform.Mac */;
}
else if (_isWindows) {
_platform = 3 /* Platform.Windows */;
}
else if (_isLinux) {
_platform = 2 /* Platform.Linux */;
}
export const isWindows = _isWindows;
export const isMacintosh = _isMacintosh;
export const isLinux = _isLinux;
export const isNative = _isNative;
export const isWeb = _isWeb;
export const isWebWorker = (_isWeb && typeof $globalThis.importScripts === 'function');
export const webWorkerOrigin = isWebWorker ? $globalThis.origin : undefined;
export const isIOS = _isIOS;
export const isMobile = _isMobile;
export const userAgent = _userAgent;
/**
* The language used for the user interface. The format of
* the string is all lower case (e.g. zh-tw for Traditional
* Chinese or de for German)
*/
export const language = _language;
export const setTimeout0IsFaster = (typeof $globalThis.postMessage === 'function' && !$globalThis.importScripts);
/**
* See https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#:~:text=than%204%2C%20then-,set%20timeout%20to%204,-.
*
* Works similarly to `setTimeout(0)` but doesn't suffer from the 4ms artificial delay
* that browsers set when the nesting level is > 5.
*/
export const setTimeout0 = (() => {
if (setTimeout0IsFaster) {
const pending = [];
$globalThis.addEventListener('message', (e) => {
if (e.data && e.data.vscodeScheduleAsyncWork) {
for (let i = 0, len = pending.length; i < len; i++) {
const candidate = pending[i];
if (candidate.id === e.data.vscodeScheduleAsyncWork) {
pending.splice(i, 1);
candidate.callback();
return;
}
}
}
});
let lastId = 0;
return (callback) => {
const myId = ++lastId;
pending.push({
id: myId,
callback: callback
});
$globalThis.postMessage({ vscodeScheduleAsyncWork: myId }, '*');
};
}
return (callback) => setTimeout(callback);
})();
export const OS = (_isMacintosh || _isIOS ? 2 /* OperatingSystem.Macintosh */ : (_isWindows ? 1 /* OperatingSystem.Windows */ : 3 /* OperatingSystem.Linux */));
let _isLittleEndian = true;
let _isLittleEndianComputed = false;
export function isLittleEndian() {
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;
}
export const isChrome = !!(userAgent && userAgent.indexOf('Chrome') >= 0);
export const isFirefox = !!(userAgent && userAgent.indexOf('Firefox') >= 0);
export const isSafari = !!(!isChrome && (userAgent && userAgent.indexOf('Safari') >= 0));
export const isEdge = !!(userAgent && userAgent.indexOf('Edg/') >= 0);
export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0);
+58
View File
@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isMacintosh, isWindows } from './platform.js';
let safeProcess;
// Native sandbox environment
const vscodeGlobal = globalThis.vscode;
if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'undefined') {
const sandboxProcess = vscodeGlobal.process;
safeProcess = {
get platform() { return sandboxProcess.platform; },
get arch() { return sandboxProcess.arch; },
get env() { return sandboxProcess.env; },
cwd() { return sandboxProcess.cwd(); }
};
}
// Native node.js environment
else if (typeof process !== 'undefined') {
safeProcess = {
get platform() { return process.platform; },
get arch() { return process.arch; },
get env() { return process.env; },
cwd() { return process.env['VSCODE_CWD'] || process.cwd(); }
};
}
// Web environment
else {
safeProcess = {
// Supported
get platform() { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; },
get arch() { return undefined; /* arch is undefined in web */ },
// Unsupported
get env() { return {}; },
cwd() { return '/'; }
};
}
/**
* Provides safe access to the `cwd` property in node.js, sandboxed or web
* environments.
*
* Note: in web, this property is hardcoded to be `/`.
*
* @skipMangle
*/
export const cwd = safeProcess.cwd;
/**
* Provides safe access to the `env` property in node.js, sandboxed or web
* environments.
*
* Note: in web, this property is hardcoded to be `{}`.
*/
export const env = safeProcess.env;
/**
* Provides safe access to the `platform` property in node.js, sandboxed or web
* environments.
*/
export const platform = safeProcess.platform;
+44
View File
@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export var Range;
(function (Range) {
/**
* Returns the intersection between two ranges as a range itself.
* Returns `{ start: 0, end: 0 }` if the intersection is empty.
*/
function intersect(one, other) {
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 };
}
Range.intersect = intersect;
function isEmpty(range) {
return range.end - range.start <= 0;
}
Range.isEmpty = isEmpty;
function intersects(one, other) {
return !isEmpty(intersect(one, other));
}
Range.intersects = intersects;
function relativeComplement(one, other) {
const result = [];
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;
}
Range.relativeComplement = relativeComplement;
})(Range || (Range = {}));
@@ -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 * as extpath from './extpath.js';
import { Schemas } from './network.js';
import * as paths from './path.js';
import { isLinux, isWindows } from './platform.js';
import { compare as strCompare, equalsIgnoreCase } from './strings.js';
import { URI, uriToFsPath } from './uri.js';
export function originalFSPath(uri) {
return uriToFsPath(uri, true);
}
export class ExtUri {
constructor(_ignorePathCasing) {
this._ignorePathCasing = _ignorePathCasing;
}
compare(uri1, uri2, ignoreFragment = false) {
if (uri1 === uri2) {
return 0;
}
return strCompare(this.getComparisonKey(uri1, ignoreFragment), this.getComparisonKey(uri2, ignoreFragment));
}
isEqual(uri1, uri2, ignoreFragment = false) {
if (uri1 === uri2) {
return true;
}
if (!uri1 || !uri2) {
return false;
}
return this.getComparisonKey(uri1, ignoreFragment) === this.getComparisonKey(uri2, ignoreFragment);
}
getComparisonKey(uri, ignoreFragment = false) {
return uri.with({
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
fragment: ignoreFragment ? null : undefined
}).toString();
}
isEqualOrParent(base, parentCandidate, ignoreFragment = false) {
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, ...pathFragment) {
return URI.joinPath(resource, ...pathFragment);
}
basenameOrAuthority(resource) {
return basename(resource) || resource.authority;
}
basename(resource) {
return paths.posix.basename(resource.path);
}
extname(resource) {
return paths.posix.extname(resource.path);
}
dirname(resource) {
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) !== 47 /* 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) {
if (!resource.path.length) {
return resource;
}
let normalizedPath;
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, to) {
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 || '/';
const 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, path) {
if (base.scheme === Schemas.file) {
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
return base.with({
authority: newURI.authority,
path: newURI.path
});
}
path = extpath.toPosixPath(path); // we allow path to be a windows path
return base.with({
path: paths.posix.resolve(base.path, path)
});
}
// --- misc
isAbsolutePath(resource) {
return !!resource.path && resource.path[0] === '/';
}
isEqualAuthority(a1, a2) {
return a1 === a2 || (a1 !== undefined && a2 !== undefined && equalsIgnoreCase(a1, a2));
}
hasTrailingPathSeparator(resource, sep = paths.sep) {
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) === 47 /* CharCode.Slash */) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
}
}
removeTrailingPathSeparator(resource, sep = paths.sep) {
// 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, sep = paths.sep) {
let isRootSep = 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) === 47 /* 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);
/**
* Data URI related helpers.
*/
export var DataUri;
(function (DataUri) {
DataUri.META_DATA_LABEL = 'label';
DataUri.META_DATA_DESCRIPTION = 'description';
DataUri.META_DATA_SIZE = 'size';
DataUri.META_DATA_MIME = 'mime';
function parseMetaData(dataUri) {
const metadata = new Map();
// 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(DataUri.META_DATA_MIME, mime);
}
return metadata;
}
DataUri.parseMetaData = parseMetaData;
})(DataUri || (DataUri = {}));
@@ -0,0 +1,320 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from './event.js';
import { Disposable } from './lifecycle.js';
export class ScrollState {
constructor(_forceIntegerValues, width, scrollWidth, scrollLeft, height, scrollHeight, scrollTop) {
this._forceIntegerValues = _forceIntegerValues;
this._scrollStateBrand = undefined;
if (this._forceIntegerValues) {
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;
}
equals(other) {
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);
}
withScrollDimensions(update, useRawScrollPositions) {
return new ScrollState(this._forceIntegerValues, (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);
}
withScrollPosition(update) {
return new ScrollState(this._forceIntegerValues, this.width, this.scrollWidth, (typeof update.scrollLeft !== 'undefined' ? update.scrollLeft : this.rawScrollLeft), this.height, this.scrollHeight, (typeof update.scrollTop !== 'undefined' ? update.scrollTop : this.rawScrollTop));
}
createScrollEvent(previous, inSmoothScrolling) {
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 {
inSmoothScrolling: inSmoothScrolling,
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 class Scrollable extends Disposable {
constructor(options) {
super();
this._scrollableBrand = undefined;
this._onScroll = this._register(new Emitter());
this.onScroll = this._onScroll.event;
this._smoothScrollDuration = options.smoothScrollDuration;
this._scheduleAtNextAnimationFrame = options.scheduleAtNextAnimationFrame;
this._state = new ScrollState(options.forceIntegerValues, 0, 0, 0, 0, 0, 0);
this._smoothScrolling = null;
}
dispose() {
if (this._smoothScrolling) {
this._smoothScrolling.dispose();
this._smoothScrolling = null;
}
super.dispose();
}
setSmoothScrollDuration(smoothScrollDuration) {
this._smoothScrollDuration = smoothScrollDuration;
}
validateScrollPosition(scrollPosition) {
return this._state.withScrollPosition(scrollPosition);
}
getScrollDimensions() {
return this._state;
}
setScrollDimensions(dimensions, useRawScrollPositions) {
const newState = this._state.withScrollDimensions(dimensions, useRawScrollPositions);
this._setState(newState, Boolean(this._smoothScrolling));
// Validate outstanding animated scroll position target
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.
*/
getFutureScrollPosition() {
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.
*/
getCurrentScrollPosition() {
return this._state;
}
setScrollPositionNow(update) {
// 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, false);
}
setScrollPositionSmooth(update, reuseAnimation) {
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;
}
let newSmoothScrolling;
if (reuseAnimation) {
newSmoothScrolling = new SmoothScrollingOperation(this._smoothScrolling.from, validTarget, this._smoothScrolling.startTime, this._smoothScrolling.duration);
}
else {
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();
});
}
hasPendingScrollAnimation() {
return Boolean(this._smoothScrolling);
}
_performSmoothScrolling() {
if (!this._smoothScrolling) {
return;
}
const update = this._smoothScrolling.tick();
const newState = this._state.withScrollPosition(update);
this._setState(newState, true);
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();
});
}
_setState(newState, inSmoothScrolling) {
const oldState = this._state;
if (oldState.equals(newState)) {
// no change
return;
}
this._state = newState;
this._onScroll.fire(this._state.createScrollEvent(oldState, inSmoothScrolling));
}
}
export class SmoothScrollingUpdate {
constructor(scrollLeft, scrollTop, isDone) {
this.scrollLeft = scrollLeft;
this.scrollTop = scrollTop;
this.isDone = isDone;
}
}
function createEaseOutCubic(from, to) {
const delta = to - from;
return function (completion) {
return from + delta * easeOutCubic(completion);
};
}
function createComposed(a, b, cut) {
return function (completion) {
if (completion < cut) {
return a(completion / cut);
}
return b((completion - cut) / (1 - cut));
};
}
export class SmoothScrollingOperation {
constructor(from, to, startTime, duration) {
this.from = from;
this.to = to;
this.duration = duration;
this.startTime = startTime;
this.animationFrameDisposable = null;
this._initAnimations();
}
_initAnimations() {
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);
}
_initAnimation(from, to, viewportSize) {
const delta = Math.abs(from - to);
if (delta > 2.5 * viewportSize) {
let stop1, stop2;
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);
}
dispose() {
if (this.animationFrameDisposable !== null) {
this.animationFrameDisposable.dispose();
this.animationFrameDisposable = null;
}
}
acceptScrollDimensions(state) {
this.to = state.withScrollPosition(this.to);
this._initAnimations();
}
tick() {
return this._tick(Date.now());
}
_tick(now) {
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);
}
combine(from, to, duration) {
return SmoothScrollingOperation.start(from, to, duration);
}
static start(from, to, duration) {
// +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) {
return Math.pow(t, 3);
}
function easeOutCubic(t) {
return 1 - easeInCubic(1 - t);
}
+49
View File
@@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* 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.js';
export function buildReplaceStringWithCasePreserved(matches, pattern) {
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 if (matches[0][0].toUpperCase() !== matches[0][0] && pattern.length > 0) {
return pattern[0].toLowerCase() + pattern.substr(1);
}
else {
// we don't understand its pattern yet.
return pattern;
}
}
else {
return pattern;
}
}
function validateSpecificSpecialCharacter(matches, pattern, specialCharacter) {
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, pattern, specialCharacter) {
const splitPatternAtSpecialCharacter = pattern.split(specialCharacter);
const splitMatchAtSpecialCharacter = matches[0].split(specialCharacter);
let replaceString = '';
splitPatternAtSpecialCharacter.forEach((splitValue, index) => {
replaceString += buildReplaceStringWithCasePreserved([splitMatchAtSpecialCharacter[index]], splitValue) + specialCharacter;
});
return replaceString.slice(0, -1);
}
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* 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.js';
var Severity;
(function (Severity) {
Severity[Severity["Ignore"] = 0] = "Ignore";
Severity[Severity["Info"] = 1] = "Info";
Severity[Severity["Warning"] = 2] = "Warning";
Severity[Severity["Error"] = 3] = "Error";
})(Severity || (Severity = {}));
(function (Severity) {
const _error = 'error';
const _warning = 'warning';
const _warn = 'warn';
const _info = 'info';
const _ignore = 'ignore';
/**
* Parses 'error', 'warning', 'warn', 'info' in call casings
* and falls back to ignore.
*/
function fromValue(value) {
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;
}
Severity.fromValue = fromValue;
function toString(severity) {
switch (severity) {
case Severity.Error: return _error;
case Severity.Warning: return _warning;
case Severity.Info: return _info;
default: return _ignore;
}
}
Severity.toString = toString;
})(Severity || (Severity = {}));
export default Severity;
@@ -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.
*--------------------------------------------------------------------------------------------*/
const hasPerformanceNow = (globalThis.performance && typeof globalThis.performance.now === 'function');
export class StopWatch {
static create(highResolution) {
return new StopWatch(highResolution);
}
constructor(highResolution) {
this._now = hasPerformanceNow && highResolution === false ? Date.now : globalThis.performance.now.bind(globalThis.performance);
this._startTime = this._now();
this._stopTime = -1;
}
stop() {
this._stopTime = this._now();
}
reset() {
this._startTime = this._now();
this._stopTime = -1;
}
elapsed() {
if (this._stopTime !== -1) {
return this._stopTime - this._startTime;
}
return this._now() - this._startTime;
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Can be passed into the Delayed to defer using a microtask
* */
export const MicrotaskDelay = Symbol('MicrotaskDelay');
@@ -0,0 +1,618 @@
import { compare, compareIgnoreCase, compareSubstring, compareSubstringIgnoreCase } from './strings.js';
export class StringIterator {
constructor() {
this._value = '';
this._pos = 0;
}
reset(key) {
this._value = key;
this._pos = 0;
return this;
}
next() {
this._pos += 1;
return this;
}
hasNext() {
return this._pos < this._value.length - 1;
}
cmp(a) {
const aCode = a.charCodeAt(0);
const thisCode = this._value.charCodeAt(this._pos);
return aCode - thisCode;
}
value() {
return this._value[this._pos];
}
}
export class ConfigKeysIterator {
constructor(_caseSensitive = true) {
this._caseSensitive = _caseSensitive;
}
reset(key) {
this._value = key;
this._from = 0;
this._to = 0;
return this.next();
}
hasNext() {
return this._to < this._value.length;
}
next() {
// 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 === 46 /* CharCode.Period */) {
if (justSeps) {
this._from++;
}
else {
break;
}
}
else {
justSeps = false;
}
}
return this;
}
cmp(a) {
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() {
return this._value.substring(this._from, this._to);
}
}
export class PathIterator {
constructor(_splitOnBackslash = true, _caseSensitive = true) {
this._splitOnBackslash = _splitOnBackslash;
this._caseSensitive = _caseSensitive;
}
reset(key) {
this._from = 0;
this._to = 0;
this._value = key;
this._valueLen = key.length;
for (let pos = key.length - 1; pos >= 0; pos--, this._valueLen--) {
const ch = this._value.charCodeAt(pos);
if (!(ch === 47 /* CharCode.Slash */ || this._splitOnBackslash && ch === 92 /* CharCode.Backslash */)) {
break;
}
}
return this.next();
}
hasNext() {
return this._to < this._valueLen;
}
next() {
// this._data = key.split(/[\\/]/).filter(s => !!s);
this._from = this._to;
let justSeps = true;
for (; this._to < this._valueLen; this._to++) {
const ch = this._value.charCodeAt(this._to);
if (ch === 47 /* CharCode.Slash */ || this._splitOnBackslash && ch === 92 /* CharCode.Backslash */) {
if (justSeps) {
this._from++;
}
else {
break;
}
}
else {
justSeps = false;
}
}
return this;
}
cmp(a) {
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() {
return this._value.substring(this._from, this._to);
}
}
export class UriIterator {
constructor(_ignorePathCasing, _ignoreQueryAndFragment) {
this._ignorePathCasing = _ignorePathCasing;
this._ignoreQueryAndFragment = _ignoreQueryAndFragment;
this._states = [];
this._stateIdx = 0;
}
reset(key) {
this._value = key;
this._states = [];
if (this._value.scheme) {
this._states.push(1 /* UriIteratorState.Scheme */);
}
if (this._value.authority) {
this._states.push(2 /* UriIteratorState.Authority */);
}
if (this._value.path) {
this._pathIterator = new PathIterator(false, !this._ignorePathCasing(key));
this._pathIterator.reset(key.path);
if (this._pathIterator.value()) {
this._states.push(3 /* UriIteratorState.Path */);
}
}
if (!this._ignoreQueryAndFragment(key)) {
if (this._value.query) {
this._states.push(4 /* UriIteratorState.Query */);
}
if (this._value.fragment) {
this._states.push(5 /* UriIteratorState.Fragment */);
}
}
this._stateIdx = 0;
return this;
}
next() {
if (this._states[this._stateIdx] === 3 /* UriIteratorState.Path */ && this._pathIterator.hasNext()) {
this._pathIterator.next();
}
else {
this._stateIdx += 1;
}
return this;
}
hasNext() {
return (this._states[this._stateIdx] === 3 /* UriIteratorState.Path */ && this._pathIterator.hasNext())
|| this._stateIdx < this._states.length - 1;
}
cmp(a) {
if (this._states[this._stateIdx] === 1 /* UriIteratorState.Scheme */) {
return compareIgnoreCase(a, this._value.scheme);
}
else if (this._states[this._stateIdx] === 2 /* UriIteratorState.Authority */) {
return compareIgnoreCase(a, this._value.authority);
}
else if (this._states[this._stateIdx] === 3 /* UriIteratorState.Path */) {
return this._pathIterator.cmp(a);
}
else if (this._states[this._stateIdx] === 4 /* UriIteratorState.Query */) {
return compare(a, this._value.query);
}
else if (this._states[this._stateIdx] === 5 /* UriIteratorState.Fragment */) {
return compare(a, this._value.fragment);
}
throw new Error();
}
value() {
if (this._states[this._stateIdx] === 1 /* UriIteratorState.Scheme */) {
return this._value.scheme;
}
else if (this._states[this._stateIdx] === 2 /* UriIteratorState.Authority */) {
return this._value.authority;
}
else if (this._states[this._stateIdx] === 3 /* UriIteratorState.Path */) {
return this._pathIterator.value();
}
else if (this._states[this._stateIdx] === 4 /* UriIteratorState.Query */) {
return this._value.query;
}
else if (this._states[this._stateIdx] === 5 /* UriIteratorState.Fragment */) {
return this._value.fragment;
}
throw new Error();
}
}
class TernarySearchTreeNode {
constructor() {
this.height = 1;
}
rotateLeft() {
const tmp = this.right;
this.right = tmp.left;
tmp.left = this;
this.updateHeight();
tmp.updateHeight();
return tmp;
}
rotateRight() {
const tmp = this.left;
this.left = tmp.right;
tmp.right = this;
this.updateHeight();
tmp.updateHeight();
return tmp;
}
updateHeight() {
this.height = 1 + Math.max(this.heightLeft, this.heightRight);
}
balanceFactor() {
return this.heightRight - this.heightLeft;
}
get heightLeft() {
return this.left?.height ?? 0;
}
get heightRight() {
return this.right?.height ?? 0;
}
}
export class TernarySearchTree {
static forUris(ignorePathCasing = () => false, ignoreQueryAndFragment = () => false) {
return new TernarySearchTree(new UriIterator(ignorePathCasing, ignoreQueryAndFragment));
}
static forStrings() {
return new TernarySearchTree(new StringIterator());
}
static forConfigKeys() {
return new TernarySearchTree(new ConfigKeysIterator());
}
constructor(segments) {
this._iter = segments;
}
clear() {
this._root = undefined;
}
set(key, element) {
const iter = this._iter.reset(key);
let node;
if (!this._root) {
this._root = new TernarySearchTreeNode();
this._root.segment = iter.value();
}
const stack = [];
// find insert_node
node = this._root;
while (true) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
if (!node.left) {
node.left = new TernarySearchTreeNode();
node.left.segment = iter.value();
}
stack.push([-1 /* Dir.Left */, node]);
node = node.left;
}
else if (val < 0) {
// right
if (!node.right) {
node.right = new TernarySearchTreeNode();
node.right.segment = iter.value();
}
stack.push([1 /* Dir.Right */, node]);
node = node.right;
}
else if (iter.hasNext()) {
// mid
iter.next();
if (!node.mid) {
node.mid = new TernarySearchTreeNode();
node.mid.segment = iter.value();
}
stack.push([0 /* Dir.Mid */, node]);
node = node.mid;
}
else {
break;
}
}
// set value
const oldElement = node.value;
node.value = element;
node.key = key;
// balance
for (let i = stack.length - 1; i >= 0; i--) {
const node = stack[i][1];
node.updateHeight();
const bf = node.balanceFactor();
if (bf < -1 || bf > 1) {
// needs rotate
const d1 = stack[i][0];
const d2 = stack[i + 1][0];
if (d1 === 1 /* Dir.Right */ && d2 === 1 /* Dir.Right */) {
//right, right -> rotate left
stack[i][1] = node.rotateLeft();
}
else if (d1 === -1 /* Dir.Left */ && d2 === -1 /* Dir.Left */) {
// left, left -> rotate right
stack[i][1] = node.rotateRight();
}
else if (d1 === 1 /* Dir.Right */ && d2 === -1 /* Dir.Left */) {
// right, left -> double rotate right, left
node.right = stack[i + 1][1] = stack[i + 1][1].rotateRight();
stack[i][1] = node.rotateLeft();
}
else if (d1 === -1 /* Dir.Left */ && d2 === 1 /* Dir.Right */) {
// left, right -> double rotate left, right
node.left = stack[i + 1][1] = stack[i + 1][1].rotateLeft();
stack[i][1] = node.rotateRight();
}
else {
throw new Error();
}
// patch path to parent
if (i > 0) {
switch (stack[i - 1][0]) {
case -1 /* Dir.Left */:
stack[i - 1][1].left = stack[i][1];
break;
case 1 /* Dir.Right */:
stack[i - 1][1].right = stack[i][1];
break;
case 0 /* Dir.Mid */:
stack[i - 1][1].mid = stack[i][1];
break;
}
}
else {
this._root = stack[0][1];
}
}
}
return oldElement;
}
get(key) {
return this._getNode(key)?.value;
}
_getNode(key) {
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;
}
has(key) {
const node = this._getNode(key);
return !(node?.value === undefined && node?.mid === undefined);
}
delete(key) {
return this._delete(key, false);
}
deleteSuperstr(key) {
return this._delete(key, true);
}
_delete(key, superStr) {
const iter = this._iter.reset(key);
const stack = [];
let node = this._root;
// find node
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
stack.push([-1 /* Dir.Left */, node]);
node = node.left;
}
else if (val < 0) {
// right
stack.push([1 /* Dir.Right */, node]);
node = node.right;
}
else if (iter.hasNext()) {
// mid
iter.next();
stack.push([0 /* Dir.Mid */, node]);
node = node.mid;
}
else {
break;
}
}
if (!node) {
// node not found
return;
}
if (superStr) {
// removing children, reset height
node.left = undefined;
node.mid = undefined;
node.right = undefined;
node.height = 1;
}
else {
// removing element
node.key = undefined;
node.value = undefined;
}
// BST node removal
if (!node.mid && !node.value) {
if (node.left && node.right) {
// full node
// replace deleted-node with the min-node of the right branch.
// If there is no true min-node leave things as they are
const min = this._min(node.right);
if (min.key) {
const { key, value, segment } = min;
this._delete(min.key, false);
node.key = key;
node.value = value;
node.segment = segment;
}
}
else {
// empty or half empty
const newChild = node.left ?? node.right;
if (stack.length > 0) {
const [dir, parent] = stack[stack.length - 1];
switch (dir) {
case -1 /* Dir.Left */:
parent.left = newChild;
break;
case 0 /* Dir.Mid */:
parent.mid = newChild;
break;
case 1 /* Dir.Right */:
parent.right = newChild;
break;
}
}
else {
this._root = newChild;
}
}
}
// AVL balance
for (let i = stack.length - 1; i >= 0; i--) {
const node = stack[i][1];
node.updateHeight();
const bf = node.balanceFactor();
if (bf > 1) {
// right heavy
if (node.right.balanceFactor() >= 0) {
// right, right -> rotate left
stack[i][1] = node.rotateLeft();
}
else {
// right, left -> double rotate
node.right = node.right.rotateRight();
stack[i][1] = node.rotateLeft();
}
}
else if (bf < -1) {
// left heavy
if (node.left.balanceFactor() <= 0) {
// left, left -> rotate right
stack[i][1] = node.rotateRight();
}
else {
// left, right -> double rotate
node.left = node.left.rotateLeft();
stack[i][1] = node.rotateRight();
}
}
// patch path to parent
if (i > 0) {
switch (stack[i - 1][0]) {
case -1 /* Dir.Left */:
stack[i - 1][1].left = stack[i][1];
break;
case 1 /* Dir.Right */:
stack[i - 1][1].right = stack[i][1];
break;
case 0 /* Dir.Mid */:
stack[i - 1][1].mid = stack[i][1];
break;
}
}
else {
this._root = stack[0][1];
}
}
}
_min(node) {
while (node.left) {
node = node.left;
}
return node;
}
findSubstr(key) {
const iter = this._iter.reset(key);
let node = this._root;
let candidate = 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) {
return this._findSuperstrOrElement(key, false);
}
_findSuperstrOrElement(key, allowValue) {
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) {
if (allowValue) {
return node.value;
}
else {
return undefined;
}
}
else {
return this._entries(node.mid);
}
}
}
return undefined;
}
forEach(callback) {
for (const [key, value] of this) {
callback(value, key);
}
}
*[Symbol.iterator]() {
yield* this._entries(this._root);
}
_entries(node) {
const result = [];
this._dfsEntries(node, result);
return result[Symbol.iterator]();
}
_dfsEntries(node, bucket) {
// DFS
if (!node) {
return;
}
if (node.left) {
this._dfsEntries(node.left, bucket);
}
if (node.value) {
bucket.push([node.key, node.value]);
}
if (node.mid) {
this._dfsEntries(node.mid, bucket);
}
if (node.right) {
this._dfsEntries(node.right, bucket);
}
}
}
+177
View File
@@ -0,0 +1,177 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function countMapFrom(values) {
const map = new Map();
for (const value of values) {
map.set(value, (map.get(value) ?? 0) + 1);
}
return map;
}
/**
* Implementation of tf-idf (term frequency-inverse document frequency) for a set of
* documents where each document contains one or more chunks of text.
* Each document is identified by a key, and the score for each document is computed
* by taking the max score over all the chunks in the document.
*/
export class TfIdfCalculator {
constructor() {
/**
* Total number of chunks
*/
this.chunkCount = 0;
this.chunkOccurrences = new Map();
this.documents = new Map();
}
calculateScores(query, token) {
const embedding = this.computeEmbedding(query);
const idfCache = new Map();
const scores = [];
// For each document, generate one score
for (const [key, doc] of this.documents) {
if (token.isCancellationRequested) {
return [];
}
for (const chunk of doc.chunks) {
const score = this.computeSimilarityScore(chunk, embedding, idfCache);
if (score > 0) {
scores.push({ key, score });
}
}
}
return scores;
}
/**
* Count how many times each term (word) appears in a string.
*/
static termFrequencies(input) {
return countMapFrom(TfIdfCalculator.splitTerms(input));
}
/**
* Break a string into terms (words).
*/
static *splitTerms(input) {
const normalize = (word) => word.toLowerCase();
// Only match on words that are at least 3 characters long and start with a letter
for (const [word] of input.matchAll(/\b\p{Letter}[\p{Letter}\d]{2,}\b/gu)) {
yield normalize(word);
const camelParts = word.replace(/([a-z])([A-Z])/g, '$1 $2').split(/\s+/g);
if (camelParts.length > 1) {
for (const part of camelParts) {
// Require at least 3 letters in the parts of a camel case word
if (part.length > 2 && /\p{Letter}{3,}/gu.test(part)) {
yield normalize(part);
}
}
}
}
}
updateDocuments(documents) {
for (const { key } of documents) {
this.deleteDocument(key);
}
for (const doc of documents) {
const chunks = [];
for (const text of doc.textChunks) {
// TODO: See if we can compute the tf lazily
// The challenge is that we need to also update the `chunkOccurrences`
// and all of those updates need to get flushed before the real TF-IDF of
// anything is computed.
const tf = TfIdfCalculator.termFrequencies(text);
// Update occurrences list
for (const term of tf.keys()) {
this.chunkOccurrences.set(term, (this.chunkOccurrences.get(term) ?? 0) + 1);
}
chunks.push({ text, tf });
}
this.chunkCount += chunks.length;
this.documents.set(doc.key, { chunks });
}
return this;
}
deleteDocument(key) {
const doc = this.documents.get(key);
if (!doc) {
return;
}
this.documents.delete(key);
this.chunkCount -= doc.chunks.length;
// Update term occurrences for the document
for (const chunk of doc.chunks) {
for (const term of chunk.tf.keys()) {
const currentOccurrences = this.chunkOccurrences.get(term);
if (typeof currentOccurrences === 'number') {
const newOccurrences = currentOccurrences - 1;
if (newOccurrences <= 0) {
this.chunkOccurrences.delete(term);
}
else {
this.chunkOccurrences.set(term, newOccurrences);
}
}
}
}
}
computeSimilarityScore(chunk, queryEmbedding, idfCache) {
// Compute the dot product between the chunk's embedding and the query embedding
// Note that the chunk embedding is computed lazily on a per-term basis.
// This lets us skip a large number of calculations because the majority
// of chunks do not share any terms with the query.
let sum = 0;
for (const [term, termTfidf] of Object.entries(queryEmbedding)) {
const chunkTf = chunk.tf.get(term);
if (!chunkTf) {
// Term does not appear in chunk so it has no contribution
continue;
}
let chunkIdf = idfCache.get(term);
if (typeof chunkIdf !== 'number') {
chunkIdf = this.computeIdf(term);
idfCache.set(term, chunkIdf);
}
const chunkTfidf = chunkTf * chunkIdf;
sum += chunkTfidf * termTfidf;
}
return sum;
}
computeEmbedding(input) {
const tf = TfIdfCalculator.termFrequencies(input);
return this.computeTfidf(tf);
}
computeIdf(term) {
const chunkOccurrences = this.chunkOccurrences.get(term) ?? 0;
return chunkOccurrences > 0
? Math.log((this.chunkCount + 1) / chunkOccurrences)
: 0;
}
computeTfidf(termFrequencies) {
const embedding = Object.create(null);
for (const [word, occurrences] of termFrequencies) {
const idf = this.computeIdf(word);
if (idf > 0) {
embedding[word] = occurrences * idf;
}
}
return embedding;
}
}
/**
* Normalize the scores to be between 0 and 1 and sort them decending.
* @param scores array of scores from {@link TfIdfCalculator.calculateScores}
* @returns normalized scores
*/
export function normalizeTfIdfScores(scores) {
// copy of scores
const result = scores.slice(0);
// sort descending
result.sort((a, b) => b.score - a.score);
// normalize
const max = result[0]?.score ?? 0;
if (max > 0) {
for (const score of result) {
score.score /= max;
}
}
return result;
}
@@ -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 { Codicon } from './codicons.js';
export var ThemeColor;
(function (ThemeColor) {
function isThemeColor(obj) {
return obj && typeof obj === 'object' && typeof obj.id === 'string';
}
ThemeColor.isThemeColor = isThemeColor;
})(ThemeColor || (ThemeColor = {}));
export var ThemeIcon;
(function (ThemeIcon) {
ThemeIcon.iconNameSegment = '[A-Za-z0-9]+';
ThemeIcon.iconNameExpression = '[A-Za-z0-9-]+';
ThemeIcon.iconModifierExpression = '~[A-Za-z]+';
ThemeIcon.iconNameCharacter = '[A-Za-z0-9~-]';
const ThemeIconIdRegex = new RegExp(`^(${ThemeIcon.iconNameExpression})(${ThemeIcon.iconModifierExpression})?$`);
function asClassNameArray(icon) {
const match = ThemeIconIdRegex.exec(icon.id);
if (!match) {
return asClassNameArray(Codicon.error);
}
const [, id, modifier] = match;
const classNames = ['codicon', 'codicon-' + id];
if (modifier) {
classNames.push('codicon-modifier-' + modifier.substring(1));
}
return classNames;
}
ThemeIcon.asClassNameArray = asClassNameArray;
function asClassName(icon) {
return asClassNameArray(icon).join(' ');
}
ThemeIcon.asClassName = asClassName;
function asCSSSelector(icon) {
return '.' + asClassNameArray(icon).join('.');
}
ThemeIcon.asCSSSelector = asCSSSelector;
function isThemeIcon(obj) {
return obj && typeof obj === 'object' && typeof obj.id === 'string' && (typeof obj.color === 'undefined' || ThemeColor.isThemeColor(obj.color));
}
ThemeIcon.isThemeIcon = isThemeIcon;
const _regexFromString = new RegExp(`^\\$\\((${ThemeIcon.iconNameExpression}(?:${ThemeIcon.iconModifierExpression})?)\\)$`);
function fromString(str) {
const match = _regexFromString.exec(str);
if (!match) {
return undefined;
}
const [, name] = match;
return { id: name };
}
ThemeIcon.fromString = fromString;
function fromId(id) {
return { id };
}
ThemeIcon.fromId = fromId;
function modify(icon, modifier) {
let id = icon.id;
const tildeIndex = id.lastIndexOf('~');
if (tildeIndex !== -1) {
id = id.substring(0, tildeIndex);
}
if (modifier) {
id = `${id}~${modifier}`;
}
return { id };
}
ThemeIcon.modify = modify;
function getModifier(icon) {
const tildeIndex = icon.id.lastIndexOf('~');
if (tildeIndex !== -1) {
return icon.id.substring(tildeIndex + 1);
}
return undefined;
}
ThemeIcon.getModifier = getModifier;
function isEqual(ti1, ti2) {
return ti1.id === ti2.id && ti1.color?.id === ti2.color?.id;
}
ThemeIcon.isEqual = isEqual;
})(ThemeIcon || (ThemeIcon = {}));
+119
View File
@@ -0,0 +1,119 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* @returns whether the provided parameter is a JavaScript String or not.
*/
export function isString(str) {
return (typeof str === 'string');
}
/**
* @returns whether the provided parameter is of type `object` but **not**
* `null`, an `array`, a `regexp`, nor a `date`.
*/
export function isObject(obj) {
// 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);
}
/**
* @returns whether the provided parameter is of type `Buffer` or Uint8Array dervived type
*/
export function isTypedArray(obj) {
const TypedArray = Object.getPrototypeOf(Uint8Array);
return typeof obj === 'object'
&& obj instanceof TypedArray;
}
/**
* 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) {
return (typeof obj === 'number' && !isNaN(obj));
}
/**
* @returns whether the provided parameter is an Iterable, casting to the given generic
*/
export function isIterable(obj) {
return !!obj && typeof obj[Symbol.iterator] === 'function';
}
/**
* @returns whether the provided parameter is a JavaScript Boolean or not.
*/
export function isBoolean(obj) {
return (obj === true || obj === false);
}
/**
* @returns whether the provided parameter is undefined.
*/
export function isUndefined(obj) {
return (typeof obj === 'undefined');
}
/**
* @returns whether the provided parameter is defined.
*/
export function isDefined(arg) {
return !isUndefinedOrNull(arg);
}
/**
* @returns whether the provided parameter is undefined or null.
*/
export function isUndefinedOrNull(obj) {
return (isUndefined(obj) || obj === null);
}
export function assertType(condition, type) {
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(arg) {
if (isUndefinedOrNull(arg)) {
throw new Error('Assertion Failed: argument is undefined or null');
}
return arg;
}
/**
* @returns whether the provided parameter is a JavaScript Function or not.
*/
export function isFunction(obj) {
return (typeof obj === 'function');
}
export function validateConstraints(args, constraints) {
const len = Math.min(args.length, constraints.length);
for (let i = 0; i < len; i++) {
validateConstraint(args[i], constraints[i]);
}
}
export function validateConstraint(arg, constraint) {
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`);
}
}
+22
View File
@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function toUint8(v) {
if (v < 0) {
return 0;
}
if (v > 255 /* Constants.MAX_UINT_8 */) {
return 255 /* Constants.MAX_UINT_8 */;
}
return v | 0;
}
export function toUint32(v) {
if (v < 0) {
return 0;
}
if (v > 4294967295 /* Constants.MAX_UINT_32 */) {
return 4294967295 /* Constants.MAX_UINT_32 */;
}
return v | 0;
}
+602
View File
@@ -0,0 +1,602 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as paths from './path.js';
import { isWindows } from './platform.js';
const _schemePattern = /^\w[\w\d+.-]*$/;
const _singleSlashStart = /^\//;
const _doubleSlashStart = /^\/\//;
function _validateUri(ret, _strict) {
// 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, _strict) {
if (!scheme && !_strict) {
return 'file';
}
return scheme;
}
// implements a bit of https://tools.ietf.org/html/rfc3986#section-5
function _referenceResolution(scheme, path) {
// 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 {
static isUri(thing) {
if (thing instanceof URI) {
return true;
}
if (!thing) {
return false;
}
return typeof thing.authority === 'string'
&& typeof thing.fragment === 'string'
&& typeof thing.path === 'string'
&& typeof thing.query === 'string'
&& typeof thing.scheme === 'string'
&& typeof thing.fsPath === 'string'
&& typeof thing.with === 'function'
&& typeof thing.toString === 'function';
}
/**
* @internal
*/
constructor(schemeOrData, authority, path, query, fragment, _strict = 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() {
// if (this.scheme !== 'file') {
// console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`);
// }
return uriToFsPath(this, false);
}
// ---- modify to new -------------------------
with(change) {
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.example.com/some/path`,
* `file:///usr/home`, or `scheme:with/path`.
*
* @param value A string which represents an URI (see `URI#toString`).
*/
static parse(value, _strict = false) {
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) {
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);
}
/**
* Creates new URI from uri components.
*
* Unless `strict` is `true` the scheme is defaults to be `file`. This function performs
* validation and should be used for untrusted uri components retrieved from storage,
* user input, command arguments etc
*/
static from(components, strict) {
const result = new Uri(components.scheme, components.authority, components.path, components.query, components.fragment, strict);
return result;
}
/**
* 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, ...pathFragment) {
if (!uri.path) {
throw new Error(`[UriError]: cannot call joinPath on URI without path`);
}
let newPath;
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 = false) {
return _asFormatted(this, skipEncoding);
}
toJSON() {
return this;
}
static revive(data) {
if (!data) {
return data;
}
else if (data instanceof URI) {
return data;
}
else {
const result = new Uri(data);
result._formatted = data.external ?? null;
result._fsPath = data._sep === _pathSepMarker ? data.fsPath ?? null : null;
return result;
}
}
}
const _pathSepMarker = isWindows ? 1 : undefined;
// This class exists so that URI is compatible with vscode.Uri (API).
class Uri extends URI {
constructor() {
super(...arguments);
this._formatted = null;
this._fsPath = null;
}
get fsPath() {
if (!this._fsPath) {
this._fsPath = uriToFsPath(this, false);
}
return this._fsPath;
}
toString(skipEncoding = false) {
if (!skipEncoding) {
if (!this._formatted) {
this._formatted = _asFormatted(this, false);
}
return this._formatted;
}
else {
// we don't cache that
return _asFormatted(this, true);
}
}
toJSON() {
const res = {
$mid: 1 /* MarshalledId.Uri */
};
// 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;
}
// TODO
// this isn't correct and can violate the UriComponents contract but
// this is part of the vscode.Uri API and we shouldn't change how that
// works anymore
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 = {
[58 /* CharCode.Colon */]: '%3A', // gen-delims
[47 /* CharCode.Slash */]: '%2F',
[63 /* CharCode.QuestionMark */]: '%3F',
[35 /* CharCode.Hash */]: '%23',
[91 /* CharCode.OpenSquareBracket */]: '%5B',
[93 /* CharCode.CloseSquareBracket */]: '%5D',
[64 /* CharCode.AtSign */]: '%40',
[33 /* CharCode.ExclamationMark */]: '%21', // sub-delims
[36 /* CharCode.DollarSign */]: '%24',
[38 /* CharCode.Ampersand */]: '%26',
[39 /* CharCode.SingleQuote */]: '%27',
[40 /* CharCode.OpenParen */]: '%28',
[41 /* CharCode.CloseParen */]: '%29',
[42 /* CharCode.Asterisk */]: '%2A',
[43 /* CharCode.Plus */]: '%2B',
[44 /* CharCode.Comma */]: '%2C',
[59 /* CharCode.Semicolon */]: '%3B',
[61 /* CharCode.Equals */]: '%3D',
[32 /* CharCode.Space */]: '%20',
};
function encodeURIComponentFast(uriComponent, isPath, isAuthority) {
let res = 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 >= 97 /* CharCode.a */ && code <= 122 /* CharCode.z */)
|| (code >= 65 /* CharCode.A */ && code <= 90 /* CharCode.Z */)
|| (code >= 48 /* CharCode.Digit0 */ && code <= 57 /* CharCode.Digit9 */)
|| code === 45 /* CharCode.Dash */
|| code === 46 /* CharCode.Period */
|| code === 95 /* CharCode.Underline */
|| code === 126 /* CharCode.Tilde */
|| (isPath && code === 47 /* CharCode.Slash */)
|| (isAuthority && code === 91 /* CharCode.OpenSquareBracket */)
|| (isAuthority && code === 93 /* CharCode.CloseSquareBracket */)
|| (isAuthority && code === 58 /* CharCode.Colon */)) {
// 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) {
let res = undefined;
for (let pos = 0; pos < path.length; pos++) {
const code = path.charCodeAt(pos);
if (code === 35 /* CharCode.Hash */ || code === 63 /* 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, keepDriveLetterCasing) {
let value;
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) === 47 /* CharCode.Slash */
&& (uri.path.charCodeAt(1) >= 65 /* CharCode.A */ && uri.path.charCodeAt(1) <= 90 /* CharCode.Z */ || uri.path.charCodeAt(1) >= 97 /* CharCode.a */ && uri.path.charCodeAt(1) <= 122 /* CharCode.z */)
&& uri.path.charCodeAt(2) === 58 /* 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, skipEncoding) {
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.lastIndexOf(':');
if (idx === -1) {
res += encoder(userinfo, false, false);
}
else {
// <user>:<pass>@<auth>
res += encoder(userinfo.substr(0, idx), false, false);
res += ':';
res += encoder(userinfo.substr(idx + 1), false, true);
}
res += '@';
}
authority = authority.toLowerCase();
idx = authority.lastIndexOf(':');
if (idx === -1) {
res += encoder(authority, false, true);
}
else {
// <auth>:<port>
res += encoder(authority.substr(0, idx), false, true);
res += authority.substr(idx);
}
}
if (path) {
// lower-case windows drive letters in /C:/fff or C:/fff
if (path.length >= 3 && path.charCodeAt(0) === 47 /* CharCode.Slash */ && path.charCodeAt(2) === 58 /* CharCode.Colon */) {
const code = path.charCodeAt(1);
if (code >= 65 /* CharCode.A */ && code <= 90 /* CharCode.Z */) {
path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3
}
}
else if (path.length >= 2 && path.charCodeAt(1) === 58 /* CharCode.Colon */) {
const code = path.charCodeAt(0);
if (code >= 65 /* CharCode.A */ && code <= 90 /* CharCode.Z */) {
path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "/c:".length === 3
}
}
// encode the rest of the path
res += encoder(path, true, false);
}
if (query) {
res += '?';
res += encoder(query, false, false);
}
if (fragment) {
res += '#';
res += !skipEncoding ? encodeURIComponentFast(fragment, false, false) : fragment;
}
return res;
}
// --- decode
function decodeURIComponentGraceful(str) {
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) {
if (!str.match(_rEncodedAsHex)) {
return str;
}
return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
}
+56
View File
@@ -0,0 +1,56 @@
export const generateUuid = (function () {
// use `randomUUID` if possible
if (typeof crypto === 'object' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID.bind(crypto);
}
// use `randomValues` if possible
let getRandomValues;
if (typeof crypto === 'object' && typeof crypto.getRandomValues === 'function') {
getRandomValues = crypto.getRandomValues.bind(crypto);
}
else {
getRandomValues = function (bucket) {
for (let i = 0; i < bucket.length; i++) {
bucket[i] = Math.floor(Math.random() * 256);
}
return bucket;
};
}
// prep-work
const _data = new Uint8Array(16);
const _hex = [];
for (let i = 0; i < 256; i++) {
_hex.push(i.toString(16).padStart(2, '0'));
}
return function generateUuid() {
// get data
getRandomValues(_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;
};
})();
@@ -0,0 +1,441 @@
/*---------------------------------------------------------------------------------------------
* 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 '../errors.js';
import { Emitter } from '../event.js';
import { Disposable } from '../lifecycle.js';
import { getAllMethodNames } from '../objects.js';
import { isWeb } from '../platform.js';
import * as strings from '../strings.js';
const INITIALIZE = '$initialize';
let webWorkerWarningLogged = false;
export function logOnceWebWorkerWarning(err) {
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);
}
class RequestMessage {
constructor(vsWorker, req, method, args) {
this.vsWorker = vsWorker;
this.req = req;
this.method = method;
this.args = args;
this.type = 0 /* MessageType.Request */;
}
}
class ReplyMessage {
constructor(vsWorker, seq, res, err) {
this.vsWorker = vsWorker;
this.seq = seq;
this.res = res;
this.err = err;
this.type = 1 /* MessageType.Reply */;
}
}
class SubscribeEventMessage {
constructor(vsWorker, req, eventName, arg) {
this.vsWorker = vsWorker;
this.req = req;
this.eventName = eventName;
this.arg = arg;
this.type = 2 /* MessageType.SubscribeEvent */;
}
}
class EventMessage {
constructor(vsWorker, req, event) {
this.vsWorker = vsWorker;
this.req = req;
this.event = event;
this.type = 3 /* MessageType.Event */;
}
}
class UnsubscribeEventMessage {
constructor(vsWorker, req) {
this.vsWorker = vsWorker;
this.req = req;
this.type = 4 /* MessageType.UnsubscribeEvent */;
}
}
class SimpleWorkerProtocol {
constructor(handler) {
this._workerId = -1;
this._handler = handler;
this._lastSentReq = 0;
this._pendingReplies = Object.create(null);
this._pendingEmitters = new Map();
this._pendingEvents = new Map();
}
setWorkerId(workerId) {
this._workerId = workerId;
}
sendMessage(method, args) {
const req = String(++this._lastSentReq);
return new Promise((resolve, reject) => {
this._pendingReplies[req] = {
resolve: resolve,
reject: reject
};
this._send(new RequestMessage(this._workerId, req, method, args));
});
}
listen(eventName, arg) {
let req = null;
const emitter = new Emitter({
onWillAddFirstListener: () => {
req = String(++this._lastSentReq);
this._pendingEmitters.set(req, emitter);
this._send(new SubscribeEventMessage(this._workerId, req, eventName, arg));
},
onDidRemoveLastListener: () => {
this._pendingEmitters.delete(req);
this._send(new UnsubscribeEventMessage(this._workerId, req));
req = null;
}
});
return emitter.event;
}
handleMessage(message) {
if (!message || !message.vsWorker) {
return;
}
if (this._workerId !== -1 && message.vsWorker !== this._workerId) {
return;
}
this._handleMessage(message);
}
_handleMessage(msg) {
switch (msg.type) {
case 1 /* MessageType.Reply */:
return this._handleReplyMessage(msg);
case 0 /* MessageType.Request */:
return this._handleRequestMessage(msg);
case 2 /* MessageType.SubscribeEvent */:
return this._handleSubscribeEventMessage(msg);
case 3 /* MessageType.Event */:
return this._handleEventMessage(msg);
case 4 /* MessageType.UnsubscribeEvent */:
return this._handleUnsubscribeEventMessage(msg);
}
}
_handleReplyMessage(replyMessage) {
if (!this._pendingReplies[replyMessage.seq]) {
console.warn('Got reply to unknown seq');
return;
}
const 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);
}
_handleRequestMessage(requestMessage) {
const req = requestMessage.req;
const result = this._handler.handleMessage(requestMessage.method, requestMessage.args);
result.then((r) => {
this._send(new ReplyMessage(this._workerId, req, r, 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(new ReplyMessage(this._workerId, req, undefined, transformErrorForSerialization(e)));
});
}
_handleSubscribeEventMessage(msg) {
const req = msg.req;
const disposable = this._handler.handleEvent(msg.eventName, msg.arg)((event) => {
this._send(new EventMessage(this._workerId, req, event));
});
this._pendingEvents.set(req, disposable);
}
_handleEventMessage(msg) {
if (!this._pendingEmitters.has(msg.req)) {
console.warn('Got event for unknown req');
return;
}
this._pendingEmitters.get(msg.req).fire(msg.event);
}
_handleUnsubscribeEventMessage(msg) {
if (!this._pendingEvents.has(msg.req)) {
console.warn('Got unsubscribe for unknown req');
return;
}
this._pendingEvents.get(msg.req).dispose();
this._pendingEvents.delete(msg.req);
}
_send(msg) {
const transfer = [];
if (msg.type === 0 /* MessageType.Request */) {
for (let i = 0; i < msg.args.length; i++) {
if (msg.args[i] instanceof ArrayBuffer) {
transfer.push(msg.args[i]);
}
}
}
else if (msg.type === 1 /* MessageType.Reply */) {
if (msg.res instanceof ArrayBuffer) {
transfer.push(msg.res);
}
}
this._handler.sendMessage(msg, transfer);
}
}
/**
* Main thread side
*/
export class SimpleWorkerClient extends Disposable {
constructor(workerFactory, moduleId, host) {
super();
let lazyProxyReject = null;
this._worker = this._register(workerFactory.create('vs/base/common/worker/simpleWorker', (msg) => {
this._protocol.handleMessage(msg);
}, (err) => {
// in Firefox, web workers fail lazily :(
// we will reject the proxy
lazyProxyReject?.(err);
}));
this._protocol = new SimpleWorkerProtocol({
sendMessage: (msg, transfer) => {
this._worker.postMessage(msg, transfer);
},
handleMessage: (method, args) => {
if (typeof host[method] !== 'function') {
return Promise.reject(new Error('Missing method ' + method + ' on main thread host.'));
}
try {
return Promise.resolve(host[method].apply(host, args));
}
catch (e) {
return Promise.reject(e);
}
},
handleEvent: (eventName, arg) => {
if (propertyIsDynamicEvent(eventName)) {
const event = host[eventName].call(host, arg);
if (typeof event !== 'function') {
throw new Error(`Missing dynamic event ${eventName} on main thread host.`);
}
return event;
}
if (propertyIsEvent(eventName)) {
const event = host[eventName];
if (typeof event !== 'function') {
throw new Error(`Missing event ${eventName} on main thread host.`);
}
return event;
}
throw new Error(`Malformed event name ${eventName}`);
}
});
this._protocol.setWorkerId(this._worker.getId());
// Gather loader configuration
let loaderConfiguration = null;
const globalRequire = globalThis.require;
if (typeof globalRequire !== 'undefined' && typeof globalRequire.getConfig === 'function') {
// Get the configuration from the Monaco AMD Loader
loaderConfiguration = globalRequire.getConfig();
}
else if (typeof globalThis.requirejs !== 'undefined') {
// Get the configuration from requirejs
loaderConfiguration = globalThis.requirejs.s.contexts._.config;
}
const hostMethods = 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, args) => {
return this._request(method, args);
};
const proxyListen = (eventName, arg) => {
return this._protocol.listen(eventName, arg);
};
this._lazyProxy = new Promise((resolve, reject) => {
lazyProxyReject = reject;
this._onModuleLoaded.then((availableMethods) => {
resolve(createProxyObject(availableMethods, proxyMethodRequest, proxyListen));
}, (e) => {
reject(e);
this._onError('Worker failed to load ' + moduleId, e);
});
});
}
getProxyObject() {
return this._lazyProxy;
}
_request(method, args) {
return new Promise((resolve, reject) => {
this._onModuleLoaded.then(() => {
this._protocol.sendMessage(method, args).then(resolve, reject);
}, reject);
});
}
_onError(message, error) {
console.error(message);
console.info(error);
}
}
function propertyIsEvent(name) {
// Assume a property is an event if it has a form of "onSomething"
return name[0] === 'o' && name[1] === 'n' && strings.isUpperAsciiLetter(name.charCodeAt(2));
}
function propertyIsDynamicEvent(name) {
// Assume a property is a dynamic event (a method that returns an event) if it has a form of "onDynamicSomething"
return /^onDynamic/.test(name) && strings.isUpperAsciiLetter(name.charCodeAt(9));
}
function createProxyObject(methodNames, invoke, proxyListen) {
const createProxyMethod = (method) => {
return function () {
const args = Array.prototype.slice.call(arguments, 0);
return invoke(method, args);
};
};
const createProxyDynamicEvent = (eventName) => {
return function (arg) {
return proxyListen(eventName, arg);
};
};
const result = {};
for (const methodName of methodNames) {
if (propertyIsDynamicEvent(methodName)) {
result[methodName] = createProxyDynamicEvent(methodName);
continue;
}
if (propertyIsEvent(methodName)) {
result[methodName] = proxyListen(methodName, undefined);
continue;
}
result[methodName] = createProxyMethod(methodName);
}
return result;
}
/**
* Worker side
*/
export class SimpleWorkerServer {
constructor(postMessage, requestHandlerFactory) {
this._requestHandlerFactory = requestHandlerFactory;
this._requestHandler = null;
this._protocol = new SimpleWorkerProtocol({
sendMessage: (msg, transfer) => {
postMessage(msg, transfer);
},
handleMessage: (method, args) => this._handleMessage(method, args),
handleEvent: (eventName, arg) => this._handleEvent(eventName, arg)
});
}
onmessage(msg) {
this._protocol.handleMessage(msg);
}
_handleMessage(method, args) {
if (method === INITIALIZE) {
return this.initialize(args[0], args[1], args[2], 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);
}
}
_handleEvent(eventName, arg) {
if (!this._requestHandler) {
throw new Error(`Missing requestHandler`);
}
if (propertyIsDynamicEvent(eventName)) {
const event = this._requestHandler[eventName].call(this._requestHandler, arg);
if (typeof event !== 'function') {
throw new Error(`Missing dynamic event ${eventName} on request handler.`);
}
return event;
}
if (propertyIsEvent(eventName)) {
const event = this._requestHandler[eventName];
if (typeof event !== 'function') {
throw new Error(`Missing event ${eventName} on request handler.`);
}
return event;
}
throw new Error(`Malformed event name ${eventName}`);
}
initialize(workerId, loaderConfig, moduleId, hostMethods) {
this._protocol.setWorkerId(workerId);
const proxyMethodRequest = (method, args) => {
return this._protocol.sendMessage(method, args);
};
const proxyListen = (eventName, arg) => {
return this._protocol.listen(eventName, arg);
};
const hostProxy = createProxyObject(hostMethods, proxyMethodRequest, proxyListen);
if (this._requestHandlerFactory) {
// static request handler
this._requestHandler = this._requestHandlerFactory(hostProxy);
return Promise.resolve(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'];
}
}
if (typeof loaderConfig.trustedTypesPolicy !== 'undefined') {
// don't use, it has been destroyed during serialize
delete loaderConfig['trustedTypesPolicy'];
}
// Since this is in a web worker, enable catching errors
loaderConfig.catchError = true;
globalThis.require.config(loaderConfig);
}
return new Promise((resolve, reject) => {
// Use the global require to be sure to get the global config
// ESM-comment-begin
// const req = (globalThis.require || require);
// ESM-comment-end
// ESM-uncomment-begin
const req = globalThis.require;
// ESM-uncomment-end
req([moduleId], (module) => {
this._requestHandler = module.create(hostProxy);
if (!this._requestHandler) {
reject(new Error(`No RequestHandler!`));
return;
}
resolve(getAllMethodNames(this._requestHandler));
}, reject);
});
}
}
/**
* Called on the worker side
* @skipMangle
*/
export function create(postMessage) {
return new SimpleWorkerServer(postMessage, null);
}