Version 1.0

This commit is contained in:
Alexander R.
2026-07-21 22:24:25 +00:00
parent f3645fbfbc
commit 9edbfd3f77
4134 changed files with 1448752 additions and 1 deletions
+180
View File
@@ -0,0 +1,180 @@
import { Emitter } from './event.js';
import { Disposable } from './lifecycle.js';
import { localize } from '../../nls.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* A concrete implementation of {@link IAction}.
*
* Note that in most cases you should use the lighter-weight {@linkcode toAction} function instead.
*/
class Action extends Disposable {
get onDidChange() { return this._onDidChange.event; }
constructor(id, label = '', cssClass = '', enabled = true, actionCallback) {
super();
this._onDidChange = this._register(new Emitter());
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);
}
}
}
class ActionRunner extends Disposable {
constructor() {
super(...arguments);
this._onWillRun = this._register(new Emitter());
this._onDidRun = this._register(new Emitter());
}
get onWillRun() { return this._onWillRun.event; }
get onDidRun() { return 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);
}
}
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) ;
else if (out.length) {
out = [...out, new Separator(), ...list];
}
else {
out = list;
}
}
return out;
}
static { this.ID = 'vs.actions.separator'; }
async run() { }
}
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() { }
}
class EmptySubmenuAction extends Action {
static { this.ID = 'vs.actions.empty'; }
constructor() {
super(EmptySubmenuAction.ID, localize(28, '(empty)'), undefined, false);
}
}
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),
};
}
export { Action, ActionRunner, EmptySubmenuAction, Separator, SubmenuAction, toAction };
+529
View File
@@ -0,0 +1,529 @@
/**
* Returns the last entry and the initial N-1 entries of the array, as a tuple of [rest, last].
*
* The array must have at least one element.
*
* @param arr The input array
* @returns A tuple of [rest, last] where rest is all but the last element and last is the last element
* @throws Error if the array is empty
*/
function tail(arr) {
if (arr.length === 0) {
throw new Error('Invalid tail call');
}
return [arr.slice(0, arr.length - 1), arr[arr.length - 1]];
}
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
*/
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}
*/
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.
*/
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);
}
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);
}
}
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.
*/
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;
}
}
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]);
}
}
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.
*/
function coalesce(array) {
return array.filter((e) => !!e);
}
/**
* Remove all falsy values from `array`. The original array IS modified.
*/
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.
*/
function isFalsyOrEmpty(obj) {
return !Array.isArray(obj) || obj.length === 0;
}
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.
*/
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;
});
}
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
*/
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.
*/
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.
*/
function pushToEnd(arr, value) {
const index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
arr.push(value);
}
}
function pushMany(arr, items) {
for (const item of items) {
arr.push(item);
}
}
function mapFilter(array, fn) {
const result = [];
for (const item of array) {
const mapped = fn(item);
if (mapped !== undefined) {
result.push(mapped);
}
}
return result;
}
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
*/
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.
*/
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);
}
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 = {}));
function compareBy(selector, comparator) {
return (a, b) => comparator(selector(a), selector(b));
}
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.
*/
const numberComparator = (a, b) => a - b;
const booleanComparator = (a, b) => numberComparator(a ? 1 : 0, b ? 1 : 0);
function reverseOrder(comparator) {
return (a, b) => -comparator(a, b);
}
/**
* Returns a new comparator that treats `undefined` as the smallest value.
* All other values are compared using the given comparator.
*/
function compareUndefinedSmallest(comparator) {
return (a, b) => {
if (a === undefined) {
return b === undefined ? CompareResult.neitherLessOrGreaterThan : CompareResult.lessThan;
}
else if (b === undefined) {
return CompareResult.greaterThan;
}
return comparator(a, b);
};
}
class ArrayQueue {
/**
* Constructs a queue that is backed by the given array. Runtime is O(1).
*/
constructor(items) {
this.firstIdx = 0;
this.items = items;
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.
*/
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.
*/
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);
}
}
function sum(array) {
return array.reduce((acc, value) => acc + value, 0);
}
export { ArrayQueue, CallbackIterable, CompareResult, Permutation, arrayInsert, asArray, binarySearch, binarySearch2, booleanComparator, coalesce, coalesceInPlace, compareBy, compareUndefinedSmallest, distinct, equals, forEachAdjacent, forEachWithNeighbors, groupAdjacentBy, groupBy, insertInto, isFalsyOrEmpty, isNonEmptyArray, mapFilter, numberComparator, pushMany, pushToEnd, pushToStart, quickSelect, range, removeFastWithoutKeepingOrder, reverseOrder, splice, sum, tail, tieBreakComparators };
+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 findLast(array, predicate, fromIndex = array.length - 1) {
const idx = findLastIdx(array, predicate, fromIndex);
if (idx === -1) {
return undefined;
}
return array[idx];
}
function findLastIdx(array, predicate, fromIndex = array.length - 1) {
for (let i = fromIndex; i >= 0; i--) {
const element = array[i];
if (predicate(element, i)) {
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
function findFirstMin(array, comparator) {
return findFirstMax(array, (a, b) => -comparator(a, b));
}
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.
*/
function mapFindFirst(items, mapFn) {
for (const value of items) {
const mapped = mapFn(value);
if (mapped !== undefined) {
return mapped;
}
}
return undefined;
}
export { MonotonousArray, findFirstIdxMonotonousOrArrLen, findFirstMax, findFirstMin, findFirstMonotonous, findLast, findLastIdx, findLastIdxMonotonous, findLastMax, findLastMonotonous, findMaxIdx, mapFindFirst };
+79
View File
@@ -0,0 +1,79 @@
import { onUnexpectedError, BugIndicatingError } from './errors.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Throws an error with the provided message if the provided value does not evaluate to a true Javascript value.
*
* @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.
*/
function ok(value, message) {
if (!value) {
throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed');
}
}
function assertNever(value, message = 'Unreachable') {
throw new Error(message);
}
/**
* Asserts that a condition is `truthy`.
*
* @throws provided {@linkcode messageOrError} if the {@linkcode condition} is `falsy`.
*
* @param condition The condition to assert.
* @param messageOrError An error message or error object to throw if condition is `falsy`.
*/
function assert(condition, messageOrError = 'unexpected state') {
if (!condition) {
// if error instance is provided, use it, otherwise create a new one
const errorToThrow = typeof messageOrError === 'string'
? new BugIndicatingError(`Assertion Failed: ${messageOrError}`)
: messageOrError;
throw errorToThrow;
}
}
/**
* Like assert, but doesn't throw.
*/
function softAssert(condition, message = 'Soft Assertion Failed') {
if (!condition) {
onUnexpectedError(new BugIndicatingError(message));
}
}
/**
* condition must be side-effect free!
*/
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'));
}
}
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;
}
export { assert, assertFn, assertNever, checkAdjacentItems, ok, softAssert };
+938
View File
@@ -0,0 +1,938 @@
import { CancellationTokenSource } from './cancellation.js';
import { BugIndicatingError, CancellationError } from './errors.js';
import { toDisposable, isDisposable } from './lifecycle.js';
import { setTimeout0 } from './platform.js';
import { MicrotaskDelay } from './symbols.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function isThenable(obj) {
return !!obj && typeof obj.then === 'function';
}
/**
* Returns a promise that can be cancelled using the provided cancellation token.
*
* @remarks When cancellation is requested, the promise will be rejected with a {@link CancellationError}.
* If the promise resolves to a disposable object, it will be automatically disposed when cancellation
* is requested.
*
* @param callback A function that accepts a cancellation token and returns a promise
* @returns A promise that can be cancelled
*/
function createCancelablePromise(callback) {
const source = new CancellationTokenSource();
const thenable = callback(source.token);
let isCancelled = false;
const promise = new Promise((resolve, reject) => {
const subscription = source.token.onCancellationRequested(() => {
isCancelled = true;
subscription.dispose();
reject(new CancellationError());
});
Promise.resolve(thenable).then(value => {
subscription.dispose();
source.dispose();
if (!isCancelled) {
resolve(value);
}
else if (isDisposable(value)) {
// promise has been cancelled, result is disposable and will
// be cleaned up
value.dispose();
}
}, 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);
}
};
}
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());
});
}
/**
* Returns a promise that rejects with an {@CancellationError} as soon as the passed token is cancelled.
* @see {@link raceCancellation}
*/
function raceCancellationError(promise, token) {
return new Promise((resolve, reject) => {
const ref = token.onCancellationRequested(() => {
ref.dispose();
reject(new CancellationError());
});
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);
* }
*/
class Throttler {
constructor() {
this.activePromise = null;
this.queuedPromise = null;
this.queuedPromiseFactory = null;
this.cancellationTokenSource = new CancellationTokenSource();
}
queue(promiseFactory) {
if (this.cancellationTokenSource.token.isCancellationRequested) {
return Promise.reject(new Error('Throttler is disposed'));
}
if (this.activePromise) {
this.queuedPromiseFactory = promiseFactory;
if (!this.queuedPromise) {
const onComplete = () => {
this.queuedPromise = null;
if (this.cancellationTokenSource.token.isCancellationRequested) {
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(this.cancellationTokenSource.token);
return new Promise((resolve, reject) => {
this.activePromise.then((result) => {
this.activePromise = null;
resolve(result);
}, (err) => {
this.activePromise = null;
reject(err);
});
});
}
dispose() {
this.cancellationTokenSource.cancel();
}
}
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(); });
* }
*/
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.
*/
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();
}
}
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();
* }
*/
function disposableTimeout(handler, timeout = 0, store) {
const timer = setTimeout(() => {
handler();
if (store) {
disposable.dispose();
}
}, timeout);
const disposable = toDisposable(() => {
clearTimeout(timer);
store?.delete(disposable);
});
store?.add(disposable);
return disposable;
}
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();
}
/**
* Processes tasks in the order they were scheduled.
*/
class TaskQueue {
constructor() {
this._runningTask = undefined;
this._pendingTasks = [];
}
/**
* Waits for the current and pending tasks to finish, then runs and awaits the given task.
* If the task is skipped because of clearPending, the promise is rejected with a CancellationError.
*/
schedule(task) {
const deferred = new DeferredPromise();
this._pendingTasks.push({ task, deferred, setUndefinedWhenCleared: false });
this._runIfNotRunning();
return deferred.p;
}
_runIfNotRunning() {
if (this._runningTask === undefined) {
this._processQueue();
}
}
async _processQueue() {
if (this._pendingTasks.length === 0) {
return;
}
const next = this._pendingTasks.shift();
if (!next) {
return;
}
if (this._runningTask) {
throw new BugIndicatingError();
}
this._runningTask = next.task;
try {
const result = await next.task();
next.deferred.complete(result);
}
catch (e) {
next.deferred.error(e);
}
finally {
this._runningTask = undefined;
this._processQueue();
}
}
/**
* Clears all pending tasks. Does not cancel the currently running task.
*/
clearPending() {
const tasks = this._pendingTasks;
this._pendingTasks = [];
for (const task of tasks) {
if (task.setUndefinedWhenCleared) {
task.deferred.complete(undefined);
}
else {
task.deferred.error(new CancellationError());
}
}
}
}
class TimeoutTimer {
constructor(runner, timeout) {
this._isDisposed = false;
this._token = undefined;
if (typeof runner === 'function' && typeof timeout === 'number') {
this.setIfNotSet(runner, timeout);
}
}
dispose() {
this.cancel();
this._isDisposed = true;
}
cancel() {
if (this._token !== undefined) {
clearTimeout(this._token);
this._token = undefined;
}
}
cancelAndSet(runner, timeout) {
if (this._isDisposed) {
throw new BugIndicatingError(`Calling 'cancelAndSet' on a disposed TimeoutTimer`);
}
this.cancel();
this._token = setTimeout(() => {
this._token = undefined;
runner();
}, timeout);
}
setIfNotSet(runner, timeout) {
if (this._isDisposed) {
throw new BugIndicatingError(`Calling 'setIfNotSet' on a disposed TimeoutTimer`);
}
if (this._token !== undefined) {
// timer is already set
return;
}
this._token = setTimeout(() => {
this._token = undefined;
runner();
}, timeout);
}
}
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;
}
}
class RunOnceScheduler {
constructor(runner, delay) {
this.timeoutToken = undefined;
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 = undefined;
}
}
/**
* 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 !== undefined;
}
onTimeout() {
this.timeoutToken = undefined;
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
*/
let runWhenGlobalIdle;
let _runWhenIdle;
(function () {
const safeGlobal = globalThis;
if (typeof safeGlobal.requestIdleCallback !== 'function' || typeof safeGlobal.cancelIdleCallback !== 'function') {
_runWhenIdle = (_targetWindow, runner, timeout) => {
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, timeout) => _runWhenIdle(globalThis, runner, timeout);
})();
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
*/
class GlobalIdleValue extends AbstractIdleValue {
constructor(executor) {
super(globalThis, executor);
}
}
/**
* Creates a promise whose resolution or rejection can be controlled imperatively.
*/
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) {
if (this.isSettled) {
return Promise.resolve();
}
return new Promise(resolve => {
this.completeCallback(value);
this.outcome = { outcome: 0 /* DeferredOutcome.Resolved */, value };
resolve();
});
}
error(err) {
if (this.isSettled) {
return Promise.resolve();
}
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
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 = {}));
function createCancelableAsyncIterableProducer(callback) {
const source = new CancellationTokenSource();
const innerIterable = callback(source.token);
return new CancelableAsyncIterableProducer(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);
}
});
}
class ProducerConsumer {
constructor() {
this._unsatisfiedConsumers = [];
this._unconsumedValues = [];
}
get hasFinalValue() {
return !!this._finalValue;
}
produce(value) {
this._ensureNoFinalValue();
if (this._unsatisfiedConsumers.length > 0) {
const deferred = this._unsatisfiedConsumers.shift();
this._resolveOrRejectDeferred(deferred, value);
}
else {
this._unconsumedValues.push(value);
}
}
produceFinal(value) {
this._ensureNoFinalValue();
this._finalValue = value;
for (const deferred of this._unsatisfiedConsumers) {
this._resolveOrRejectDeferred(deferred, value);
}
this._unsatisfiedConsumers.length = 0;
}
_ensureNoFinalValue() {
if (this._finalValue) {
throw new BugIndicatingError('ProducerConsumer: cannot produce after final value has been set');
}
}
_resolveOrRejectDeferred(deferred, value) {
if (value.ok) {
deferred.complete(value.value);
}
else {
deferred.error(value.error);
}
}
consume() {
if (this._unconsumedValues.length > 0 || this._finalValue) {
const value = this._unconsumedValues.length > 0 ? this._unconsumedValues.shift() : this._finalValue;
if (value.ok) {
return Promise.resolve(value.value);
}
else {
return Promise.reject(value.error);
}
}
else {
const deferred = new DeferredPromise();
this._unsatisfiedConsumers.push(deferred);
return deferred.p;
}
}
}
/**
* Important difference to AsyncIterableObject:
* If it is iterated two times, the second iterator will not see the values emitted by the first iterator.
*/
class AsyncIterableProducer {
constructor(executor, _onReturn) {
this._onReturn = _onReturn;
this._producerConsumer = new ProducerConsumer();
this._iterator = {
next: () => this._producerConsumer.consume(),
return: () => {
this._onReturn?.();
return Promise.resolve({ done: true, value: undefined });
},
throw: async (e) => {
this._finishError(e);
return { done: true, value: undefined };
},
};
queueMicrotask(async () => {
const p = executor({
emitOne: value => this._producerConsumer.produce({ ok: true, value: { done: false, value: value } }),
emitMany: values => {
for (const value of values) {
this._producerConsumer.produce({ ok: true, value: { done: false, value: value } });
}
},
reject: error => this._finishError(error),
});
if (!this._producerConsumer.hasFinalValue) {
try {
await p;
this._finishOk();
}
catch (error) {
this._finishError(error);
}
}
});
}
static fromArray(items) {
return new AsyncIterableProducer((writer) => {
writer.emitMany(items);
});
}
static fromPromise(promise) {
return new AsyncIterableProducer(async (emitter) => {
emitter.emitMany(await promise);
});
}
static fromPromisesResolveOrder(promises) {
return new AsyncIterableProducer(async (emitter) => {
await Promise.all(promises.map(async (p) => emitter.emitOne(await p)));
});
}
static merge(iterables) {
return new AsyncIterableProducer(async (emitter) => {
await Promise.all(iterables.map(async (iterable) => {
for await (const item of iterable) {
emitter.emitOne(item);
}
}));
});
}
static { this.EMPTY = AsyncIterableProducer.fromArray([]); }
static map(iterable, mapFn) {
return new AsyncIterableProducer(async (emitter) => {
for await (const item of iterable) {
emitter.emitOne(mapFn(item));
}
});
}
map(mapFn) {
return AsyncIterableProducer.map(this, mapFn);
}
static coalesce(iterable) {
return AsyncIterableProducer.filter(iterable, item => !!item);
}
coalesce() {
return AsyncIterableProducer.coalesce(this);
}
static filter(iterable, filterFn) {
return new AsyncIterableProducer(async (emitter) => {
for await (const item of iterable) {
if (filterFn(item)) {
emitter.emitOne(item);
}
}
});
}
filter(filterFn) {
return AsyncIterableProducer.filter(this, filterFn);
}
_finishOk() {
if (!this._producerConsumer.hasFinalValue) {
this._producerConsumer.produceFinal({ ok: true, value: { done: true, value: undefined } });
}
}
_finishError(error) {
if (!this._producerConsumer.hasFinalValue) {
this._producerConsumer.produceFinal({ ok: false, error: error });
}
// Warning: this can cause to dropped errors.
}
[Symbol.asyncIterator]() {
return this._iterator;
}
}
class CancelableAsyncIterableProducer extends AsyncIterableProducer {
constructor(_source, executor) {
super(executor);
this._source = _source;
}
cancel() {
this._source.cancel();
}
}
export { AbstractIdleValue, AsyncIterableProducer, CancelableAsyncIterableProducer, DeferredPromise, Delayer, GlobalIdleValue, IntervalTimer, Promises, RunOnceScheduler, TaskQueue, ThrottledDelayer, Throttler, TimeoutTimer, _runWhenIdle, createCancelableAsyncIterableProducer, createCancelablePromise, disposableTimeout, first, isThenable, raceCancellation, raceCancellationError, runWhenGlobalIdle, timeout };
+81
View File
@@ -0,0 +1,81 @@
import { Lazy } from './lazy.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const hasBuffer = (typeof Buffer !== 'undefined');
new Lazy(() => new Uint8Array(256));
let textDecoder;
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);
}
}
}
function readUInt16LE(source, offset) {
return (((source[offset + 0] << 0) >>> 0) |
((source[offset + 1] << 8) >>> 0));
}
function writeUInt16LE(destination, value, offset) {
destination[offset + 0] = (value & 0b11111111);
value = value >>> 8;
destination[offset + 1] = (value & 0b11111111);
}
function readUInt32BE(source, offset) {
return (source[offset] * 2 ** 24
+ source[offset + 1] * 2 ** 16
+ source[offset + 2] * 2 ** 8
+ source[offset + 3]);
}
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;
}
function readUInt8(source, offset) {
return source[offset];
}
function writeUInt8(destination, value, offset) {
destination[offset] = value;
}
const hexChars = '0123456789abcdef';
function encodeHex({ buffer }) {
let result = '';
for (let i = 0; i < buffer.length; i++) {
const byte = buffer[i];
result += hexChars[byte >>> 4];
result += hexChars[byte & 0x0f];
}
return result;
}
export { VSBuffer, encodeHex, readUInt16LE, readUInt32BE, readUInt8, writeUInt16LE, writeUInt32BE, writeUInt8 };
+61
View File
@@ -0,0 +1,61 @@
function identity(t) {
return t;
}
/**
* Uses a LRU cache to make a given parametrized function cached.
* Caches just the last key/value.
*/
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.
*/
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;
}
}
export { CachedFunction, LRUCachedFunction, identity };
+116
View File
@@ -0,0 +1,116 @@
import { Event, Emitter } from './event.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const shortcutEvent = Object.freeze(function (callback, context) {
const handle = setTimeout(callback.bind(context), 0);
return { dispose() { clearTimeout(handle); } };
});
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;
}
}
}
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();
}
}
}
function cancelOnDispose(store) {
const source = new CancellationTokenSource();
store.add({ dispose() { source.cancel(); } });
return source.token;
}
export { CancellationToken, CancellationTokenSource, cancelOnDispose };
+50
View File
@@ -0,0 +1,50 @@
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.
*/
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.
*/
const Codicon = {
...codiconsLibrary,
...codiconsDerived
};
export { Codicon, codiconsDerived };
@@ -0,0 +1,642 @@
import { register } from './codiconsUtil.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// 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.
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),
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),
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),
goToEditingSession: register('go-to-editing-session', 0xec35),
editSession: register('edit-session', 0xec36),
codeReview: register('code-review', 0xec37),
copilotWarning: register('copilot-warning', 0xec38),
python: register('python', 0xec39),
copilotLarge: register('copilot-large', 0xec3a),
copilotWarningLarge: register('copilot-warning-large', 0xec3b),
keyboardTab: register('keyboard-tab', 0xec3c),
copilotBlocked: register('copilot-blocked', 0xec3d),
copilotNotConnected: register('copilot-not-connected', 0xec3e),
flag: register('flag', 0xec3f),
lightbulbEmpty: register('lightbulb-empty', 0xec40),
symbolMethodArrow: register('symbol-method-arrow', 0xec41),
copilotUnavailable: register('copilot-unavailable', 0xec42),
repoPinned: register('repo-pinned', 0xec43),
keyboardTabAbove: register('keyboard-tab-above', 0xec44),
keyboardTabBelow: register('keyboard-tab-below', 0xec45),
gitPullRequestDone: register('git-pull-request-done', 0xec46),
mcp: register('mcp', 0xec47),
extensionsLarge: register('extensions-large', 0xec48),
layoutPanelDock: register('layout-panel-dock', 0xec49),
layoutSidebarLeftDock: register('layout-sidebar-left-dock', 0xec4a),
layoutSidebarRightDock: register('layout-sidebar-right-dock', 0xec4b),
copilotInProgress: register('copilot-in-progress', 0xec4c),
copilotError: register('copilot-error', 0xec4d),
copilotSuccess: register('copilot-success', 0xec4e),
chatSparkle: register('chat-sparkle', 0xec4f),
searchSparkle: register('search-sparkle', 0xec50),
editSparkle: register('edit-sparkle', 0xec51),
copilotSnooze: register('copilot-snooze', 0xec52),
sendToRemoteAgent: register('send-to-remote-agent', 0xec53),
commentDiscussionSparkle: register('comment-discussion-sparkle', 0xec54),
chatSparkleWarning: register('chat-sparkle-warning', 0xec55),
chatSparkleError: register('chat-sparkle-error', 0xec56),
collection: register('collection', 0xec57),
newCollection: register('new-collection', 0xec58),
thinking: register('thinking', 0xec59),
build: register('build', 0xec5a),
commentDiscussionQuote: register('comment-discussion-quote', 0xec5b),
cursor: register('cursor', 0xec5c),
eraser: register('eraser', 0xec5d),
fileText: register('file-text', 0xec5e),
gitLens: register('git-lens', 0xec5f),
quotes: register('quotes', 0xec60),
rename: register('rename', 0xec61),
runWithDeps: register('run-with-deps', 0xec62),
debugConnected: register('debug-connected', 0xec63),
strikethrough: register('strikethrough', 0xec64),
openInProduct: register('open-in-product', 0xec65),
indexZero: register('index-zero', 0xec66),
agent: register('agent', 0xec67),
editCode: register('edit-code', 0xec68),
repoSelected: register('repo-selected', 0xec69),
skip: register('skip', 0xec6a),
mergeInto: register('merge-into', 0xec6b),
gitBranchChanges: register('git-branch-changes', 0xec6c),
gitBranchStagedChanges: register('git-branch-staged-changes', 0xec6d),
gitBranchConflicts: register('git-branch-conflicts', 0xec6e),
gitBranch: register('git-branch', 0xec6f),
gitBranchCreate: register('git-branch-create', 0xec6f),
gitBranchDelete: register('git-branch-delete', 0xec6f),
searchLarge: register('search-large', 0xec70),
terminalGitBash: register('terminal-git-bash', 0xec71),
};
export { codiconsLibrary };
+22
View File
@@ -0,0 +1,22 @@
import { isString } from './types.js';
const _codiconFontCharacters = Object.create(null);
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.
*/
function getCodiconFontCharacters() {
return _codiconFontCharacters;
}
export { getCodiconFontCharacters, register };
+98
View File
@@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var _a;
function groupByMap(data, groupFn) {
const result = new Map();
for (const element of data) {
const key = groupFn(element);
let target = result.get(key);
if (!target) {
target = [];
result.set(key, target);
}
target.push(element);
}
return result;
}
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`.
*/
function intersection(setA, setB) {
const result = new Set();
for (const elem of setB) {
if (setA.has(elem)) {
result.add(elem);
}
}
return result;
}
class SetWithKey {
static { _a = Symbol.toStringTag; }
constructor(values, toKey) {
this.toKey = toKey;
this._map = new Map();
this[_a] = 'SetWithKey';
for (const value of values) {
this.add(value);
}
}
get size() {
return this._map.size;
}
add(value) {
const key = this.toKey(value);
this._map.set(key, value);
return this;
}
delete(value) {
return this._map.delete(this.toKey(value));
}
has(value) {
return this._map.has(this.toKey(value));
}
*entries() {
for (const entry of this._map.values()) {
yield [entry, entry];
}
}
keys() {
return this.values();
}
*values() {
for (const entry of this._map.values()) {
yield entry;
}
}
clear() {
this._map.clear();
}
forEach(callbackfn, thisArg) {
this._map.forEach(entry => callbackfn.call(thisArg, entry, entry, this));
}
[Symbol.iterator]() {
return this.values();
}
}
export { SetWithKey, diffSets, groupByMap, intersection };
+678
View File
@@ -0,0 +1,678 @@
/*---------------------------------------------------------------------------------------------
* 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;
}
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;
}
}
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);
}
}
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);
}
}
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));
}
/**
* Mixes the current color with the provided color based on the given factor.
* @param color The color to mix with
* @param factor The factor of mixing (0 means this color, 1 means the input color, 0.5 means equal mix)
* @returns A new color representing the mix
*/
mix(color, factor = 0.5) {
const normalize = Math.min(Math.max(factor, 0), 1);
const thisRGBA = this.rgba;
const otherRGBA = color.rgba;
const r = thisRGBA.r + (otherRGBA.r - thisRGBA.r) * normalize;
const g = thisRGBA.g + (otherRGBA.g - thisRGBA.g) * normalize;
const b = thisRGBA.b + (otherRGBA.b - thisRGBA.b) * normalize;
const a = thisRGBA.a + (otherRGBA.a - thisRGBA.a) * normalize;
return new Color(new RGBA(r, g, b, 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;
}
toNumber32Bit() {
if (!this._toNumber32Bit) {
this._toNumber32Bit = (this.rgba.r /* */ << 24 |
this.rgba.g /* */ << 16 |
this.rgba.b /* */ << 8 |
this.rgba.a * 0xFF << 0) >>> 0;
}
return this._toNumber32Bit;
}
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) {
(function (Format) {
(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}, ${Math.round(color.hsla.s * 100)}%, ${Math.round(color.hsla.l * 100)}%)`;
}
return Color.Format.CSS.formatHSLA(color);
}
CSS.formatHSL = formatHSL;
function formatHSLA(color) {
return `hsla(${color.hsla.h}, ${Math.round(color.hsla.s * 100)}%, ${Math.round(color.hsla.l * 100)}%, ${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;
/**
* Parse a CSS color and return a {@link Color}.
* @param css The CSS color to parse.
* @see https://drafts.csswg.org/css-color/#typedef-color
*/
function parse(css) {
if (css === 'transparent') {
return Color.transparent;
}
if (css.startsWith('#')) {
return parseHex(css);
}
if (css.startsWith('rgba(')) {
const color = css.match(/rgba\((?<r>(?:\+|-)?\d+), *(?<g>(?:\+|-)?\d+), *(?<b>(?:\+|-)?\d+), *(?<a>(?:\+|-)?\d+(\.\d+)?)\)/);
if (!color) {
throw new Error('Invalid color format ' + css);
}
const r = parseInt(color.groups?.r ?? '0');
const g = parseInt(color.groups?.g ?? '0');
const b = parseInt(color.groups?.b ?? '0');
const a = parseFloat(color.groups?.a ?? '0');
return new Color(new RGBA(r, g, b, a));
}
if (css.startsWith('rgb(')) {
const color = css.match(/rgb\((?<r>(?:\+|-)?\d+), *(?<g>(?:\+|-)?\d+), *(?<b>(?:\+|-)?\d+)\)/);
if (!color) {
throw new Error('Invalid color format ' + css);
}
const r = parseInt(color.groups?.r ?? '0');
const g = parseInt(color.groups?.g ?? '0');
const b = parseInt(color.groups?.b ?? '0');
return new Color(new RGBA(r, g, b));
}
// TODO: Support more formats as needed
return parseNamedKeyword(css);
}
CSS.parse = parse;
function parseNamedKeyword(css) {
// https://drafts.csswg.org/css-color/#named-colors
switch (css) {
case 'aliceblue': return new Color(new RGBA(240, 248, 255, 1));
case 'antiquewhite': return new Color(new RGBA(250, 235, 215, 1));
case 'aqua': return new Color(new RGBA(0, 255, 255, 1));
case 'aquamarine': return new Color(new RGBA(127, 255, 212, 1));
case 'azure': return new Color(new RGBA(240, 255, 255, 1));
case 'beige': return new Color(new RGBA(245, 245, 220, 1));
case 'bisque': return new Color(new RGBA(255, 228, 196, 1));
case 'black': return new Color(new RGBA(0, 0, 0, 1));
case 'blanchedalmond': return new Color(new RGBA(255, 235, 205, 1));
case 'blue': return new Color(new RGBA(0, 0, 255, 1));
case 'blueviolet': return new Color(new RGBA(138, 43, 226, 1));
case 'brown': return new Color(new RGBA(165, 42, 42, 1));
case 'burlywood': return new Color(new RGBA(222, 184, 135, 1));
case 'cadetblue': return new Color(new RGBA(95, 158, 160, 1));
case 'chartreuse': return new Color(new RGBA(127, 255, 0, 1));
case 'chocolate': return new Color(new RGBA(210, 105, 30, 1));
case 'coral': return new Color(new RGBA(255, 127, 80, 1));
case 'cornflowerblue': return new Color(new RGBA(100, 149, 237, 1));
case 'cornsilk': return new Color(new RGBA(255, 248, 220, 1));
case 'crimson': return new Color(new RGBA(220, 20, 60, 1));
case 'cyan': return new Color(new RGBA(0, 255, 255, 1));
case 'darkblue': return new Color(new RGBA(0, 0, 139, 1));
case 'darkcyan': return new Color(new RGBA(0, 139, 139, 1));
case 'darkgoldenrod': return new Color(new RGBA(184, 134, 11, 1));
case 'darkgray': return new Color(new RGBA(169, 169, 169, 1));
case 'darkgreen': return new Color(new RGBA(0, 100, 0, 1));
case 'darkgrey': return new Color(new RGBA(169, 169, 169, 1));
case 'darkkhaki': return new Color(new RGBA(189, 183, 107, 1));
case 'darkmagenta': return new Color(new RGBA(139, 0, 139, 1));
case 'darkolivegreen': return new Color(new RGBA(85, 107, 47, 1));
case 'darkorange': return new Color(new RGBA(255, 140, 0, 1));
case 'darkorchid': return new Color(new RGBA(153, 50, 204, 1));
case 'darkred': return new Color(new RGBA(139, 0, 0, 1));
case 'darksalmon': return new Color(new RGBA(233, 150, 122, 1));
case 'darkseagreen': return new Color(new RGBA(143, 188, 143, 1));
case 'darkslateblue': return new Color(new RGBA(72, 61, 139, 1));
case 'darkslategray': return new Color(new RGBA(47, 79, 79, 1));
case 'darkslategrey': return new Color(new RGBA(47, 79, 79, 1));
case 'darkturquoise': return new Color(new RGBA(0, 206, 209, 1));
case 'darkviolet': return new Color(new RGBA(148, 0, 211, 1));
case 'deeppink': return new Color(new RGBA(255, 20, 147, 1));
case 'deepskyblue': return new Color(new RGBA(0, 191, 255, 1));
case 'dimgray': return new Color(new RGBA(105, 105, 105, 1));
case 'dimgrey': return new Color(new RGBA(105, 105, 105, 1));
case 'dodgerblue': return new Color(new RGBA(30, 144, 255, 1));
case 'firebrick': return new Color(new RGBA(178, 34, 34, 1));
case 'floralwhite': return new Color(new RGBA(255, 250, 240, 1));
case 'forestgreen': return new Color(new RGBA(34, 139, 34, 1));
case 'fuchsia': return new Color(new RGBA(255, 0, 255, 1));
case 'gainsboro': return new Color(new RGBA(220, 220, 220, 1));
case 'ghostwhite': return new Color(new RGBA(248, 248, 255, 1));
case 'gold': return new Color(new RGBA(255, 215, 0, 1));
case 'goldenrod': return new Color(new RGBA(218, 165, 32, 1));
case 'gray': return new Color(new RGBA(128, 128, 128, 1));
case 'green': return new Color(new RGBA(0, 128, 0, 1));
case 'greenyellow': return new Color(new RGBA(173, 255, 47, 1));
case 'grey': return new Color(new RGBA(128, 128, 128, 1));
case 'honeydew': return new Color(new RGBA(240, 255, 240, 1));
case 'hotpink': return new Color(new RGBA(255, 105, 180, 1));
case 'indianred': return new Color(new RGBA(205, 92, 92, 1));
case 'indigo': return new Color(new RGBA(75, 0, 130, 1));
case 'ivory': return new Color(new RGBA(255, 255, 240, 1));
case 'khaki': return new Color(new RGBA(240, 230, 140, 1));
case 'lavender': return new Color(new RGBA(230, 230, 250, 1));
case 'lavenderblush': return new Color(new RGBA(255, 240, 245, 1));
case 'lawngreen': return new Color(new RGBA(124, 252, 0, 1));
case 'lemonchiffon': return new Color(new RGBA(255, 250, 205, 1));
case 'lightblue': return new Color(new RGBA(173, 216, 230, 1));
case 'lightcoral': return new Color(new RGBA(240, 128, 128, 1));
case 'lightcyan': return new Color(new RGBA(224, 255, 255, 1));
case 'lightgoldenrodyellow': return new Color(new RGBA(250, 250, 210, 1));
case 'lightgray': return new Color(new RGBA(211, 211, 211, 1));
case 'lightgreen': return new Color(new RGBA(144, 238, 144, 1));
case 'lightgrey': return new Color(new RGBA(211, 211, 211, 1));
case 'lightpink': return new Color(new RGBA(255, 182, 193, 1));
case 'lightsalmon': return new Color(new RGBA(255, 160, 122, 1));
case 'lightseagreen': return new Color(new RGBA(32, 178, 170, 1));
case 'lightskyblue': return new Color(new RGBA(135, 206, 250, 1));
case 'lightslategray': return new Color(new RGBA(119, 136, 153, 1));
case 'lightslategrey': return new Color(new RGBA(119, 136, 153, 1));
case 'lightsteelblue': return new Color(new RGBA(176, 196, 222, 1));
case 'lightyellow': return new Color(new RGBA(255, 255, 224, 1));
case 'lime': return new Color(new RGBA(0, 255, 0, 1));
case 'limegreen': return new Color(new RGBA(50, 205, 50, 1));
case 'linen': return new Color(new RGBA(250, 240, 230, 1));
case 'magenta': return new Color(new RGBA(255, 0, 255, 1));
case 'maroon': return new Color(new RGBA(128, 0, 0, 1));
case 'mediumaquamarine': return new Color(new RGBA(102, 205, 170, 1));
case 'mediumblue': return new Color(new RGBA(0, 0, 205, 1));
case 'mediumorchid': return new Color(new RGBA(186, 85, 211, 1));
case 'mediumpurple': return new Color(new RGBA(147, 112, 219, 1));
case 'mediumseagreen': return new Color(new RGBA(60, 179, 113, 1));
case 'mediumslateblue': return new Color(new RGBA(123, 104, 238, 1));
case 'mediumspringgreen': return new Color(new RGBA(0, 250, 154, 1));
case 'mediumturquoise': return new Color(new RGBA(72, 209, 204, 1));
case 'mediumvioletred': return new Color(new RGBA(199, 21, 133, 1));
case 'midnightblue': return new Color(new RGBA(25, 25, 112, 1));
case 'mintcream': return new Color(new RGBA(245, 255, 250, 1));
case 'mistyrose': return new Color(new RGBA(255, 228, 225, 1));
case 'moccasin': return new Color(new RGBA(255, 228, 181, 1));
case 'navajowhite': return new Color(new RGBA(255, 222, 173, 1));
case 'navy': return new Color(new RGBA(0, 0, 128, 1));
case 'oldlace': return new Color(new RGBA(253, 245, 230, 1));
case 'olive': return new Color(new RGBA(128, 128, 0, 1));
case 'olivedrab': return new Color(new RGBA(107, 142, 35, 1));
case 'orange': return new Color(new RGBA(255, 165, 0, 1));
case 'orangered': return new Color(new RGBA(255, 69, 0, 1));
case 'orchid': return new Color(new RGBA(218, 112, 214, 1));
case 'palegoldenrod': return new Color(new RGBA(238, 232, 170, 1));
case 'palegreen': return new Color(new RGBA(152, 251, 152, 1));
case 'paleturquoise': return new Color(new RGBA(175, 238, 238, 1));
case 'palevioletred': return new Color(new RGBA(219, 112, 147, 1));
case 'papayawhip': return new Color(new RGBA(255, 239, 213, 1));
case 'peachpuff': return new Color(new RGBA(255, 218, 185, 1));
case 'peru': return new Color(new RGBA(205, 133, 63, 1));
case 'pink': return new Color(new RGBA(255, 192, 203, 1));
case 'plum': return new Color(new RGBA(221, 160, 221, 1));
case 'powderblue': return new Color(new RGBA(176, 224, 230, 1));
case 'purple': return new Color(new RGBA(128, 0, 128, 1));
case 'rebeccapurple': return new Color(new RGBA(102, 51, 153, 1));
case 'red': return new Color(new RGBA(255, 0, 0, 1));
case 'rosybrown': return new Color(new RGBA(188, 143, 143, 1));
case 'royalblue': return new Color(new RGBA(65, 105, 225, 1));
case 'saddlebrown': return new Color(new RGBA(139, 69, 19, 1));
case 'salmon': return new Color(new RGBA(250, 128, 114, 1));
case 'sandybrown': return new Color(new RGBA(244, 164, 96, 1));
case 'seagreen': return new Color(new RGBA(46, 139, 87, 1));
case 'seashell': return new Color(new RGBA(255, 245, 238, 1));
case 'sienna': return new Color(new RGBA(160, 82, 45, 1));
case 'silver': return new Color(new RGBA(192, 192, 192, 1));
case 'skyblue': return new Color(new RGBA(135, 206, 235, 1));
case 'slateblue': return new Color(new RGBA(106, 90, 205, 1));
case 'slategray': return new Color(new RGBA(112, 128, 144, 1));
case 'slategrey': return new Color(new RGBA(112, 128, 144, 1));
case 'snow': return new Color(new RGBA(255, 250, 250, 1));
case 'springgreen': return new Color(new RGBA(0, 255, 127, 1));
case 'steelblue': return new Color(new RGBA(70, 130, 180, 1));
case 'tan': return new Color(new RGBA(210, 180, 140, 1));
case 'teal': return new Color(new RGBA(0, 128, 128, 1));
case 'thistle': return new Color(new RGBA(216, 191, 216, 1));
case 'tomato': return new Color(new RGBA(255, 99, 71, 1));
case 'turquoise': return new Color(new RGBA(64, 224, 208, 1));
case 'violet': return new Color(new RGBA(238, 130, 238, 1));
case 'wheat': return new Color(new RGBA(245, 222, 179, 1));
case 'white': return new Color(new RGBA(255, 255, 255, 1));
case 'whitesmoke': return new Color(new RGBA(245, 245, 245, 1));
case 'yellow': return new Color(new RGBA(255, 255, 0, 1));
case 'yellowgreen': return new Color(new RGBA(154, 205, 50, 1));
default: return null;
}
}
/**
* 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;
}
})(Format.CSS || (Format.CSS = {}));
})(Color.Format || (Color.Format = {}));
})(Color || (Color = {}));
export { Color, HSLA, HSVA, RGBA };
+87
View File
@@ -0,0 +1,87 @@
import { safeIntl } from './date.js';
import { Lazy } from './lazy.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// 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 = safeIntl.Collator(undefined, { numeric: true, sensitivity: 'base' }).value;
return {
collator,
collatorIsNumeric: collator.resolvedOptions().numeric
};
});
// A collator with numeric sorting enabled.
new Lazy(() => {
const collator = safeIntl.Collator(undefined, { numeric: true }).value;
return {
collator
};
});
// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
new Lazy(() => {
const collator = safeIntl.Collator(undefined, { numeric: true, sensitivity: 'accent' }).value;
return {
collator
};
});
/** Compares filenames without distinguishing the name from the extension. Disambiguates by unicode comparison. */
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;
}
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);
}
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;
}
export { compareAnything, compareByPrefix, compareFileNames };
+133
View File
@@ -0,0 +1,133 @@
import { distinct } from './arrays.js';
import { Iterable } from './iterator.js';
import { generateUuid } from './uuid.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function createStringDataTransferItem(stringOrPromise, id) {
return {
id,
asString: async () => stringOrPromise,
asFile: () => undefined,
value: typeof stringOrPromise === 'string' ? stringOrPromise : undefined,
};
}
function createFileDataTransferItem(fileName, uri, data, id) {
const file = { id: generateUuid(), name: fileName, uri, data };
return {
id,
asString: async () => '',
asFile: () => file,
value: undefined,
};
}
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();
}
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;
}
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('#'));
}
});
export { UriList, VSDataTransfer, createFileDataTransferItem, createStringDataTransferItem, matchesMimeType };
+57
View File
@@ -0,0 +1,57 @@
import { Lazy } from './lazy.js';
import { LANGUAGE_DEFAULT } from './platform.js';
const safeIntl = {
DateTimeFormat(locales, options) {
return new Lazy(() => {
try {
return new Intl.DateTimeFormat(locales, options);
}
catch {
return new Intl.DateTimeFormat(undefined, options);
}
});
},
Collator(locales, options) {
return new Lazy(() => {
try {
return new Intl.Collator(locales, options);
}
catch {
return new Intl.Collator(undefined, options);
}
});
},
Segmenter(locales, options) {
return new Lazy(() => {
try {
return new Intl.Segmenter(locales, options);
}
catch {
return new Intl.Segmenter(undefined, options);
}
});
},
Locale(tag, options) {
return new Lazy(() => {
try {
return new Intl.Locale(tag, options);
}
catch {
return new Intl.Locale(LANGUAGE_DEFAULT, options);
}
});
},
NumberFormat(locales, options) {
return new Lazy(() => {
try {
return new Intl.NumberFormat(locales, options);
}
catch {
return new Intl.NumberFormat(undefined, options);
}
});
}
};
export { safeIntl };
+33
View File
@@ -0,0 +1,33 @@
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)
});
}
// eslint-disable-next-line local/code-no-any-casts
return this[memoizeKey];
};
}
export { memoize };
+902
View File
@@ -0,0 +1,902 @@
import { DiffChange } from './diffChange.js';
import { stringHash } from '../hash.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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;
}
}
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
*/
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;
}
}
}
export { LcsDiff, StringDiffSequence, stringDiff };
@@ -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.
*--------------------------------------------------------------------------------------------*/
/**
* Represents information about a specific difference between two sequences.
*/
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;
}
}
export { DiffChange };
+84
View File
@@ -0,0 +1,84 @@
import { equals } from './arrays.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Compares two items for equality using strict equality.
*/
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.
*/
function itemsEquals(itemEquals = strictEquals) {
return (a, b) => equals(a, b, itemEquals);
}
/**
* Uses `item.equals(other)` to determine equality.
*/
function itemEquals() {
return (a, b) => a.equals(b);
}
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.
*/
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;
}
export { equalsIfDefined, itemEquals, itemsEquals, strictEquals, structuralEquals };
+71
View File
@@ -0,0 +1,71 @@
import { coalesce } from './arrays.js';
import { isString } from './types.js';
import { localize } from '../../nls.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function exceptionToErrorMessage(exception, verbose) {
if (verbose && (exception.stack || exception.stacktrace)) {
return localize(29, "{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 localize(30, "A system error occurred ({0})", exception.message);
}
return exception.message || localize(31, "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.
*/
function toErrorMessage(error = null, verbose = false) {
if (!error) {
return localize(32, "An unknown error occurred. Please consult the log for more details.");
}
if (Array.isArray(error)) {
const errors = coalesce(error);
const msg = toErrorMessage(errors[0], verbose);
if (errors.length > 1) {
return localize(33, "{0} ({1} errors in total)", msg, errors.length);
}
return msg;
}
if (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 localize(34, "An unknown error occurred. Please consult the log for more details.");
}
export { toErrorMessage };
+163
View File
@@ -0,0 +1,163 @@
/*---------------------------------------------------------------------------------------------
* 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.
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);
}
}
const errorHandler = new ErrorHandler();
/**
* This function should only be called with errors that indicate a bug in the product.
* E.g. buggy extensions/invalid user-input/network issues should not be able to trigger this code path.
* If they are, this indicates there is also a bug in the product.
*/
function onBugIndicatingError(e) {
errorHandler.onUnexpectedError(e);
return undefined;
}
function onUnexpectedError(e) {
// ignore errors from cancelled promises
if (!isCancellationError(e)) {
errorHandler.onUnexpectedError(e);
}
return undefined;
}
function onUnexpectedExternalError(e) {
// ignore errors from cancelled promises
if (!isCancellationError(e)) {
errorHandler.onUnexpectedExternalError(e);
}
return undefined;
}
function transformErrorForSerialization(error) {
if (error instanceof Error) {
const { name, message, cause } = error;
// eslint-disable-next-line local/code-no-any-casts
const stack = error.stacktrace || error.stack;
return {
$isError: true,
name,
message,
stack,
noTelemetry: ErrorNoTelemetry.isErrorNoTelemetry(error),
cause: cause ? transformErrorForSerialization(cause) : undefined,
code: error.code
};
}
// return as is
return error;
}
const canceledName = 'Canceled';
/**
* Checks if the given error is a promise in canceled state
*/
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.
class CancellationError extends Error {
constructor() {
super(canceledName);
this.name = this.message;
}
}
/**
* @deprecated use {@link CancellationError `new CancellationError()`} instead
*/
function canceled() {
const error = new Error(canceledName);
error.name = error.message;
return error;
}
function illegalArgument(name) {
if (name) {
return new Error(`Illegal argument: ${name}`);
}
else {
return new Error('Illegal argument');
}
}
function illegalState(name) {
if (name) {
return new Error(`Illegal state: ${name}`);
}
else {
return new Error('Illegal state');
}
}
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.
*/
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.
*/
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.
// debugger;
}
}
export { BugIndicatingError, CancellationError, ErrorHandler, ErrorNoTelemetry, NotSupportedError, canceled, canceledName, errorHandler, illegalArgument, illegalState, isCancellationError, onBugIndicatingError, onUnexpectedError, onUnexpectedExternalError, transformErrorForSerialization };
File diff suppressed because it is too large Load Diff
+147
View File
@@ -0,0 +1,147 @@
import { sep, posix } from './path.js';
import { isWindows } from './platform.js';
import { startsWithIgnoreCase } from './strings.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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.
*/
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.
*/
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\`
*/
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.
*/
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;
}
function isWindowsDriveLetter(char0) {
return char0 >= 65 /* CharCode.A */ && char0 <= 90 /* CharCode.Z */ || char0 >= 97 /* CharCode.a */ && char0 <= 122 /* CharCode.z */;
}
function hasDriveLetter(path, isWindowsOS = isWindows) {
if (isWindowsOS) {
return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === 58 /* CharCode.Colon */;
}
return false;
}
export { getRoot, hasDriveLetter, isEqualOrParent, isPathSeparator, isWindowsDriveLetter, toPosixPath, toSlashes };
+742
View File
@@ -0,0 +1,742 @@
import { LRUCache } from './map.js';
import { getKoreanAltChars } from './naturalLanguage/korean.js';
import { startsWithIgnoreCase, isEmojiImprecise, convertSimple2RegExpPattern } from './strings.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// 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.
*/
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
_matchesPrefix.bind(undefined, false);
const matchesPrefix = _matchesPrefix.bind(undefined, true);
function _matchesPrefix(ignoreCase, word, wordToMatchAgainst) {
if (!wordToMatchAgainst || wordToMatchAgainst.length < word.length) {
return null;
}
let matches;
if (ignoreCase) {
matches = startsWithIgnoreCase(wordToMatchAgainst, word);
}
else {
matches = wordToMatchAgainst.indexOf(word) === 0;
}
if (!matches) {
return null;
}
return word.length > 0 ? [{ start: 0, end: word.length }] : [];
}
// Contiguous Substring
function matchesContiguousSubString(word, wordToMatchAgainst) {
const index = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase());
if (index === -1) {
return null;
}
return [{ start: index, end: index + word.length }];
}
// Substring
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 */;
}
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;
}
}
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)
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
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(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`
*/
function matchesFuzzy2(pattern, word) {
const score = fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true });
return score ? createMatches(score) : null;
}
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 ---
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();
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 (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];
}
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
}
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 = {}));
class FuzzyScoreOptions {
static { this.default = { boostFullMatch: true, firstMatchCanBeWeak: false }; }
constructor(firstMatchCanBeWeak, boostFullMatch) {
this.firstMatchCanBeWeak = firstMatchCanBeWeak;
this.boostFullMatch = boostFullMatch;
}
}
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.MIN_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 (!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 ---
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 && false) {
// 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
export { FuzzyScore, FuzzyScoreOptions, anyScore, createMatches, fuzzyScore, fuzzyScoreGracefulAggressive, isPatternInWord, isUpper, matchesCamelCase, matchesContiguousSubString, matchesFuzzy, matchesFuzzy2, matchesPrefix, matchesSubString, matchesWords, or };
+24
View File
@@ -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.
*--------------------------------------------------------------------------------------------*/
/**
* Given a function, returns a function that is only calling that function once.
*/
function createSingleCallFunction(fn, fnDidRunCallback) {
const _this = this;
let didCall = false;
let result;
return function () {
if (didCall) {
return result;
}
didCall = true;
{
result = fn.apply(_this, arguments);
}
return result;
};
}
export { createSingleCallFunction };
+143
View File
@@ -0,0 +1,143 @@
import { fuzzyScore, createMatches } from './filters.js';
import { sep } from './path.js';
import { isWindows } from './platform.js';
const NO_SCORE2 = [undefined, []];
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], createMatches(score)];
}
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 = ' ';
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
}
// remove certain characters that help find better results:
// - quotes: are used for exact match search
// - wildcards: are used for fuzzy matching
// - whitespace: are used to separate queries
// - ellipsis: sometimes used to indicate any path segments
const normalized = pathNormalized.replace(/[\*\u2026\s"]/g, '');
return {
pathNormalized,
normalized,
normalizedLowercase: normalized.toLowerCase()
};
}
function pieceToQuery(arg1) {
if (Array.isArray(arg1)) {
return prepareQuery(arg1.map(piece => piece.original).join(MULTIPLE_QUERY_VALUES_SEPARATOR));
}
return prepareQuery(arg1.original);
}
//#endregion
export { pieceToQuery, prepareQuery, scoreFuzzy2 };
+581
View File
@@ -0,0 +1,581 @@
import { isThenable } from './async.js';
import { isEqualOrParent } from './extpath.js';
import { LRUCache } from './map.js';
import { sep, posix, basename, extname } from './path.js';
import { isLinux } from './platform.js';
import { ltrim, escapeRegExpCharacters, endsWithIgnoreCase, equalsIgnoreCase } from './strings.js';
const GLOBSTAR = '**';
const GLOB_SPLIT = '/';
const PATH_REGEX = '[/\\\\]'; // any slash or backslash
const NO_PATH_REGEX = '[^/\\\\]'; // any non-slash and non-backslash
const ALL_FORWARD_SLASHES = /\//g;
function starsToRegExp(starCount, 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}+` : ''})*?`;
}
}
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();
const ignoreCase = options.ignoreCase ?? false;
const internalOptions = {
...options,
equals: ignoreCase ? equalsIgnoreCase : (a, b) => a === b,
endsWith: ignoreCase ? endsWithIgnoreCase : (str, candidate) => str.endsWith(candidate),
// TODO: the '!isLinux' part below is to keep current behavior unchanged, but it should probably be removed
// in favor of passing correct options from the caller.
isEqualOrParent: (base, candidate) => isEqualOrParent(base, candidate, !isLinux || ignoreCase)
};
// Check cache
const patternKey = `${ignoreCase ? pattern.toLowerCase() : pattern}_${!!options.trimForExclusions}_${ignoreCase}`;
let parsedPattern = CACHE.get(patternKey);
if (parsedPattern) {
return wrapRelativePattern(parsedPattern, arg1, internalOptions);
}
// Check for Trivials
let match;
if (T1.test(pattern)) {
parsedPattern = trivia1(pattern.substring(4), pattern, internalOptions); // common pattern: **/*.txt just need endsWith check
}
else if (match = T2.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: **/some.txt just need basename check
parsedPattern = trivia2(match[1], pattern, internalOptions);
}
else if ((options.trimForExclusions ? T3_2 : T3).test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png}
parsedPattern = trivia3(pattern, internalOptions);
}
else if (match = T4.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: **/something/else just need endsWith check
parsedPattern = trivia4and5(match[1].substring(1), pattern, true, internalOptions);
}
else if (match = T5.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: something/else just need equals check
parsedPattern = trivia4and5(match[1], pattern, false, internalOptions);
}
// Otherwise convert to pattern
else {
parsedPattern = toRegExp(pattern, internalOptions);
}
// Cache
CACHE.set(patternKey, parsedPattern);
return wrapRelativePattern(parsedPattern, arg1, internalOptions);
}
function wrapRelativePattern(parsedPattern, arg2, options) {
if (typeof arg2 === 'string') {
return parsedPattern;
}
const wrappedPattern = function (path, basename) {
if (!options.isEqualOrParent(path, arg2.base)) {
// 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.substring(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.substring(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later
}
// common pattern: **/*.txt just need endsWith check
function trivia1(base, pattern, options) {
return function (path, basename) {
return typeof path === 'string' && options.endsWith(path, base) ? pattern : null;
};
}
// common pattern: **/some.txt just need basename check
function trivia2(base, pattern, options) {
const slashBase = `/${base}`;
const backslashBase = `\\${base}`;
const parsedPattern = function (path, basename) {
if (typeof path !== 'string') {
return null;
}
if (basename) {
return options.equals(basename, base) ? pattern : null;
}
return options.equals(path, base) || options.endsWith(path, slashBase) || options.endsWith(path, 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, options) {
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' && ((options.equals(path, nativePath) || options.endsWith(path, nativePathEnd)) ||
!usingPosixSep && (options.equals(path, targetPath) || options.endsWith(path, targetPathEnd))) ? pattern : null;
};
}
else {
parsedPattern = function (path, basename) {
return typeof path === 'string' && (options.equals(path, nativePath) || (!usingPosixSep && options.equals(path, targetPath))) ? pattern : null;
};
}
parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + targetPath];
return parsedPattern;
}
function toRegExp(pattern, options) {
try {
const regExp = new RegExp(`^${parseRegExp(pattern)}$`, options.ignoreCase ? 'i' : undefined);
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 {
return NULL;
}
}
function match(arg1, path, options) {
if (!arg1 || typeof path !== 'string') {
return false;
}
return parse(arg1, options)(path);
}
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);
}
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.substring(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.substring(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;
}
export { GLOBSTAR, GLOB_SPLIT, isRelativePattern, match, parse, splitGlobAware };
+256
View File
@@ -0,0 +1,256 @@
import { encodeHex, VSBuffer } from './buffer.js';
import { isHighSurrogate, isLowSurrogate, computeCodePoint } from './strings.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Return a hash value for an object.
*
* Note that this should not be used for binary data types. Instead,
* prefer {@link hashAsync}.
*/
function hash(obj) {
return doHash(obj, 0);
}
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);
}
}
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);
}
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 toHexString(bufferOrValue, bitsize = 32) {
if (bufferOrValue instanceof ArrayBuffer) {
return encodeHex(VSBuffer.wrap(new Uint8Array(bufferOrValue)));
}
return (bufferOrValue >>> 0).toString(16).padStart(bitsize / 4, '0');
}
/**
* A SHA1 implementation that works with strings and does not allocate.
*
* Prefer to use {@link hashAsync} in async contexts
*/
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 (isHighSurrogate(charCode)) {
if (offset + 1 < strLen) {
const nextCharCode = str.charCodeAt(offset + 1);
if (isLowSurrogate(nextCharCode)) {
offset++;
codePoint = 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 (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;
this._buff.subarray(this._buffLen).fill(0);
if (this._buffLen > 56) {
this._step();
this._buff.fill(0);
}
// 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;
}
}
export { StringSHA1, doHash, hash, numberHash, stringHash };
@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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));
}
}
export { HierarchicalKind };
+85
View File
@@ -0,0 +1,85 @@
import { ArrayNavigator } from './navigator.js';
class HistoryNavigator {
constructor(_history = new Set(), limit = 10) {
this._history = _history;
this._limit = limit;
this._onChange();
if (this._history.onDidChange) {
this._disposable = this._history.onDidChange(() => 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) {
const replaceValue = data.slice(data.length - this._limit);
if (this._history.replace) {
this._history.replace(replaceValue);
}
else {
this._history = new Set(replaceValue);
}
}
}
_currentPosition() {
const currentElement = this._navigator.current();
if (!currentElement) {
return -1;
}
return this._elements.indexOf(currentElement);
}
get _elements() {
const elements = [];
this._history.forEach(e => elements.push(e));
return elements;
}
dispose() {
if (this._disposable) {
this._disposable.dispose();
this._disposable = undefined;
}
}
}
export { HistoryNavigator };
@@ -0,0 +1,17 @@
import './observableInternal/index.js';
import { constObservable } from './observableInternal/observables/constObservable.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function readHotReloadableExport(value, reader) {
return value;
}
function createHotClass(clazz) {
{
return constObservable(clazz);
}
}
export { createHotClass, readHotReloadableExport };
+164
View File
@@ -0,0 +1,164 @@
import { illegalArgument } from './errors.js';
import { escapeIcons } from './iconLabels.js';
import { Schemas } from './network.js';
import { isEqual } from './resources.js';
import { escapeRegExpCharacters } from './strings.js';
import { URI } from './uri.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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;
this.supportAlertSyntax = false;
}
else {
this.isTrusted = isTrustedOrOptions.isTrusted ?? undefined;
this.supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false;
this.supportHtml = isTrustedOrOptions.supportHtml ?? false;
this.supportAlertSyntax = isTrustedOrOptions.supportAlertSyntax ?? 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;
}
});
}
}
function isEmptyMarkdownString(oneOrMany) {
if (isMarkdownString(oneOrMany)) {
return !oneOrMany.value;
}
else if (Array.isArray(oneOrMany)) {
return oneOrMany.every(isEmptyMarkdownString);
}
else {
return true;
}
}
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)
&& (typeof thing.supportAlertSyntax === 'boolean' || thing.supportAlertSyntax === undefined);
}
return false;
}
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.supportAlertSyntax === b.supportAlertSyntax
&& (a.baseUri === b.baseUri || !!a.baseUri && !!b.baseUri && isEqual(URI.from(a.baseUri), URI.from(b.baseUri)));
}
}
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
*/
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');
}
function escapeDoubleQuotes(input) {
return input.replace(/"/g, '&quot;');
}
function removeMarkdownEscapes(text) {
if (!text) {
return text;
}
return text.replace(/\\([\\`*_{}[\]()#+\-.!~])/g, '$1');
}
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 };
}
function createCommandUri(commandId, ...commandArgs) {
return URI.from({
scheme: Schemas.command,
path: commandId,
query: commandArgs.length ? encodeURIComponent(JSON.stringify(commandArgs)) : undefined,
});
}
export { MarkdownString, appendEscapedMarkdownCodeBlockFence, createCommandUri, escapeDoubleQuotes, escapeMarkdownSyntaxTokens, isEmptyMarkdownString, isMarkdownString, markdownStringEqual, parseHrefAndDimensions, removeMarkdownEscapes };
+88
View File
@@ -0,0 +1,88 @@
import { matchesFuzzy } from './filters.js';
import { ltrim } from './strings.js';
import { ThemeIcon } from './themables.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const iconStartMarker = '$(';
const iconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameExpression}(?:${ThemeIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups
const escapeIconsRegex = new RegExp(`(\\\\)?${iconsRegex.source}`, 'g');
function escapeIcons(text) {
return text.replace(escapeIconsRegex, (match, escaped) => escaped ? match : `\\${match}`);
}
const markdownEscapedIconsRegex = new RegExp(`\\\\${iconsRegex.source}`, 'g');
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`)
*/
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.
*/
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]`)
*/
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 };
}
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;
}
export { escapeIcons, getCodiconAriaLabel, markdownEscapeEscapedIcons, matchesFuzzyIconAware, parseLabelWithIcons, stripIcons };
+16
View File
@@ -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.
*--------------------------------------------------------------------------------------------*/
class IdGenerator {
constructor(prefix) {
this._prefix = prefix;
this._lastId = 0;
}
nextId() {
return this._prefix + (++this._lastId);
}
}
const defaultGenerator = new IdGenerator('id#');
export { IdGenerator, defaultGenerator };
+33
View File
@@ -0,0 +1,33 @@
import { Emitter } from './event.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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();
}
}
const IME = new IMEImpl();
export { IME, IMEImpl };
+186
View File
@@ -0,0 +1,186 @@
import { isIterable } from './types.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 every(iterable, predicate) {
let i = 0;
for (const element of iterable) {
if (!predicate(element, i++)) {
return false;
}
}
return true;
}
Iterable.every = every;
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 item of iterables) {
if (isIterable(item)) {
yield* item;
}
else {
yield item;
}
}
}
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;
function length(iterable) {
let count = 0;
for (const _ of iterable) {
count++;
}
return count;
}
Iterable.length = length;
/**
* Returns an iterable slice of the array, with the same semantics as `array.slice()`.
*/
function* slice(arr, from, to = arr.length) {
if (from < -arr.length) {
from = 0;
}
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 result;
}
Iterable.asyncToArray = asyncToArray;
async function asyncToArrayFlat(iterable) {
let result = [];
for await (const item of iterable) {
result = result.concat(item);
}
return result;
}
Iterable.asyncToArrayFlat = asyncToArrayFlat;
})(Iterable || (Iterable = {}));
export { Iterable };
+351
View File
@@ -0,0 +1,351 @@
/*---------------------------------------------------------------------------------------------
* 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();
const EVENT_KEY_CODE_MAP = new Array(230);
const scanCodeStrToInt = Object.create(null);
const scanCodeLowerCaseStrToInt = Object.create(null);
/**
* -1 if a ScanCode => KeyCode mapping depends on kb layout.
*/
const IMMUTABLE_CODE_TO_KEY_CODE = [];
for (let i = 0; i <= 193 /* ScanCode.MAX_VALUE */; i++) {
IMMUTABLE_CODE_TO_KEY_CODE[i] = -1 /* KeyCode.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;
scanCodeStrToInt[scanCodeStr] = scanCode;
scanCodeLowerCaseStrToInt[scanCodeStr.toLowerCase()] = scanCode;
if (immutable) {
IMMUTABLE_CODE_TO_KEY_CODE[scanCode] = keyCode;
}
}
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;
}
}
})();
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 = {}));
function KeyChord(firstPart, secondPart) {
const chordPart = ((secondPart & 0x0000FFFF) << 16) >>> 0;
return (firstPart | chordPart) >>> 0;
}
export { EVENT_KEY_CODE_MAP, IMMUTABLE_CODE_TO_KEY_CODE, KeyChord, KeyCodeUtils };
@@ -0,0 +1,139 @@
import { localize } from '../../nls.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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.
*/
const UILabelProvider = new ModifierLabelProvider({
ctrlKey: '\u2303',
shiftKey: '⇧',
altKey: '⌥',
metaKey: '⌘',
separator: '',
}, {
ctrlKey: localize(35, "Ctrl"),
shiftKey: localize(36, "Shift"),
altKey: localize(37, "Alt"),
metaKey: localize(38, "Windows"),
separator: '+',
}, {
ctrlKey: localize(39, "Ctrl"),
shiftKey: localize(40, "Shift"),
altKey: localize(41, "Alt"),
metaKey: localize(42, "Super"),
separator: '+',
});
/**
* A label provider that prints modifiers in a suitable format for ARIA.
*/
const AriaLabelProvider = new ModifierLabelProvider({
ctrlKey: localize(43, "Control"),
shiftKey: localize(44, "Shift"),
altKey: localize(45, "Option"),
metaKey: localize(46, "Command"),
separator: '+',
}, {
ctrlKey: localize(47, "Control"),
shiftKey: localize(48, "Shift"),
altKey: localize(49, "Alt"),
metaKey: localize(50, "Windows"),
separator: '+',
}, {
ctrlKey: localize(51, "Control"),
shiftKey: localize(52, "Shift"),
altKey: localize(53, "Alt"),
metaKey: localize(54, "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
*/
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.
*/
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);
}
export { AriaLabelProvider, ElectronAcceleratorLabelProvider, ModifierLabelProvider, UILabelProvider, UserSettingsLabelProvider };
+104
View File
@@ -0,0 +1,104 @@
import { illegalArgument } from './errors.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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);
}
}
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.
*/
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 */));
}
}
/**
* A keybinding is a sequence of chords.
*/
class Keybinding {
constructor(chords) {
if (chords.length === 0) {
throw illegalArgument(`chords`);
}
this.chords = chords;
}
}
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.
*/
class ResolvedKeybinding {
}
export { KeyCodeChord, Keybinding, ResolvedChord, ResolvedKeybinding, createSimpleKeybinding, decodeKeybinding };
+15
View File
@@ -0,0 +1,15 @@
import { hasDriveLetter } from './extpath.js';
import { isWindows } from './platform.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function normalizeDriveLetter(path, isWindowsOS = isWindows) {
if (hasDriveLetter(path, isWindowsOS)) {
return path.charAt(0).toUpperCase() + path.slice(1);
}
return path;
}
export { normalizeDriveLetter };
+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.
*--------------------------------------------------------------------------------------------*/
var LazyValueState;
(function (LazyValueState) {
LazyValueState[LazyValueState["Uninitialized"] = 0] = "Uninitialized";
LazyValueState[LazyValueState["Running"] = 1] = "Running";
LazyValueState[LazyValueState["Completed"] = 2] = "Completed";
})(LazyValueState || (LazyValueState = {}));
class Lazy {
constructor(executor) {
this.executor = executor;
this._state = LazyValueState.Uninitialized;
}
/**
* 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._state === LazyValueState.Uninitialized) {
this._state = LazyValueState.Running;
try {
this._value = this.executor();
}
catch (err) {
this._error = err;
}
finally {
this._state = LazyValueState.Completed;
}
}
else if (this._state === LazyValueState.Running) {
throw new Error('Cannot read the value of a lazy that is being initialized');
}
if (this._error) {
throw this._error;
}
return this._value;
}
/**
* Get the wrapped value without forcing evaluation.
*/
get rawValue() { return this._value; }
}
export { Lazy };
+311
View File
@@ -0,0 +1,311 @@
import { Iterable } from './iterator.js';
function setParentOfDisposable(child, parent) {
}
/**
* Indicates that the given object is a singleton which does not need to be disposed.
*/
function markAsSingleton(singleton) {
return singleton;
}
/**
* Check if `thing` is {@link IDisposable disposable}.
*/
function isDisposable(thing) {
// eslint-disable-next-line local/code-no-any-casts
return typeof thing === 'object' && thing !== null && typeof thing.dispose === 'function' && thing.dispose.length === 0;
}
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}.
*/
function combinedDisposable(...disposables) {
const parent = toDisposable(() => dispose(disposables));
return parent;
}
class FunctionDisposable {
constructor(fn) {
this._isDisposed = false;
this._fn = fn;
}
dispose() {
if (this._isDisposed) {
return;
}
if (!this._fn) {
throw new Error(`Unbound disposable context: Need to use an arrow function to preserve the value of this`);
}
this._isDisposed = true;
this._fn();
}
}
/**
* Turn a function that implements dispose into an {@link IDisposable}.
*
* @param fn Clean up function, guaranteed to be called only **once**.
*/
function toDisposable(fn) {
return new FunctionDisposable(fn);
}
/**
* 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.
*/
class DisposableStore {
static { this.DISABLE_DISPOSED_WARNING = false; }
constructor() {
this._toDispose = new Set();
this._isDisposed = false;
}
/**
* Dispose of all registered disposables and mark this object as disposed.
*
* Any future disposables added to this object will be disposed of on `add`.
*/
dispose() {
if (this._isDisposed) {
return;
}
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 || o === Disposable.None) {
return o;
}
if (o === this) {
throw new Error('Cannot register a disposable on itself!');
}
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 a disposable from store and disposes of it. This will not throw or warn and proceed to dispose the
* disposable even when the disposable is not part in the store.
*/
delete(o) {
if (!o) {
return;
}
if (o === this) {
throw new Error('Cannot dispose a disposable on itself!');
}
this._toDispose.delete(o);
o.dispose();
}
}
/**
* 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.
*/
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();
setParentOfDisposable(this._store);
}
dispose() {
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.
*/
class MutableDisposable {
constructor() {
this._isDisposed = false;
}
/**
* Get the currently held disposable value, or `undefined` if this MutableDisposable has been disposed
*/
get value() {
return this._isDisposed ? undefined : this._value;
}
/**
* Set a new disposable value.
*
* Behaviour:
* - If the MutableDisposable has been disposed, the setter is a no-op.
* - If the new value is strictly equal to the current value, the setter is a no-op.
* - Otherwise the previous value (if any) is disposed and the new value is stored.
*
* Related helpers:
* - clear() resets the value to `undefined` (and disposes the previous value).
* - clearAndLeak() returns the old value without disposing it and removes its parent.
*/
set value(value) {
if (this._isDisposed || value === this._value) {
return;
}
this._value?.dispose();
this._value = value;
}
/**
* Resets the stored value and disposed of the previously stored value.
*/
clear() {
this.value = undefined;
}
dispose() {
this._isDisposed = true;
this._value?.dispose();
this._value = undefined;
}
}
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;
}
}
class ImmortalReference {
constructor(object) {
this.object = object;
}
dispose() { }
}
/**
* A map the manages the lifecycle of the values that it stores.
*/
class DisposableMap {
constructor() {
this._store = new Map();
this._isDisposed = false;
}
/**
* 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() {
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);
}
values() {
return this._store.values();
}
[Symbol.iterator]() {
return this._store[Symbol.iterator]();
}
}
export { Disposable, DisposableMap, DisposableStore, ImmortalReference, MutableDisposable, RefCountedDisposable, combinedDisposable, dispose, isDisposable, markAsSingleton, toDisposable };
+126
View File
@@ -0,0 +1,126 @@
/*---------------------------------------------------------------------------------------------
* 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;
}
}
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;
}
}
}
export { LinkedList };
+48
View File
@@ -0,0 +1,48 @@
import { memoize } from './decorators.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__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;
};
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;
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);
}
export { LinkedText, parseLinkedText };
+670
View File
@@ -0,0 +1,670 @@
/*---------------------------------------------------------------------------------------------
* 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, _c;
class ResourceMapEntry {
constructor(uri, value) {
this.uri = uri;
this.value = value;
}
}
function isEntries(arg) {
return Array.isArray(arg);
}
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];
}
}
}
class ResourceSet {
constructor(entriesOrKey, toKey) {
this[_b] = 'ResourceSet';
if (!entriesOrKey || typeof entriesOrKey === 'function') {
this._map = new ResourceMap(entriesOrKey);
}
else {
this._map = new ResourceMap(toKey);
entriesOrKey.forEach(this.add, this);
}
}
get size() {
return this._map.size;
}
add(value) {
this._map.set(value, value);
return this;
}
clear() {
this._map.clear();
}
delete(value) {
return this._map.delete(value);
}
forEach(callbackfn, thisArg) {
this._map.forEach((_value, key) => callbackfn.call(thisArg, key, key, this));
}
has(value) {
return this._map.has(value);
}
entries() {
return this._map.entries();
}
keys() {
return this._map.keys();
}
values() {
return this._map.keys();
}
[(_b = Symbol.toStringTag, Symbol.iterator)]() {
return this.keys();
}
}
class LinkedMap {
constructor() {
this[_c] = '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;
}
[(_c = 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));
}
}
}
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.
*/
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();
}
}
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);
}
}
/**
* A map that is addressable with an arbitrary number of keys. This is useful in high performance
* scenarios where creating a composite key whenever the data is accessed is too expensive. For
* example for a very hot function, constructing a string like `first-second-third` for every call
* will cause a significant hit to performance.
*/
class NKeyMap {
constructor() {
this._data = new Map();
}
/**
* Sets a value on the map. Note that unlike a standard `Map`, the first argument is the value.
* This is because the spread operator is used for the keys and must be last..
* @param value The value to set.
* @param keys The keys for the value.
*/
set(value, ...keys) {
let currentMap = this._data;
for (let i = 0; i < keys.length - 1; i++) {
if (!currentMap.has(keys[i])) {
currentMap.set(keys[i], new Map());
}
currentMap = currentMap.get(keys[i]);
}
currentMap.set(keys[keys.length - 1], value);
}
get(...keys) {
let currentMap = this._data;
for (let i = 0; i < keys.length - 1; i++) {
if (!currentMap.has(keys[i])) {
return undefined;
}
currentMap = currentMap.get(keys[i]);
}
return currentMap.get(keys[keys.length - 1]);
}
clear() {
this._data.clear();
}
/**
* Get a textual representation of the map for debugging purposes.
*/
toString() {
const printMap = (map, depth) => {
let result = '';
for (const [key, value] of map) {
result += `${' '.repeat(depth)}${key}: `;
if (value instanceof Map) {
result += '\n' + printMap(value, depth + 1);
}
else {
result += `${value}\n`;
}
}
return result;
};
return printMap(this._data, 0);
}
}
export { BidirectionalMap, LRUCache, LinkedMap, NKeyMap, ResourceMap, ResourceSet, SetMap };
File diff suppressed because it is too large Load Diff
+62
View File
@@ -0,0 +1,62 @@
import { VSBuffer } from './buffer.js';
import { URI } from './uri.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function stringify(obj) {
return JSON.stringify(obj, replacer);
}
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;
}
function revive(obj, depth = 0) {
if (!obj || depth > 200) {
return obj;
}
if (typeof obj === 'object') {
switch (obj.$mid) {
// eslint-disable-next-line local/code-no-any-casts
case 1 /* MarshalledId.Uri */: return URI.revive(obj);
// eslint-disable-next-line local/code-no-any-casts
case 2 /* MarshalledId.Regexp */: return new RegExp(obj.source, obj.flags);
// eslint-disable-next-line local/code-no-any-casts
case 17 /* MarshalledId.Date */: return new Date(obj.source);
}
if (obj instanceof VSBuffer
|| obj instanceof Uint8Array) {
// eslint-disable-next-line local/code-no-any-casts
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;
}
export { parse, revive, stringify };
+11
View File
@@ -0,0 +1,11 @@
const Mimes = Object.freeze({
text: 'text/plain',
binary: 'application/octet-stream',
unknown: 'application/unknown',
markdown: 'text/markdown',
latex: 'text/latex',
uriList: 'text/uri-list',
html: 'text/html',
});
export { Mimes };
@@ -0,0 +1,321 @@
/*---------------------------------------------------------------------------------------------
* 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
*/
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)
// ㅥ
// ㅦ
// ㅧ
// ㅨ
// ㅩ
// ㅪ
// ㅫ
// ㅬ
// ㅮ
// ㅯ
// ㅰ
// ㅱ
// ㅲ
// ㅳ
// ㅴ
// ㅵ
// ㅶ
// ㅷ
// ㅸ
// ㅹ
// ㅺ
// ㅻ
// ㅼ
// ㅽ
// ㅾ
// ㅿ
// ㆀ
// ㆁ
// ㆂ
// ㆃ
// ㆄ
// ㆅ
// ㆆ
// ㆇ
// ㆈ
// ㆉ
// ㆊ
// ㆋ
// ㆌ
// ㆍ
// ㆎ
]);
export { getKoreanAltChars };
+36
View File
@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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();
}
}
export { ArrayNavigator };
+267
View File
@@ -0,0 +1,267 @@
import { onUnexpectedError } from './errors.js';
import { isWeb, isNative, webWorkerOrigin } from './platform.js';
import { equalsIgnoreCase, startsWithIgnoreCase } from './strings.js';
import { URI } from './uri.js';
import { posix } from './path.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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.vscodeNotebookCellMetadataDiff = 'vscode-notebook-cell-metadata-diff';
Schemas.vscodeNotebookCellOutput = 'vscode-notebook-cell-output';
Schemas.vscodeNotebookCellOutputDiff = 'vscode-notebook-cell-output-diff';
Schemas.vscodeNotebookMetadata = 'vscode-notebook-metadata';
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 LHS of code compare (aka diff) blocks in chat. */
Schemas.vscodeChatCodeCompareBlock = 'vscode-chat-code-compare-block';
/** Scheme used for the chat input editor. */
Schemas.vscodeChatEditor = 'vscode-chat-editor';
/** Scheme used for the chat input part */
Schemas.vscodeChatInput = 'chatSessionInput';
/** Scheme used for local chat session content */
Schemas.vscodeLocalChatSession = 'vscode-chat-session';
/**
* 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';
/**
* Scheme used for output panel resources
*/
Schemas.outputChannel = 'output';
/**
* Scheme used for the accessible view
*/
Schemas.accessibleView = 'accessible-view';
/**
* Used for snapshots of chat edits
*/
Schemas.chatEditingSnapshotScheme = 'chat-editing-snapshot-text-model';
Schemas.chatEditingModel = 'chat-editing-text-model';
/**
* Used for rendering multidiffs in copilot agent sessions
*/
Schemas.copilotPr = 'copilot-pr';
})(Schemas || (Schemas = {}));
function matchesScheme(target, scheme) {
if (URI.isUri(target)) {
return equalsIgnoreCase(target.scheme, scheme);
}
else {
return startsWithIgnoreCase(target, scheme + ':');
}
}
function matchesSomeScheme(target, ...schemes) {
return schemes.some(scheme => matchesScheme(target, scheme));
}
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 posix.join(this._serverRootPath, Schemas.vscodeRemoteResource);
}
rewrite(uri) {
if (this._delegate) {
try {
return this._delegate(uri);
}
catch (err) {
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: isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
authority: `${host}:${port}`,
path: this._remoteResourcesPath,
query
});
}
}
const RemoteAuthorities = new RemoteAuthoritiesImpl();
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
isNative ||
// ...or web worker extensions on desktop
(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;
}
}
const FileAccess = new FileAccessImpl();
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) {
// eslint-disable-next-line local/code-no-any-casts
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 = {}));
export { COI, FileAccess, RemoteAuthorities, Schemas, VSCODE_AUTHORITY, connectionTokenQueryName, matchesScheme, matchesSomeScheme };
@@ -0,0 +1,41 @@
import { LRUCache } from './map.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
new LRUCache(10000);
const nfdCache = new LRUCache(10000); // bounded to 10000 elements
function normalizeNFD(str) {
return normalize(str, 'NFD', nfdCache);
}
const nonAsciiCharactersPattern = /[^\u0000-\u0080]/;
function normalize(str, form, normalizedCache) {
if (!str) {
return str;
}
const cached = normalizedCache.get(str);
if (cached) {
return cached;
}
let res;
if (nonAsciiCharactersPattern.test(str)) {
res = str.normalize(form);
}
else {
res = str;
}
// Use the cache for fast lookup
normalizedCache.set(str, res);
return res;
}
const removeAccents = (function () {
// transform into NFD form and remove accents
// see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463
const regex = /[\u0300-\u036f]/g;
return function (str) {
return normalizeNFD(str).replace(regex, '');
};
})();
export { normalizeNFD, removeAccents };
+45
View File
@@ -0,0 +1,45 @@
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
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;
}
}
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;
}
}
export { MovingAverage, SlidingWindowAverage, clamp };
+152
View File
@@ -0,0 +1,152 @@
import { isUndefinedOrNull, isObject, isTypedArray } from './types.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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;
}
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;
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).
*/
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;
}
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 { cloneAndChange, deepClone, deepFreeze, equals, mixin };
@@ -0,0 +1,19 @@
import { onUnexpectedError } from '../errors.js';
import '../arrays.js';
import '../event.js';
import '../lifecycle.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* This function is used to indicate that the caller recovered from an error that indicates a bug.
*/
function handleBugIndicatingErrorRecovery(message) {
const err = new Error('BugIndicatingErrorRecovery: ' + message);
onUnexpectedError(err);
console.error('recovered from an error that indicates a bug', err);
}
export { handleBugIndicatingErrorRecovery };
@@ -0,0 +1,80 @@
import { BugIndicatingError } from '../errors.js';
import '../arrays.js';
import '../event.js';
import '../lifecycle.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Subscribes to and records changes and the last value of the given observables.
* Don't use the key "changes", as it is reserved for the changes array!
*/
function recordChanges(obs) {
return {
createChangeSummary: (_previousChangeSummary) => {
// eslint-disable-next-line local/code-no-any-casts
return {
changes: [],
};
},
handleChange(ctx, changeSummary) {
for (const key in obs) {
if (ctx.didChange(obs[key])) {
// eslint-disable-next-line local/code-no-any-casts
changeSummary.changes.push({ key, change: ctx.change });
}
}
return true;
},
beforeUpdate(reader, changeSummary) {
for (const key in obs) {
if (key === 'changes') {
throw new BugIndicatingError('property name "changes" is reserved for change tracking');
}
changeSummary[key] = obs[key].read(reader);
}
}
};
}
/**
* Subscribes to and records changes and the last value of the given observables.
* Don't use the key "changes", as it is reserved for the changes array!
*/
function recordChangesLazy(getObs) {
let obs = undefined;
return {
createChangeSummary: (_previousChangeSummary) => {
// eslint-disable-next-line local/code-no-any-casts
return {
changes: [],
};
},
handleChange(ctx, changeSummary) {
if (!obs) {
obs = getObs();
}
for (const key in obs) {
if (ctx.didChange(obs[key])) {
// eslint-disable-next-line local/code-no-any-casts
changeSummary.changes.push({ key, change: ctx.change });
}
}
return true;
},
beforeUpdate(reader, changeSummary) {
if (!obs) {
obs = getObs();
}
for (const key in obs) {
if (key === 'changes') {
throw new BugIndicatingError('property name "changes" is reserved for change tracking');
}
changeSummary[key] = obs[key].read(reader);
}
}
};
}
export { recordChanges, recordChangesLazy };
@@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var DebugLocation;
(function (DebugLocation) {
let enabled = false;
function enable() {
enabled = true;
}
DebugLocation.enable = enable;
function ofCaller() {
if (!enabled) {
return undefined;
}
// eslint-disable-next-line local/code-no-any-casts
const Err = Error; // For the monaco editor checks, which don't have the nodejs types.
const l = Err.stackTraceLimit;
Err.stackTraceLimit = 3;
const stack = new Error().stack;
Err.stackTraceLimit = l;
return DebugLocationImpl.fromStack(stack, 2);
}
DebugLocation.ofCaller = ofCaller;
})(DebugLocation || (DebugLocation = {}));
class DebugLocationImpl {
static fromStack(stack, parentIdx) {
const lines = stack.split('\n');
const location = parseLine(lines[parentIdx + 1]);
if (location) {
return new DebugLocationImpl(location.fileName, location.line, location.column, location.id);
}
else {
return undefined;
}
}
constructor(fileName, line, column, id) {
this.fileName = fileName;
this.line = line;
this.column = column;
this.id = id;
}
}
function parseLine(stackLine) {
const match = stackLine.match(/\((.*):(\d+):(\d+)\)/);
if (match) {
return {
fileName: match[1],
line: parseInt(match[2]),
column: parseInt(match[3]),
id: stackLine,
};
}
const match2 = stackLine.match(/at ([^\(\)]*):(\d+):(\d+)/);
if (match2) {
return {
fileName: match2[1],
line: parseInt(match2[2]),
column: parseInt(match2[3]),
id: stackLine,
};
}
return undefined;
}
export { DebugLocation };
@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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();
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) {
// eslint-disable-next-line local/code-no-any-casts
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) ?? 'Object';
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) {
if (ctor.name === 'Object') {
return undefined;
}
return ctor.name;
}
return undefined;
}
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();
}
export { DebugNameData, getClassName, getDebugName, getFunctionName };
@@ -0,0 +1,46 @@
import { BugIndicatingError } from '../../errors.js';
import { strictEquals } from '../../equals.js';
import '../../event.js';
import '../../lifecycle.js';
import { subtransaction } from '../transaction.js';
import { DebugNameData } from '../debugName.js';
import { DerivedWithSetter } from '../observables/derivedImpl.js';
import { DebugLocation } from '../debugLocation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Creates an observable value that is based on values and changes from other observables.
* Additionally, a reducer can report how that state changed.
*/
function observableReducerSettable(owner, options) {
let prevValue = undefined;
let hasValue = false;
const d = new DerivedWithSetter(new DebugNameData(owner, undefined, options.update), (reader, changeSummary) => {
if (!hasValue) {
prevValue = options.initial instanceof Function ? options.initial() : options.initial;
hasValue = true;
}
const newValue = options.update(reader, prevValue, changeSummary);
prevValue = newValue;
return newValue;
}, options.changeTracker, () => {
if (hasValue) {
options.disposeFinal?.(prevValue);
hasValue = false;
}
}, options.equalityComparer ?? strictEquals, (value, tx, change) => {
if (!hasValue) {
throw new BugIndicatingError('Can only set when there is a listener! This is to prevent leaks.');
}
subtransaction(tx, tx => {
prevValue = value;
d.setValue(value, tx, change);
});
}, DebugLocation.ofCaller());
return d;
}
export { observableReducerSettable };
@@ -0,0 +1,16 @@
import { derivedObservableWithCache } from '../utils/utils.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* 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.
*/
function derivedConstOnceDefined(owner, fn) {
return derivedObservableWithCache(owner, (reader, lastValue) => lastValue ?? fn(reader));
}
export { derivedConstOnceDefined };
@@ -0,0 +1,21 @@
import '../arrays.js';
import '../event.js';
import '../lifecycle.js';
import { addLogger } from './logging/logging.js';
export { DebugLocation } from './debugLocation.js';
export { derived, derivedDisposable, derivedHandleChanges, derivedOpts, derivedWithSetter } from './observables/derived.js';
import '../cancellation.js';
export { debouncedObservable, derivedObservableWithCache, mapObservableArrayCached, recomputeInitiallyAndOnChange } from './utils/utils.js';
export { observableFromEvent, observableFromEventOpts } from './observables/observableFromEvent.js';
import { DevToolsLogger } from './logging/debugger/devToolsLogger.js';
import { env } from '../process.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// This is a facade for the observable implementation. Only import from here!
if (env && env['VSCODE_DEV_DEBUG_OBSERVABLES']) {
// To debug observables you also need the extension "ms-vscode.debug-value-editor"
addLogger(DevToolsLogger.getInstance());
}
@@ -0,0 +1,83 @@
import { getClassName } from '../debugName.js';
import '../debugLocation.js';
import '../../arrays.js';
import '../../event.js';
import '../../lifecycle.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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) {
if (typeof value.toString === 'function' && value.toString !== Object.prototype.toString) {
const val = value.toString();
if (val.length <= availableLen) {
return val;
}
return val.substring(0, availableLen - 3) + '...';
}
const className = getClassName(value);
let result = className ? className + '(' : '{ ';
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 += className ? ')' : ' }';
return result;
}
export { formatValue };
@@ -0,0 +1,67 @@
import { SimpleTypedRpcConnection } from './rpc.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function registerDebugChannel(channelId, createClient) {
// eslint-disable-next-line local/code-no-any-casts
const g = globalThis;
let queuedNotifications = [];
let curHost = undefined;
const { channel, handler } = createChannelFactoryFromDebugChannel({
sendNotification: (data) => {
if (curHost) {
curHost.sendNotification(data);
}
else {
queuedNotifications.push(data);
}
},
});
let curClient = undefined;
(g.$$debugValueEditor_debugChannels ?? (g.$$debugValueEditor_debugChannels = {}))[channelId] = (host) => {
curClient = createClient();
curHost = host;
for (const n of queuedNotifications) {
host.sendNotification(n);
}
queuedNotifications = [];
return handler;
};
return SimpleTypedRpcConnection.createClient(channel, () => {
if (!curClient) {
throw new Error('Not supported');
}
return curClient;
});
}
function createChannelFactoryFromDebugChannel(host) {
let h;
const channel = (handler) => {
h = handler;
return {
sendNotification: data => {
host.sendNotification(data);
},
sendRequest: data => {
throw new Error('not supported');
},
};
};
return {
channel: channel,
handler: {
handleRequest: (data) => {
if (data.type === 'notification') {
return h?.handleNotification(data.data);
}
else {
return h?.handleRequest(data.data);
}
},
},
};
}
export { registerDebugChannel };
@@ -0,0 +1,442 @@
import { AutorunObserver } from '../../reactions/autorunImpl.js';
import { formatValue } from '../consoleObservableLogger.js';
import { registerDebugChannel } from './debuggerRpc.js';
import { Throttler, deepAssignDeleteNulls, deepAssign } from './utils.js';
import { isDefined } from '../../../types.js';
import { FromEventObservable } from '../../observables/observableFromEvent.js';
import { onUnexpectedError, BugIndicatingError } from '../../../errors.js';
import { Derived } from '../../observables/derivedImpl.js';
import { ObservableValue } from '../../observables/observableValue.js';
import { DebugLocation } from '../../debugLocation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class DevToolsLogger {
static { this._instance = undefined; }
static getInstance() {
if (DevToolsLogger._instance === undefined) {
DevToolsLogger._instance = new DevToolsLogger();
}
return DevToolsLogger._instance;
}
getTransactionState() {
const affected = [];
const txs = [...this._activeTransactions];
if (txs.length === 0) {
return undefined;
}
const observerQueue = txs.flatMap(t => t.debugGetUpdatingObservers() ?? []).map(o => o.observer);
const processedObservers = new Set();
while (observerQueue.length > 0) {
const observer = observerQueue.shift();
if (processedObservers.has(observer)) {
continue;
}
processedObservers.add(observer);
const state = this._getInfo(observer, d => {
if (!processedObservers.has(d)) {
observerQueue.push(d);
}
});
if (state) {
affected.push(state);
}
}
return { names: txs.map(t => t.getDebugName() ?? 'tx'), affected };
}
_getObservableInfo(observable) {
const info = this._instanceInfos.get(observable);
if (!info) {
onUnexpectedError(new BugIndicatingError('No info found'));
return undefined;
}
return info;
}
_getAutorunInfo(autorun) {
const info = this._instanceInfos.get(autorun);
if (!info) {
onUnexpectedError(new BugIndicatingError('No info found'));
return undefined;
}
return info;
}
_getInfo(observer, queue) {
if (observer instanceof Derived) {
const observersToUpdate = [...observer.debugGetObservers()];
for (const o of observersToUpdate) {
queue(o);
}
const info = this._getObservableInfo(observer);
if (!info) {
return;
}
const observerState = observer.debugGetState();
const base = { name: observer.debugName, instanceId: info.instanceId, updateCount: observerState.updateCount };
const changedDependencies = [...info.changedObservables].map(o => this._instanceInfos.get(o)?.instanceId).filter(isDefined);
if (observerState.isComputing) {
return { ...base, type: 'observable/derived', state: 'updating', changedDependencies, initialComputation: false };
}
switch (observerState.state) {
case 0 /* DerivedState.initial */:
return { ...base, type: 'observable/derived', state: 'noValue' };
case 3 /* DerivedState.upToDate */:
return { ...base, type: 'observable/derived', state: 'upToDate' };
case 2 /* DerivedState.stale */:
return { ...base, type: 'observable/derived', state: 'stale', changedDependencies };
case 1 /* DerivedState.dependenciesMightHaveChanged */:
return { ...base, type: 'observable/derived', state: 'possiblyStale' };
}
}
else if (observer instanceof AutorunObserver) {
const info = this._getAutorunInfo(observer);
if (!info) {
return undefined;
}
const base = { name: observer.debugName, instanceId: info.instanceId, updateCount: info.updateCount };
const changedDependencies = [...info.changedObservables].map(o => this._instanceInfos.get(o).instanceId);
if (observer.debugGetState().isRunning) {
return { ...base, type: 'autorun', state: 'updating', changedDependencies };
}
switch (observer.debugGetState().state) {
case 3 /* AutorunState.upToDate */:
return { ...base, type: 'autorun', state: 'upToDate' };
case 2 /* AutorunState.stale */:
return { ...base, type: 'autorun', state: 'stale', changedDependencies };
case 1 /* AutorunState.dependenciesMightHaveChanged */:
return { ...base, type: 'autorun', state: 'possiblyStale' };
}
}
return undefined;
}
_formatObservable(obs) {
const info = this._getObservableInfo(obs);
if (!info) {
return undefined;
}
return { name: obs.debugName, instanceId: info.instanceId };
}
_formatObserver(obs) {
if (obs instanceof Derived) {
return { name: obs.toString(), instanceId: this._getObservableInfo(obs)?.instanceId };
}
const autorunInfo = this._getAutorunInfo(obs);
if (autorunInfo) {
return { name: obs.toString(), instanceId: autorunInfo.instanceId };
}
return undefined;
}
constructor() {
this._declarationId = 0;
this._instanceId = 0;
this._declarations = new Map();
this._instanceInfos = new WeakMap();
this._aliveInstances = new Map();
this._activeTransactions = new Set();
this._channel = registerDebugChannel('observableDevTools', () => {
return {
notifications: {
setDeclarationIdFilter: declarationIds => {
},
logObservableValue: (observableId) => {
console.log('logObservableValue', observableId);
},
flushUpdates: () => {
this._flushUpdates();
},
resetUpdates: () => {
this._pendingChanges = null;
this._channel.api.notifications.handleChange(this._fullState, true);
},
},
requests: {
getDeclarations: () => {
const result = {};
for (const decl of this._declarations.values()) {
result[decl.id] = decl;
}
return { decls: result };
},
getSummarizedInstances: () => {
return null;
},
getObservableValueInfo: instanceId => {
const obs = this._aliveInstances.get(instanceId);
return {
observers: [...obs.debugGetObservers()].map(d => this._formatObserver(d)).filter(isDefined),
};
},
getDerivedInfo: instanceId => {
const d = this._aliveInstances.get(instanceId);
return {
dependencies: [...d.debugGetState().dependencies].map(d => this._formatObservable(d)).filter(isDefined),
observers: [...d.debugGetObservers()].map(d => this._formatObserver(d)).filter(isDefined),
};
},
getAutorunInfo: instanceId => {
const obs = this._aliveInstances.get(instanceId);
return {
dependencies: [...obs.debugGetState().dependencies].map(d => this._formatObservable(d)).filter(isDefined),
};
},
getTransactionState: () => {
return this.getTransactionState();
},
setValue: (instanceId, jsonValue) => {
const obs = this._aliveInstances.get(instanceId);
if (obs instanceof Derived) {
obs.debugSetValue(jsonValue);
}
else if (obs instanceof ObservableValue) {
obs.debugSetValue(jsonValue);
}
else if (obs instanceof FromEventObservable) {
obs.debugSetValue(jsonValue);
}
else {
throw new BugIndicatingError('Observable is not supported');
}
const observers = [...obs.debugGetObservers()];
for (const d of observers) {
d.beginUpdate(obs);
}
for (const d of observers) {
d.handleChange(obs, undefined);
}
for (const d of observers) {
d.endUpdate(obs);
}
},
getValue: instanceId => {
const obs = this._aliveInstances.get(instanceId);
if (obs instanceof Derived) {
return formatValue(obs.debugGetState().value, 200);
}
else if (obs instanceof ObservableValue) {
return formatValue(obs.debugGetState().value, 200);
}
return undefined;
},
logValue: (instanceId) => {
const obs = this._aliveInstances.get(instanceId);
if (obs && 'get' in obs) {
console.log('Logged Value:', obs.get());
}
else {
throw new BugIndicatingError('Observable is not supported');
}
},
rerun: (instanceId) => {
const obs = this._aliveInstances.get(instanceId);
if (obs instanceof Derived) {
obs.debugRecompute();
}
else if (obs instanceof AutorunObserver) {
obs.debugRerun();
}
else {
throw new BugIndicatingError('Observable is not supported');
}
},
}
};
});
this._pendingChanges = null;
this._changeThrottler = new Throttler();
this._fullState = {};
this._flushUpdates = () => {
if (this._pendingChanges !== null) {
this._channel.api.notifications.handleChange(this._pendingChanges, false);
this._pendingChanges = null;
}
};
DebugLocation.enable();
}
_handleChange(update) {
deepAssignDeleteNulls(this._fullState, update);
if (this._pendingChanges === null) {
this._pendingChanges = update;
}
else {
deepAssign(this._pendingChanges, update);
}
this._changeThrottler.throttle(this._flushUpdates, 10);
}
_getDeclarationId(type, location) {
if (!location) {
return -1;
}
let decInfo = this._declarations.get(location.id);
if (decInfo === undefined) {
decInfo = {
id: this._declarationId++,
type,
url: location.fileName,
line: location.line,
column: location.column,
};
this._declarations.set(location.id, decInfo);
this._handleChange({ decls: { [decInfo.id]: decInfo } });
}
return decInfo.id;
}
handleObservableCreated(observable, location) {
const declarationId = this._getDeclarationId('observable/value', location);
const info = {
declarationId,
instanceId: this._instanceId++,
listenerCount: 0,
lastValue: undefined,
updateCount: 0,
changedObservables: new Set(),
};
this._instanceInfos.set(observable, info);
}
handleOnListenerCountChanged(observable, newCount) {
const info = this._getObservableInfo(observable);
if (!info) {
return;
}
if (info.listenerCount === 0 && newCount > 0) {
const type = observable instanceof Derived ? 'observable/derived' : 'observable/value';
this._aliveInstances.set(info.instanceId, observable);
this._handleChange({
instances: {
[info.instanceId]: {
instanceId: info.instanceId,
declarationId: info.declarationId,
formattedValue: info.lastValue,
type,
name: observable.debugName,
}
}
});
}
else if (info.listenerCount > 0 && newCount === 0) {
this._handleChange({
instances: { [info.instanceId]: null }
});
this._aliveInstances.delete(info.instanceId);
}
info.listenerCount = newCount;
}
handleObservableUpdated(observable, changeInfo) {
if (observable instanceof Derived) {
this._handleDerivedRecomputed(observable, changeInfo);
return;
}
const info = this._getObservableInfo(observable);
if (info) {
if (changeInfo.didChange) {
info.lastValue = formatValue(changeInfo.newValue, 30);
if (info.listenerCount > 0) {
this._handleChange({
instances: { [info.instanceId]: { formattedValue: info.lastValue } }
});
}
}
}
}
handleAutorunCreated(autorun, location) {
const declarationId = this._getDeclarationId('autorun', location);
const info = {
declarationId,
instanceId: this._instanceId++,
updateCount: 0,
changedObservables: new Set(),
};
this._instanceInfos.set(autorun, info);
this._aliveInstances.set(info.instanceId, autorun);
if (info) {
this._handleChange({
instances: {
[info.instanceId]: {
instanceId: info.instanceId,
declarationId: info.declarationId,
runCount: 0,
type: 'autorun',
name: autorun.debugName,
}
}
});
}
}
handleAutorunDisposed(autorun) {
const info = this._getAutorunInfo(autorun);
if (!info) {
return;
}
this._handleChange({
instances: { [info.instanceId]: null }
});
this._instanceInfos.delete(autorun);
this._aliveInstances.delete(info.instanceId);
}
handleAutorunDependencyChanged(autorun, observable, change) {
const info = this._getAutorunInfo(autorun);
if (!info) {
return;
}
info.changedObservables.add(observable);
}
handleAutorunStarted(autorun) {
}
handleAutorunFinished(autorun) {
const info = this._getAutorunInfo(autorun);
if (!info) {
return;
}
info.changedObservables.clear();
info.updateCount++;
this._handleChange({
instances: { [info.instanceId]: { runCount: info.updateCount } }
});
}
handleDerivedDependencyChanged(derived, observable, change) {
const info = this._getObservableInfo(derived);
if (info) {
info.changedObservables.add(observable);
}
}
_handleDerivedRecomputed(observable, changeInfo) {
const info = this._getObservableInfo(observable);
if (!info) {
return;
}
const formattedValue = formatValue(changeInfo.newValue, 30);
info.updateCount++;
info.changedObservables.clear();
info.lastValue = formattedValue;
if (info.listenerCount > 0) {
this._handleChange({
instances: { [info.instanceId]: { formattedValue: formattedValue, recomputationCount: info.updateCount } }
});
}
}
handleDerivedCleared(observable) {
const info = this._getObservableInfo(observable);
if (!info) {
return;
}
info.lastValue = undefined;
info.changedObservables.clear();
if (info.listenerCount > 0) {
this._handleChange({
instances: {
[info.instanceId]: {
formattedValue: undefined,
}
}
});
}
}
handleBeginTransaction(transaction) {
this._activeTransactions.add(transaction);
}
handleEndTransaction(transaction) {
this._activeTransactions.delete(transaction);
}
}
export { DevToolsLogger };
@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class SimpleTypedRpcConnection {
static createClient(channelFactory, getHandler) {
return new SimpleTypedRpcConnection(channelFactory, getHandler);
}
constructor(_channelFactory, _getHandler) {
this._channelFactory = _channelFactory;
this._getHandler = _getHandler;
this._channel = this._channelFactory({
handleNotification: (notificationData) => {
const m = notificationData;
const fn = this._getHandler().notifications[m[0]];
if (!fn) {
throw new Error(`Unknown notification "${m[0]}"!`);
}
fn(...m[1]);
},
handleRequest: (requestData) => {
const m = requestData;
try {
const result = this._getHandler().requests[m[0]](...m[1]);
return { type: 'result', value: result };
}
catch (e) {
return { type: 'error', value: e };
}
},
});
const requests = new Proxy({}, {
get: (target, key) => {
return async (...args) => {
const result = await this._channel.sendRequest([key, args]);
if (result.type === 'error') {
throw result.value;
}
else {
return result.value;
}
};
}
});
const notifications = new Proxy({}, {
get: (target, key) => {
return (...args) => {
this._channel.sendNotification([key, args]);
};
}
});
// eslint-disable-next-line local/code-no-any-casts
this.api = { notifications: notifications, requests: requests };
}
}
export { SimpleTypedRpcConnection };
@@ -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.
*--------------------------------------------------------------------------------------------*/
class Throttler {
constructor() {
this._timeout = undefined;
}
throttle(fn, timeoutMs) {
if (this._timeout === undefined) {
this._timeout = setTimeout(() => {
this._timeout = undefined;
fn();
}, timeoutMs);
}
}
dispose() {
if (this._timeout !== undefined) {
clearTimeout(this._timeout);
}
}
}
function deepAssign(target, source) {
for (const key in source) {
if (!!target[key] && typeof target[key] === 'object' && !!source[key] && typeof source[key] === 'object') {
deepAssign(target[key], source[key]);
}
else {
target[key] = source[key];
}
}
}
function deepAssignDeleteNulls(target, source) {
for (const key in source) {
if (source[key] === null) {
delete target[key];
}
else if (!!target[key] && typeof target[key] === 'object' && !!source[key] && typeof source[key] === 'object') {
deepAssignDeleteNulls(target[key], source[key]);
}
else {
target[key] = source[key];
}
}
}
export { Throttler, deepAssign, deepAssignDeleteNulls };
@@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
let globalObservableLogger;
function addLogger(logger) {
if (!globalObservableLogger) {
globalObservableLogger = logger;
}
else if (globalObservableLogger instanceof ComposedLogger) {
globalObservableLogger.loggers.push(logger);
}
else {
globalObservableLogger = new ComposedLogger([globalObservableLogger, logger]);
}
}
function getLogger() {
return globalObservableLogger;
}
class ComposedLogger {
constructor(loggers) {
this.loggers = loggers;
}
handleObservableCreated(observable, location) {
for (const logger of this.loggers) {
logger.handleObservableCreated(observable, location);
}
}
handleOnListenerCountChanged(observable, newCount) {
for (const logger of this.loggers) {
logger.handleOnListenerCountChanged(observable, newCount);
}
}
handleObservableUpdated(observable, info) {
for (const logger of this.loggers) {
logger.handleObservableUpdated(observable, info);
}
}
handleAutorunCreated(autorun, location) {
for (const logger of this.loggers) {
logger.handleAutorunCreated(autorun, location);
}
}
handleAutorunDisposed(autorun) {
for (const logger of this.loggers) {
logger.handleAutorunDisposed(autorun);
}
}
handleAutorunDependencyChanged(autorun, observable, change) {
for (const logger of this.loggers) {
logger.handleAutorunDependencyChanged(autorun, observable, change);
}
}
handleAutorunStarted(autorun) {
for (const logger of this.loggers) {
logger.handleAutorunStarted(autorun);
}
}
handleAutorunFinished(autorun) {
for (const logger of this.loggers) {
logger.handleAutorunFinished(autorun);
}
}
handleDerivedDependencyChanged(derived, observable, change) {
for (const logger of this.loggers) {
logger.handleDerivedDependencyChanged(derived, observable, change);
}
}
handleDerivedCleared(observable) {
for (const logger of this.loggers) {
logger.handleDerivedCleared(observable);
}
}
handleBeginTransaction(transaction) {
for (const logger of this.loggers) {
logger.handleBeginTransaction(transaction);
}
}
handleEndTransaction(transaction) {
for (const logger of this.loggers) {
logger.handleEndTransaction(transaction);
}
}
}
export { addLogger, getLogger };
@@ -0,0 +1,106 @@
import { DebugLocation } from '../debugLocation.js';
import { getFunctionName } from '../debugName.js';
import { getLogger } from '../logging/logging.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
let _derived;
/**
* @internal
* This is to allow splitting files.
*/
function _setDerivedOpts(derived) {
_derived = derived;
}
let _recomputeInitiallyAndOnChange;
function _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange) {
_recomputeInitiallyAndOnChange = recomputeInitiallyAndOnChange;
}
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, debugLocation = DebugLocation.ofCaller()) {
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), debugLocation);
}
/**
* @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;
}
}
class BaseObservable extends ConvenientObservable {
constructor(debugLocation) {
super();
this._observers = new Set();
getLogger()?.handleObservableCreated(this, debugLocation);
}
addObserver(observer) {
const len = this._observers.size;
this._observers.add(observer);
if (len === 0) {
this.onFirstObserverAdded();
}
if (len !== this._observers.size) {
getLogger()?.handleOnListenerCountChanged(this, this._observers.size);
}
}
removeObserver(observer) {
const deleted = this._observers.delete(observer);
if (deleted && this._observers.size === 0) {
this.onLastObserverRemoved();
}
if (deleted) {
getLogger()?.handleOnListenerCountChanged(this, this._observers.size);
}
}
onFirstObserverAdded() { }
onLastObserverRemoved() { }
debugGetObservers() {
return this._observers;
}
}
export { BaseObservable, ConvenientObservable, _setDerivedOpts, _setRecomputeInitiallyAndOnChange };
@@ -0,0 +1,35 @@
import { ConvenientObservable } from './baseObservable.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Represents an efficient observable whose value never changes.
*/
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 { constObservable };
@@ -0,0 +1,80 @@
import { strictEquals } from '../../equals.js';
import '../../event.js';
import { DisposableStore } from '../../lifecycle.js';
import { DebugLocation } from '../debugLocation.js';
import { DebugNameData } from '../debugName.js';
import { _setDerivedOpts } from './baseObservable.js';
import { Derived, DerivedWithSetter } from './derivedImpl.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function derived(computeFnOrOwner, computeFn, debugLocation = DebugLocation.ofCaller()) {
if (computeFn !== undefined) {
return new Derived(new DebugNameData(computeFnOrOwner, undefined, computeFn), computeFn, undefined, undefined, strictEquals, debugLocation);
}
return new Derived(
// eslint-disable-next-line local/code-no-any-casts
new DebugNameData(undefined, undefined, computeFnOrOwner),
// eslint-disable-next-line local/code-no-any-casts
computeFnOrOwner, undefined, undefined, strictEquals, debugLocation);
}
function derivedWithSetter(owner, computeFn, setter, debugLocation = DebugLocation.ofCaller()) {
return new DerivedWithSetter(new DebugNameData(owner, undefined, computeFn), computeFn, undefined, undefined, strictEquals, setter, debugLocation);
}
function derivedOpts(options, computeFn, debugLocation = DebugLocation.ofCaller()) {
return new Derived(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn), computeFn, undefined, options.onLastObserverRemoved, options.equalsFn ?? strictEquals, debugLocation);
}
_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
*/
function derivedHandleChanges(options, computeFn, debugLocation = DebugLocation.ofCaller()) {
return new Derived(new DebugNameData(options.owner, options.debugName, undefined), computeFn, options.changeTracker, undefined, options.equalityComparer ?? strictEquals, debugLocation);
}
function derivedDisposable(computeFnOrOwner, computeFnOrUndefined, debugLocation = DebugLocation.ofCaller()) {
let computeFn;
let owner;
if (computeFnOrUndefined === undefined) {
// eslint-disable-next-line local/code-no-any-casts
computeFn = computeFnOrOwner;
owner = undefined;
}
else {
owner = computeFnOrOwner;
// eslint-disable-next-line local/code-no-any-casts
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, () => {
if (store) {
store.dispose();
store = undefined;
}
}, strictEquals, debugLocation);
}
export { derived, derivedDisposable, derivedHandleChanges, derivedOpts, derivedWithSetter };
@@ -0,0 +1,353 @@
import { BaseObservable } from './baseObservable.js';
import { assertFn } from '../../assert.js';
import '../../arrays.js';
import { onBugIndicatingError, BugIndicatingError } from '../../errors.js';
import '../../event.js';
import { DisposableStore } from '../../lifecycle.js';
import { getLogger } from '../logging/logging.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function derivedStateToString(state) {
switch (state) {
case 0 /* DerivedState.initial */: return 'initial';
case 1 /* DerivedState.dependenciesMightHaveChanged */: return 'dependenciesMightHaveChanged';
case 2 /* DerivedState.stale */: return 'stale';
case 3 /* DerivedState.upToDate */: return 'upToDate';
default: return '<unknown>';
}
}
class Derived extends BaseObservable {
get debugName() {
return this._debugNameData.getDebugName(this) ?? '(anonymous)';
}
constructor(_debugNameData, _computeFn, _changeTracker, _handleLastObserverRemoved = undefined, _equalityComparator, debugLocation) {
super(debugLocation);
this._debugNameData = _debugNameData;
this._computeFn = _computeFn;
this._changeTracker = _changeTracker;
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._isUpdating = false;
this._isComputing = false;
this._didReportChange = false;
this._isInBeforeUpdate = false;
this._isReaderValid = false;
this._store = undefined;
this._delayedStore = undefined;
this._removedObserverToCallEndUpdateOn = null;
this._changeSummary = this._changeTracker?.createChangeSummary(undefined);
}
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;
getLogger()?.handleDerivedCleared(this);
for (const d of this._dependencies) {
d.removeObserver(this);
}
this._dependencies.clear();
if (this._store !== undefined) {
this._store.dispose();
this._store = undefined;
}
if (this._delayedStore !== undefined) {
this._delayedStore.dispose();
this._delayedStore = undefined;
}
this._handleLastObserverRemoved?.();
}
get() {
const checkEnabled = false; // TODO set to true
if (this._isComputing && checkEnabled) ;
if (this._observers.size === 0) {
let result;
// Without observers, we don't know when to clean up stuff.
// Thus, we don't cache anything to prevent memory leaks.
try {
this._isReaderValid = true;
let changeSummary = undefined;
if (this._changeTracker) {
changeSummary = this._changeTracker.createChangeSummary(undefined);
this._changeTracker.beforeUpdate?.(this, changeSummary);
}
result = this._computeFn(this, changeSummary);
}
finally {
this._isReaderValid = false;
}
// 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 */;
}
if (this._state !== 3 /* DerivedState.upToDate */) {
this._recompute();
}
// In case recomputation changed one of our dependencies, we need to recompute again.
} while (this._state !== 3 /* DerivedState.upToDate */);
return this._value;
}
}
_recompute() {
let didChange = false;
this._isComputing = true;
this._didReportChange = false;
const emptySet = this._dependenciesToBeRemoved;
this._dependenciesToBeRemoved = this._dependencies;
this._dependencies = emptySet;
try {
const changeSummary = this._changeSummary;
this._isReaderValid = true;
if (this._changeTracker) {
this._isInBeforeUpdate = true;
this._changeTracker.beforeUpdate?.(this, changeSummary);
this._isInBeforeUpdate = false;
this._changeSummary = this._changeTracker?.createChangeSummary(changeSummary);
}
const hadValue = this._state !== 0 /* DerivedState.initial */;
const oldValue = this._value;
this._state = 3 /* DerivedState.upToDate */;
const delayedStore = this._delayedStore;
if (delayedStore !== undefined) {
this._delayedStore = undefined;
}
try {
if (this._store !== undefined) {
this._store.dispose();
this._store = undefined;
}
/** might call {@link handleChange} indirectly, which could invalidate us */
this._value = this._computeFn(this, changeSummary);
}
finally {
this._isReaderValid = false;
// 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();
if (delayedStore !== undefined) {
delayedStore.dispose();
}
}
didChange = this._didReportChange || (hadValue && !(this._equalityComparator(oldValue, this._value)));
getLogger()?.handleObservableUpdated(this, {
oldValue,
newValue: this._value,
change: undefined,
didChange,
hadValue,
});
}
catch (e) {
onBugIndicatingError(e);
}
this._isComputing = false;
if (!this._didReportChange && didChange) {
for (const r of this._observers) {
r.handleChange(this, undefined);
}
}
else {
this._didReportChange = false;
}
}
toString() {
return `LazyDerived<${this.debugName}>`;
}
// IObserver Implementation
beginUpdate(_observable) {
if (this._isUpdating) {
throw new BugIndicatingError('Cyclic deriveds are not supported yet!');
}
this._updateCount++;
this._isUpdating = true;
try {
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
}
}
}
finally {
this._isUpdating = false;
}
}
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);
}
if (this._removedObserverToCallEndUpdateOn) {
const observers = [...this._removedObserverToCallEndUpdateOn];
this._removedObserverToCallEndUpdateOn = null;
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) || this._isInBeforeUpdate) {
getLogger()?.handleDerivedDependencyChanged(this, observable, change);
let shouldReact = false;
try {
shouldReact = this._changeTracker ? this._changeTracker.handleChange({
changedObservable: observable,
change,
// eslint-disable-next-line local/code-no-any-casts
didChange: (o) => o === observable,
}, this._changeSummary) : true;
}
catch (e) {
onBugIndicatingError(e);
}
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
_ensureReaderValid() {
if (!this._isReaderValid) {
throw new BugIndicatingError('The reader object cannot be used outside its compute function!');
}
}
readObservable(observable) {
this._ensureReaderValid();
// 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;
}
get store() {
this._ensureReaderValid();
if (this._store === undefined) {
this._store = new DisposableStore();
}
return this._store;
}
addObserver(observer) {
const shouldCallBeginUpdate = !this._observers.has(observer) && this._updateCount > 0;
super.addObserver(observer);
if (shouldCallBeginUpdate) {
if (this._removedObserverToCallEndUpdateOn && this._removedObserverToCallEndUpdateOn.has(observer)) {
this._removedObserverToCallEndUpdateOn.delete(observer);
}
else {
observer.beginUpdate(this);
}
}
}
removeObserver(observer) {
if (this._observers.has(observer) && this._updateCount > 0) {
if (!this._removedObserverToCallEndUpdateOn) {
this._removedObserverToCallEndUpdateOn = new Set();
}
this._removedObserverToCallEndUpdateOn.add(observer);
}
super.removeObserver(observer);
}
debugGetState() {
return {
state: this._state,
stateStr: derivedStateToString(this._state),
updateCount: this._updateCount,
isComputing: this._isComputing,
dependencies: this._dependencies,
value: this._value,
};
}
debugSetValue(newValue) {
// eslint-disable-next-line local/code-no-any-casts
this._value = newValue;
}
debugRecompute() {
if (!this._isComputing) {
this._recompute();
}
else {
this._state = 2 /* DerivedState.stale */;
}
}
setValue(newValue, tx, change) {
this._value = newValue;
const observers = this._observers;
tx.updateObserver(this, this);
for (const d of observers) {
d.handleChange(this, change);
}
}
}
class DerivedWithSetter extends Derived {
constructor(debugNameData, computeFn, changeTracker, handleLastObserverRemoved = undefined, equalityComparator, set, debugLocation) {
super(debugNameData, computeFn, changeTracker, handleLastObserverRemoved, equalityComparator, debugLocation);
this.set = set;
}
}
export { Derived, DerivedWithSetter };
@@ -0,0 +1,125 @@
import { TransactionImpl } from '../transaction.js';
import { getLogger } from '../logging/logging.js';
import { BaseObservable } from './baseObservable.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Holds off updating observers until the value is actually read.
*/
class LazyObservableValue extends BaseObservable {
get debugName() {
return this._debugNameData.getDebugName(this) ?? 'LazyObservableValue';
}
constructor(_debugNameData, initialValue, _equalityComparator, debugLocation) {
super(debugLocation);
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 change of this._deltas) {
getLogger()?.handleObservableUpdated(this, { change, didChange: true, oldValue: '(unknown)', newValue: this._value, hadValue: true });
for (const observer of this._observers) {
observer.handleChange(this, change);
}
}
this._deltas.length = 0;
}
else {
getLogger()?.handleObservableUpdated(this, { change: undefined, didChange: true, oldValue: '(unknown)', newValue: this._value, hadValue: true });
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;
}
}
export { LazyObservableValue };
@@ -0,0 +1,123 @@
import { subtransaction } from '../transaction.js';
import { strictEquals } from '../../equals.js';
import '../../event.js';
import '../../lifecycle.js';
import { DebugNameData } from '../debugName.js';
import { getLogger } from '../logging/logging.js';
import { BaseObservable } from './baseObservable.js';
import { DebugLocation } from '../debugLocation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function observableFromEvent(...args) {
let owner;
let event;
let getValue;
let debugLocation;
if (args.length === 2) {
[event, getValue] = args;
}
else {
[owner, event, getValue, debugLocation] = args;
}
return new FromEventObservable(new DebugNameData(owner, undefined, getValue), event, getValue, () => FromEventObservable.globalTransaction, strictEquals, debugLocation ?? DebugLocation.ofCaller());
}
function observableFromEventOpts(options, event, getValue, debugLocation = DebugLocation.ofCaller()) {
return new FromEventObservable(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? getValue), event, getValue, () => FromEventObservable.globalTransaction, options.equalsFn ?? strictEquals, debugLocation);
}
class FromEventObservable extends BaseObservable {
constructor(_debugNameData, event, _getValue, _getTransaction, _equalityComparator, debugLocation) {
super(debugLocation);
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()?.handleObservableUpdated(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()?.handleObservableUpdated(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;
}
}
debugSetValue(value) {
// eslint-disable-next-line local/code-no-any-casts
this._value = value;
}
debugGetState() {
return { value: this._value, hasValue: this._hasValue };
}
}
(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 { FromEventObservable, observableFromEvent, observableFromEventOpts };
@@ -0,0 +1,47 @@
import { transaction } from '../transaction.js';
import { DebugNameData } from '../debugName.js';
import { BaseObservable } from './baseObservable.js';
import { DebugLocation } from '../debugLocation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function observableSignal(debugNameOrOwner, debugLocation = DebugLocation.ofCaller()) {
if (typeof debugNameOrOwner === 'string') {
return new ObservableSignal(debugNameOrOwner, undefined, debugLocation);
}
else {
return new ObservableSignal(undefined, debugNameOrOwner, debugLocation);
}
}
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, debugLocation) {
super(debugLocation);
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
}
}
export { observableSignal };
@@ -0,0 +1,41 @@
import { transaction } from '../transaction.js';
import { DebugNameData } from '../debugName.js';
import { BaseObservable } from './baseObservable.js';
import { DebugLocation } from '../debugLocation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function observableSignalFromEvent(owner, event, debugLocation = DebugLocation.ofCaller()) {
return new FromEventObservableSignal(typeof owner === 'string' ? owner : new DebugNameData(owner, undefined, undefined), event, debugLocation);
}
class FromEventObservableSignal extends BaseObservable {
constructor(debugNameDataOrName, event, debugLocation) {
super(debugLocation);
this.event = event;
this.handleEvent = () => {
transaction((tx) => {
for (const o of this._observers) {
tx.updateObserver(o, this);
o.handleChange(this, undefined);
}
}, () => this.debugName);
};
this.debugName = typeof debugNameDataOrName === 'string'
? debugNameDataOrName
: debugNameDataOrName.getDebugName(this) ?? 'Observable Signal From Event';
}
onFirstObserverAdded() {
this.subscription = this.event(this.handleEvent);
}
onLastObserverRemoved() {
this.subscription.dispose();
this.subscription = undefined;
}
get() {
// NO OP
}
}
export { observableSignalFromEvent };
@@ -0,0 +1,105 @@
import { TransactionImpl } from '../transaction.js';
import { BaseObservable } from './baseObservable.js';
import { strictEquals } from '../../equals.js';
import '../../event.js';
import '../../lifecycle.js';
import { DebugNameData } from '../debugName.js';
import { getLogger } from '../logging/logging.js';
import { DebugLocation } from '../debugLocation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function observableValue(nameOrOwner, initialValue, debugLocation = DebugLocation.ofCaller()) {
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, debugLocation);
}
class ObservableValue extends BaseObservable {
get debugName() {
return this._debugNameData.getDebugName(this) ?? 'ObservableValue';
}
constructor(_debugNameData, initialValue, _equalityComparator, debugLocation) {
super(debugLocation);
this._debugNameData = _debugNameData;
this._equalityComparator = _equalityComparator;
this._value = initialValue;
getLogger()?.handleObservableUpdated(this, { hadValue: false, newValue: initialValue, change: undefined, didChange: true, oldValue: undefined });
}
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()?.handleObservableUpdated(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;
}
debugGetState() {
return {
value: this._value,
};
}
debugSetValue(value) {
this._value = value;
}
}
/**
* A disposable observable. When disposed, its value is also disposed.
* When a new value is set, the previous value is disposed.
*/
function disposableObservableValue(nameOrOwner, initialValue, debugLocation = DebugLocation.ofCaller()) {
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, debugLocation);
}
class DisposableObservableValue extends ObservableValue {
_setValue(newValue) {
if (this._value === newValue) {
return;
}
if (this._value) {
this._value.dispose();
}
this._value = newValue;
}
dispose() {
this._value?.dispose();
}
}
export { DisposableObservableValue, ObservableValue, disposableObservableValue, observableValue };
@@ -0,0 +1,20 @@
import { DebugNameData } from '../debugName.js';
import { strictEquals } from '../../equals.js';
import '../../event.js';
import '../../lifecycle.js';
import { ObservableValue } from './observableValue.js';
import { LazyObservableValue } from './lazyObservableValue.js';
import { DebugLocation } from '../debugLocation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function observableValueOpts(options, initialValue, debugLocation = DebugLocation.ofCaller()) {
if (options.lazy) {
return new LazyObservableValue(new DebugNameData(options.owner, options.debugName, undefined), initialValue, options.equalsFn ?? strictEquals, debugLocation);
}
return new ObservableValue(new DebugNameData(options.owner, options.debugName, undefined), initialValue, options.equalsFn ?? strictEquals, debugLocation);
}
export { observableValueOpts };
@@ -0,0 +1,89 @@
import '../../arrays.js';
import '../../event.js';
import { toDisposable, DisposableStore } from '../../lifecycle.js';
import { DebugNameData } from '../debugName.js';
import { AutorunObserver } from './autorunImpl.js';
import { DebugLocation } from '../debugLocation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* 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.
*/
function autorun(fn, debugLocation = DebugLocation.ofCaller()) {
return new AutorunObserver(new DebugNameData(undefined, undefined, fn), fn, undefined, debugLocation);
}
/**
* 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.
*/
function autorunOpts(options, fn, debugLocation = DebugLocation.ofCaller()) {
return new AutorunObserver(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), fn, undefined, debugLocation);
}
/**
* 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 `changeTracker.createChangeSummary` to create a "change summary" that can collect the changes.
* Use `changeTracker.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
*/
function autorunHandleChanges(options, fn, debugLocation = DebugLocation.ofCaller()) {
return new AutorunObserver(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), fn, options.changeTracker, debugLocation);
}
/**
* @see autorunHandleChanges (but with a disposable store that is cleared before the next run or on dispose)
*/
function autorunWithStoreHandleChanges(options, fn) {
const store = new DisposableStore();
const disposable = autorunHandleChanges({
owner: options.owner,
debugName: options.debugName,
debugReferenceFn: options.debugReferenceFn ?? fn,
changeTracker: options.changeTracker,
}, (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)
*
* @deprecated Use `autorun(reader => { reader.store.add(...) })` instead!
*/
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();
});
}
function autorunDelta(observable, handler) {
let _lastValue;
return autorunOpts({ debugReferenceFn: handler }, (reader) => {
const newValue = observable.read(reader);
const lastValue = _lastValue;
_lastValue = newValue;
handler({ lastValue, newValue });
});
}
export { autorun, autorunDelta, autorunHandleChanges, autorunOpts, autorunWithStore, autorunWithStoreHandleChanges };
@@ -0,0 +1,215 @@
import { assertFn } from '../../assert.js';
import '../../arrays.js';
import { onBugIndicatingError, BugIndicatingError } from '../../errors.js';
import '../../event.js';
import { DisposableStore } from '../../lifecycle.js';
import { getLogger } from '../logging/logging.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function autorunStateToString(state) {
switch (state) {
case 1 /* AutorunState.dependenciesMightHaveChanged */: return 'dependenciesMightHaveChanged';
case 2 /* AutorunState.stale */: return 'stale';
case 3 /* AutorunState.upToDate */: return 'upToDate';
default: return '<unknown>';
}
}
class AutorunObserver {
get debugName() {
return this._debugNameData.getDebugName(this) ?? '(anonymous)';
}
constructor(_debugNameData, _runFn, _changeTracker, debugLocation) {
this._debugNameData = _debugNameData;
this._runFn = _runFn;
this._changeTracker = _changeTracker;
this._state = 2 /* AutorunState.stale */;
this._updateCount = 0;
this._disposed = false;
this._dependencies = new Set();
this._dependenciesToBeRemoved = new Set();
this._isRunning = false;
this._store = undefined;
this._delayedStore = undefined;
this._changeSummary = this._changeTracker?.createChangeSummary(undefined);
getLogger()?.handleAutorunCreated(this, debugLocation);
this._run();
}
dispose() {
if (this._disposed) {
return;
}
this._disposed = true;
for (const o of this._dependencies) {
o.removeObserver(this); // Warning: external call!
}
this._dependencies.clear();
if (this._store !== undefined) {
this._store.dispose();
}
if (this._delayedStore !== undefined) {
this._delayedStore.dispose();
}
getLogger()?.handleAutorunDisposed(this);
}
_run() {
const emptySet = this._dependenciesToBeRemoved;
this._dependenciesToBeRemoved = this._dependencies;
this._dependencies = emptySet;
this._state = 3 /* AutorunState.upToDate */;
try {
if (!this._disposed) {
getLogger()?.handleAutorunStarted(this);
const changeSummary = this._changeSummary;
const delayedStore = this._delayedStore;
if (delayedStore !== undefined) {
this._delayedStore = undefined;
}
try {
this._isRunning = true;
if (this._changeTracker) {
this._changeTracker.beforeUpdate?.(this, changeSummary);
this._changeSummary = this._changeTracker.createChangeSummary(changeSummary); // Warning: external call!
}
if (this._store !== undefined) {
this._store.dispose();
this._store = undefined;
}
this._runFn(this, changeSummary); // Warning: external call!
}
catch (e) {
onBugIndicatingError(e);
}
finally {
this._isRunning = false;
if (delayedStore !== undefined) {
delayedStore.dispose();
}
}
}
}
finally {
if (!this._disposed) {
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); // Warning: external call!
}
this._dependenciesToBeRemoved.clear();
}
}
toString() {
return `Autorun<${this.debugName}>`;
}
// IObserver implementation
beginUpdate(_observable) {
if (this._state === 3 /* AutorunState.upToDate */) {
this._state = 1 /* AutorunState.dependenciesMightHaveChanged */;
}
this._updateCount++;
}
endUpdate(_observable) {
try {
if (this._updateCount === 1) {
do {
if (this._state === 1 /* AutorunState.dependenciesMightHaveChanged */) {
this._state = 3 /* AutorunState.upToDate */;
for (const d of this._dependencies) {
d.reportChanges(); // Warning: external call!
if (this._state === 2 /* AutorunState.stale */) {
// The other dependencies will refresh on demand
break;
}
}
}
if (this._state !== 3 /* AutorunState.upToDate */) {
this._run(); // Warning: indirect external call!
}
} while (this._state !== 3 /* AutorunState.upToDate */);
}
}
finally {
this._updateCount--;
}
assertFn(() => this._updateCount >= 0);
}
handlePossibleChange(observable) {
if (this._state === 3 /* AutorunState.upToDate */ && this._isDependency(observable)) {
this._state = 1 /* AutorunState.dependenciesMightHaveChanged */;
}
}
handleChange(observable, change) {
if (this._isDependency(observable)) {
getLogger()?.handleAutorunDependencyChanged(this, observable, change);
try {
// Warning: external call!
const shouldReact = this._changeTracker ? this._changeTracker.handleChange({
changedObservable: observable,
change,
// eslint-disable-next-line local/code-no-any-casts
didChange: (o) => o === observable,
}, this._changeSummary) : true;
if (shouldReact) {
this._state = 2 /* AutorunState.stale */;
}
}
catch (e) {
onBugIndicatingError(e);
}
}
}
_isDependency(observable) {
return this._dependencies.has(observable) && !this._dependenciesToBeRemoved.has(observable);
}
// IReader implementation
_ensureNoRunning() {
if (!this._isRunning) {
throw new BugIndicatingError('The reader object cannot be used outside its compute function!');
}
}
readObservable(observable) {
this._ensureNoRunning();
// In case the run action disposes the autorun
if (this._disposed) {
return observable.get(); // warning: external call!
}
observable.addObserver(this); // warning: external call!
const value = observable.get(); // warning: external call!
this._dependencies.add(observable);
this._dependenciesToBeRemoved.delete(observable);
return value;
}
get store() {
this._ensureNoRunning();
if (this._disposed) {
throw new BugIndicatingError('Cannot access store after dispose');
}
if (this._store === undefined) {
this._store = new DisposableStore();
}
return this._store;
}
debugGetState() {
return {
isRunning: this._isRunning,
updateCount: this._updateCount,
dependencies: this._dependencies,
state: this._state,
stateStr: autorunStateToString(this._state),
};
}
debugRerun() {
if (!this._isRunning) {
this._run();
}
else {
this._state = 2 /* AutorunState.stale */;
}
}
}
export { AutorunObserver };
@@ -0,0 +1,109 @@
import { handleBugIndicatingErrorRecovery } from './base.js';
import { getFunctionName } from './debugName.js';
import { getLogger } from './logging/logging.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* 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.
*/
function transaction(fn, getDebugName) {
const tx = new TransactionImpl(fn, getDebugName);
try {
fn(tx);
}
finally {
tx.finish();
}
}
let _globalTransaction = undefined;
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;
}
}
}
/** @deprecated */
async function asyncTransaction(fn, getDebugName) {
const tx = new TransactionImpl(fn, getDebugName);
try {
await fn(tx);
}
finally {
tx.finish();
}
}
/**
* Allows to chain transactions.
*/
function subtransaction(tx, fn, getDebugName) {
if (!tx) {
transaction(fn, getDebugName);
}
else {
fn(tx);
}
}
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) {
if (!this._updatingObservers) {
// This happens when a transaction is used in a callback or async function.
// If an async transaction is used, make sure the promise awaits all users of the transaction (e.g. no race).
handleBugIndicatingErrorRecovery('Transaction already finished!');
// Error recovery
transaction(tx => {
tx.updateObserver(observer, observable);
});
return;
}
// 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;
if (!updatingObservers) {
handleBugIndicatingErrorRecovery('transaction.finish() has already been called!');
return;
}
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(this);
}
debugGetUpdatingObservers() {
return this._updatingObservers;
}
}
export { TransactionImpl, asyncTransaction, globalTransaction, subtransaction, transaction };
@@ -0,0 +1,56 @@
import { transaction } from '../transaction.js';
import { observableValue } from '../observables/observableValue.js';
/**
* A promise whose state is observable.
*/
class ObservablePromise {
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;
});
}
}
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;
}
/**
* Returns the value if the promise resolved, otherwise throws the error.
*/
getDataOrThrow() {
if (this.error) {
throw this.error;
}
return this.data;
}
}
export { ObservablePromise, PromiseResult };
@@ -0,0 +1,61 @@
import { cancelOnDispose } from '../../cancellation.js';
import '../../arrays.js';
import '../../event.js';
import { DisposableStore } from '../../lifecycle.js';
import { autorunWithStoreHandleChanges } from '../reactions/autorun.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function runOnChange(observable, cb) {
let _previousValue;
let _firstRun = true;
return autorunWithStoreHandleChanges({
changeTracker: {
createChangeSummary: () => ({ deltas: [], didChange: false }),
handleChange: (context, changeSummary) => {
if (context.didChange(observable)) {
const e = context.change;
if (e !== undefined) {
changeSummary.deltas.push(e);
}
changeSummary.didChange = true;
}
return true;
},
}
}, (reader, changeSummary) => {
const value = observable.read(reader);
const previousValue = _previousValue;
if (changeSummary.didChange) {
_previousValue = value;
// didChange can never be true on the first autorun, so we know previousValue is defined
cb(value, previousValue, changeSummary.deltas);
}
if (_firstRun) {
_firstRun = false;
_previousValue = value;
}
});
}
function runOnChangeWithStore(observable, cb) {
const store = new DisposableStore();
const disposable = runOnChange(observable, (value, previousValue, deltas) => {
store.clear();
cb(value, previousValue, deltas, store);
});
return {
dispose() {
disposable.dispose();
store.dispose();
}
};
}
function runOnChangeWithCancellationToken(observable, cb) {
return runOnChangeWithStore(observable, (value, previousValue, deltas, store) => {
cb(value, previousValue, deltas, cancelOnDispose(store));
});
}
export { runOnChange, runOnChangeWithCancellationToken, runOnChangeWithStore };
@@ -0,0 +1,164 @@
import { autorun } from '../reactions/autorun.js';
import '../../arrays.js';
import '../../event.js';
import { toDisposable, DisposableStore } from '../../lifecycle.js';
import { derivedOpts } from '../observables/derived.js';
import { observableFromEvent } from '../observables/observableFromEvent.js';
import { _setRecomputeInitiallyAndOnChange } from '../observables/baseObservable.js';
import '../debugLocation.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Creates an observable that debounces the input observable.
*/
function debouncedObservable(observable, debounceMs) {
let hasValue = false;
let lastValue;
let timeout = undefined;
return observableFromEvent(cb => {
const d = autorun(reader => {
const value = observable.read(reader);
if (!hasValue) {
hasValue = true;
lastValue = value;
}
else {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
lastValue = value;
cb();
}, debounceMs);
}
});
return {
dispose() {
d.dispose();
hasValue = false;
lastValue = undefined;
},
};
}, () => {
if (hasValue) {
return lastValue;
}
else {
return observable.get();
}
});
}
/**
* This converts the given observable into an autorun.
*/
function recomputeInitiallyAndOnChange(observable, handleValue) {
const o = new KeepAliveObserver(true, handleValue);
observable.addObserver(o);
try {
o.beginUpdate(observable);
}
finally {
o.endUpdate(observable);
}
return toDisposable(() => {
observable.removeObserver(o);
});
}
_setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange);
class KeepAliveObserver {
constructor(_forceRecompute, _handleValue) {
this._forceRecompute = _forceRecompute;
this._handleValue = _handleValue;
this._counter = 0;
}
beginUpdate(observable) {
this._counter++;
}
endUpdate(observable) {
if (this._counter === 1 && this._forceRecompute) {
if (this._handleValue) {
this._handleValue(observable.get());
}
else {
observable.reportChanges();
}
}
this._counter--;
}
handlePossibleChange(observable) {
// NO OP
}
handleChange(observable, change) {
// NO OP
}
}
function derivedObservableWithCache(owner, computeFn) {
let lastValue = undefined;
const observable = derivedOpts({ owner, debugReferenceFn: computeFn }, reader => {
lastValue = computeFn(reader, lastValue);
return lastValue;
});
return observable;
}
/**
* When the items array changes, referential equal items are not mapped again.
*/
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 { KeepAliveObserver, debouncedObservable, derivedObservableWithCache, mapObservableArrayCached, recomputeInitiallyAndOnChange };
@@ -0,0 +1,63 @@
import { CancellationError } from '../../errors.js';
import '../../cancellation.js';
import { autorun } from '../reactions/autorun.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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();
}
});
}
export { waitForState };
File diff suppressed because it is too large Load Diff
+154
View File
@@ -0,0 +1,154 @@
import '../../nls.js';
import { getNLSLanguage } from '../../nls.messages.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const LANGUAGE_DEFAULT = 'en';
let _isWindows = false;
let _isMacintosh = false;
let _isLinux = false;
let _isNative = false;
let _isWeb = false;
let _isIOS = false;
let _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');
_isLinux && !!nodeProcess.env['SNAP'] && !!nodeProcess.env['SNAP_REVISION'];
!!nodeProcess.env['CI'] || !!nodeProcess.env['BUILD_ARTIFACTSTAGINGDIRECTORY'] || !!nodeProcess.env['GITHUB_WORKSPACE'];
_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;
_language = 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 */;
}
const isWindows = _isWindows;
const isMacintosh = _isMacintosh;
const isLinux = _isLinux;
const isNative = _isNative;
const isWeb = _isWeb;
const isWebWorker = (_isWeb && typeof $globalThis.importScripts === 'function');
const webWorkerOrigin = isWebWorker ? $globalThis.origin : undefined;
const isIOS = _isIOS;
const isMobile = _isMobile;
const platform = _platform;
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)
*/
const language = _language;
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.
*/
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);
})();
const OS = (_isMacintosh || _isIOS ? 2 /* OperatingSystem.Macintosh */ : (_isWindows ? 1 /* OperatingSystem.Windows */ : 3 /* OperatingSystem.Linux */));
let _isLittleEndian = true;
let _isLittleEndianComputed = false;
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;
}
const isChrome = !!(userAgent && userAgent.indexOf('Chrome') >= 0);
const isFirefox = !!(userAgent && userAgent.indexOf('Firefox') >= 0);
const isSafari = !!(!isChrome && (userAgent && userAgent.indexOf('Safari') >= 0));
const isEdge = !!(userAgent && userAgent.indexOf('Edg/') >= 0);
const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0);
export { LANGUAGE_DEFAULT, OS, isAndroid, isChrome, isEdge, isFirefox, isIOS, isLinux, isLittleEndian, isMacintosh, isMobile, isNative, isSafari, isWeb, isWebWorker, isWindows, language, platform, setTimeout0, setTimeout0IsFaster, userAgent, webWorkerOrigin };
+61
View File
@@ -0,0 +1,61 @@
import { isWindows, isMacintosh } from './platform.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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' && typeof process?.versions?.node === 'string') {
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
*/
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 `{}`.
*/
const env = safeProcess.env;
/**
* Provides safe access to the `platform` property in node.js, sandboxed or web
* environments.
*/
const platform = safeProcess.platform;
export { cwd, env, platform };
+46
View File
@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 = {}));
export { Range };
+231
View File
@@ -0,0 +1,231 @@
import { isEqualOrParent, toSlashes, toPosixPath, getRoot } from './extpath.js';
import { Schemas } from './network.js';
import { posix, dirname as dirname$1, normalize, relative, resolve, sep } from './path.js';
import { isWindows } from './platform.js';
import { compare, equalsIgnoreCase } from './strings.js';
import { URI, uriToFsPath } from './uri.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function originalFSPath(uri) {
return uriToFsPath(uri, true);
}
class ExtUri {
constructor(_ignorePathCasing) {
this._ignorePathCasing = _ignorePathCasing;
}
compare(uri1, uri2, ignoreFragment = false) {
if (uri1 === uri2) {
return 0;
}
return compare(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 isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), this._ignorePathCasing(base)) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
}
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
return 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 posix.basename(resource.path);
}
extname(resource) {
return posix.extname(resource.path);
}
dirname(resource) {
if (resource.path.length === 0) {
return resource;
}
let dirname;
if (resource.scheme === Schemas.file) {
dirname = URI.file(dirname$1(originalFSPath(resource))).path;
}
else {
dirname = 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(normalize(originalFSPath(resource))).path;
}
else {
normalizedPath = 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 = relative(originalFSPath(from), originalFSPath(to));
return isWindows ? 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 posix.relative(fromPath, toPath);
}
resolvePath(base, path) {
if (base.scheme === Schemas.file) {
const newURI = URI.file(resolve(originalFSPath(base), path));
return base.with({
authority: newURI.authority,
path: newURI.path
});
}
path = toPosixPath(path); // we allow path to be a windows path
return base.with({
path: 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$1 = sep) {
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
return fsp.length > getRoot(fsp).length && fsp[fsp.length - 1] === sep$1;
}
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$1 = sep) {
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
if (hasTrailingPathSeparator(resource, sep$1)) {
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
}
return resource;
}
addTrailingPathSeparator(resource, sep$1 = sep) {
let isRootSep = false;
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
isRootSep = ((fsp !== undefined) && (fsp.length === getRoot(fsp).length) && (fsp[fsp.length - 1] === sep$1));
}
else {
sep$1 = '/';
const p = resource.path;
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === 47 /* CharCode.Slash */;
}
if (!isRootSep && !hasTrailingPathSeparator(resource, sep$1)) {
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))
* ```
*/
const extUri = new ExtUri(() => false);
const isEqual = extUri.isEqual.bind(extUri);
extUri.isEqualOrParent.bind(extUri);
extUri.getComparisonKey.bind(extUri);
const basenameOrAuthority = extUri.basenameOrAuthority.bind(extUri);
const basename = extUri.basename.bind(extUri);
const extname = extUri.extname.bind(extUri);
const dirname = extUri.dirname.bind(extUri);
const joinPath = extUri.joinPath.bind(extUri);
const normalizePath = extUri.normalizePath.bind(extUri);
const relativePath = extUri.relativePath.bind(extUri);
const resolvePath = extUri.resolvePath.bind(extUri);
extUri.isAbsolutePath.bind(extUri);
const isEqualAuthority = extUri.isEqualAuthority.bind(extUri);
const hasTrailingPathSeparator = extUri.hasTrailingPathSeparator.bind(extUri);
extUri.removeTrailingPathSeparator.bind(extUri);
extUri.addTrailingPathSeparator.bind(extUri);
/**
* Data URI related helpers.
*/
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 = {}));
export { DataUri, ExtUri, basename, basenameOrAuthority, dirname, extUri, extname, hasTrailingPathSeparator, isEqual, isEqualAuthority, joinPath, normalizePath, originalFSPath, relativePath, resolvePath };
+323
View File
@@ -0,0 +1,323 @@
import { Emitter } from './event.js';
import { Disposable } from './lifecycle.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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,
};
}
}
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));
}
}
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));
};
}
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);
}
export { ScrollState, Scrollable, SmoothScrollingOperation, SmoothScrollingUpdate };
+52
View File
@@ -0,0 +1,52 @@
import { containsUppercaseCharacter } from './strings.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 (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);
}
export { buildReplaceStringWithCasePreserved };
+52
View File
@@ -0,0 +1,52 @@
import { equalsIgnoreCase } from './strings.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 (equalsIgnoreCase(_error, value)) {
return Severity.Error;
}
if (equalsIgnoreCase(_warning, value) || equalsIgnoreCase(_warn, value)) {
return Severity.Warning;
}
if (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 = {}));
var Severity$1 = Severity;
export { Severity$1 as default };
+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.
*--------------------------------------------------------------------------------------------*/
const performanceNow = globalThis.performance.now.bind(globalThis.performance);
class StopWatch {
static create(highResolution) {
return new StopWatch(highResolution);
}
constructor(highResolution) {
this._now = highResolution === false ? Date.now : performanceNow;
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;
}
}
export { StopWatch };
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* 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
* */
const MicrotaskDelay = Symbol('MicrotaskDelay');
export { MicrotaskDelay };
@@ -0,0 +1,674 @@
import { assert } from './assert.js';
import { compareIgnoreCase, compare, compareSubstring, compareSubstringIgnoreCase } from './strings.js';
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];
}
}
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);
}
}
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);
}
}
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 Undef {
static { this.Val = Symbol('undefined_placeholder'); }
static wrap(value) {
return value === undefined ? Undef.Val : value;
}
static unwrap(value) {
return value === Undef.Val ? undefined : value;
}
}
class TernarySearchTreeNode {
constructor() {
this.height = 1;
this.value = undefined;
this.key = undefined;
this.left = undefined;
this.mid = undefined;
this.right = undefined;
}
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;
}
}
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 = Undef.unwrap(node.value);
node.value = Undef.wrap(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 Undef.unwrap(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 stack2 = [[1 /* Dir.Right */, node]];
const min = this._min(node.right, stack2);
if (min.key) {
node.key = min.key;
node.value = min.value;
node.segment = min.segment;
// remove NODE (inorder successor can only have right child)
const newChild = min.right;
if (stack2.length > 1) {
const [dir, parent] = stack2[stack2.length - 1];
switch (dir) {
case -1 /* Dir.Left */:
parent.left = newChild;
break;
case 0 /* Dir.Mid */: assert(false);
case 1 /* Dir.Right */: assert(false);
}
}
else {
node.right = newChild;
}
// balance right branch and UPDATE parent pointer for stack
const newChild2 = this._balanceByStack(stack2);
if (stack.length > 0) {
const [dir, parent] = stack[stack.length - 1];
switch (dir) {
case -1 /* Dir.Left */:
parent.left = newChild2;
break;
case 0 /* Dir.Mid */:
parent.mid = newChild2;
break;
case 1 /* Dir.Right */:
parent.right = newChild2;
break;
}
}
else {
this._root = newChild2;
}
}
}
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
this._root = this._balanceByStack(stack) ?? this._root;
}
_min(node, stack) {
while (node.left) {
stack.push([-1 /* Dir.Left */, node]);
node = node.left;
}
return node;
}
_balanceByStack(stack) {
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 {
return stack[0][1];
}
}
return undefined;
}
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 = Undef.unwrap(node.value) || candidate;
node = node.mid;
}
else {
break;
}
}
return node && Undef.unwrap(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 Undef.unwrap(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 !== undefined) {
bucket.push([node.key, Undef.unwrap(node.value)]);
}
if (node.mid) {
this._dfsEntries(node.mid, bucket);
}
if (node.right) {
this._dfsEntries(node.right, bucket);
}
}
}
export { ConfigKeysIterator, PathIterator, StringIterator, TernarySearchTree, UriIterator };
+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.
*--------------------------------------------------------------------------------------------*/
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.
*/
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
*/
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;
}
export { TfIdfCalculator, normalizeTfIdfScores };
+100
View File
@@ -0,0 +1,100 @@
import { Codicon } from './codicons.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var ThemeColor;
(function (ThemeColor) {
function isThemeColor(obj) {
return !!obj && typeof obj === 'object' && typeof obj.id === 'string';
}
ThemeColor.isThemeColor = isThemeColor;
})(ThemeColor || (ThemeColor = {}));
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;
/**
* Returns whether specified icon is defined and has 'file' ID.
*/
function isFile(icon) {
return icon?.id === Codicon.file.id;
}
ThemeIcon.isFile = isFile;
/**
* Returns whether specified icon is defined and has 'folder' ID.
*/
function isFolder(icon) {
return icon?.id === Codicon.folder.id;
}
ThemeIcon.isFolder = isFolder;
})(ThemeIcon || (ThemeIcon = {}));
export { ThemeColor, ThemeIcon };
+140
View File
@@ -0,0 +1,140 @@
import { assert } from './assert.js';
/*---------------------------------------------------------------------------------------------
* 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.
*/
function isString(str) {
return (typeof str === 'string');
}
/**
* @returns whether the provided parameter is a JavaScript Array and each element in the array satisfies the provided type guard.
*/
function isArrayOf(value, check) {
return Array.isArray(value) && value.every(check);
}
/**
* @returns whether the provided parameter is of type `object` but **not**
* `null`, an `array`, a `regexp`, nor a `date`.
*/
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
*/
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.
*/
function isNumber(obj) {
return (typeof obj === 'number' && !isNaN(obj));
}
/**
* @returns whether the provided parameter is an Iterable, casting to the given generic
*/
function isIterable(obj) {
// eslint-disable-next-line local/code-no-any-casts
return !!obj && typeof obj[Symbol.iterator] === 'function';
}
/**
* @returns whether the provided parameter is a JavaScript Boolean or not.
*/
function isBoolean(obj) {
return (obj === true || obj === false);
}
/**
* @returns whether the provided parameter is undefined.
*/
function isUndefined(obj) {
return (typeof obj === 'undefined');
}
/**
* @returns whether the provided parameter is defined.
*/
function isDefined(arg) {
return !isUndefinedOrNull(arg);
}
/**
* @returns whether the provided parameter is undefined or null.
*/
function isUndefinedOrNull(obj) {
return (isUndefined(obj) || obj === null);
}
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.
*
* @see {@link assertDefined} for a similar utility that leverages TS assertion functions to narrow down the type of `arg` to be non-nullable.
*/
function assertReturnsDefined(arg) {
assert(arg !== null && arg !== undefined, 'Argument is `undefined` or `null`.');
return arg;
}
/**
* @returns whether the provided parameter is a JavaScript Function or not.
*/
function isFunction(obj) {
return (typeof obj === 'function');
}
function validateConstraints(args, constraints) {
const len = Math.min(args.length, constraints.length);
for (let i = 0; i < len; i++) {
validateConstraint(args[i], constraints[i]);
}
}
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
}
// eslint-disable-next-line local/code-no-any-casts
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`);
}
}
/**
* Helper type assertion that safely upcasts a type to a supertype.
*
* This can be used to make sure the argument correctly conforms to the subtype while still being able to pass it
* to contexts that expects the supertype.
*/
function upcast(x) {
return x;
}
export { assertReturnsDefined, assertType, isArrayOf, isBoolean, isDefined, isFunction, isIterable, isNumber, isObject, isString, isTypedArray, isUndefined, isUndefinedOrNull, upcast, validateConstraint, validateConstraints };

Some files were not shown because too many files have changed in this diff Show More