@pkerschbaum/code-oss-file-service
Version:
VS Code ([microsoft/vscode](https://github.com/microsoft/vscode)) includes a rich "`FileService`" and "`DiskFileSystemProvider`" abstraction built on top of Node.js core modules (`fs`, `path`) and Electron's `shell` module. This package allows to use that
1,331 lines (1,152 loc) • 31.5 kB
text/typescript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { shuffle } from 'vs/base/common/arrays';
import { CharCode } from 'vs/base/common/charCode';
import { compare, compareIgnoreCase, compareSubstring, compareSubstringIgnoreCase } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {
let result = map.get(key);
if (result === undefined) {
result = value;
map.set(key, result);
}
return result;
}
export function mapToString<K, V>(map: Map<K, V>): string {
const entries: string[] = [];
map.forEach((value, key) => {
entries.push(`${key} => ${value}`);
});
return `Map(${map.size}) {${entries.join(', ')}}`;
}
export function setToString<K>(set: Set<K>): string {
const entries: K[] = [];
set.forEach(value => {
entries.push(value);
});
return `Set(${set.size}) {${entries.join(', ')}}`;
}
export interface IKeyIterator<K> {
reset(key: K): this;
next(): this;
hasNext(): boolean;
cmp(a: string): number;
value(): string;
}
export class StringIterator implements IKeyIterator<string> {
private _value: string = '';
private _pos: number = 0;
reset(key: string): this {
this._value = key;
this._pos = 0;
return this;
}
next(): this {
this._pos += 1;
return this;
}
hasNext(): boolean {
return this._pos < this._value.length - 1;
}
cmp(a: string): number {
const aCode = a.charCodeAt(0);
const thisCode = this._value.charCodeAt(this._pos);
return aCode - thisCode;
}
value(): string {
return this._value[this._pos];
}
}
export class ConfigKeysIterator implements IKeyIterator<string> {
private _value!: string;
private _from!: number;
private _to!: number;
constructor(
private readonly _caseSensitive: boolean = true
) { }
reset(key: string): this {
this._value = key;
this._from = 0;
this._to = 0;
return this.next();
}
hasNext(): boolean {
return this._to < this._value.length;
}
next(): this {
// this._data = key.split(/[\\/]/).filter(s => !!s);
this._from = this._to;
let justSeps = true;
for (; this._to < this._value.length; this._to++) {
const ch = this._value.charCodeAt(this._to);
if (ch === CharCode.Period) {
if (justSeps) {
this._from++;
} else {
break;
}
} else {
justSeps = false;
}
}
return this;
}
cmp(a: string): number {
return this._caseSensitive
? compareSubstring(a, this._value, 0, a.length, this._from, this._to)
: compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to);
}
value(): string {
return this._value.substring(this._from, this._to);
}
}
export class PathIterator implements IKeyIterator<string> {
private _value!: string;
private _valueLen!: number;
private _from!: number;
private _to!: number;
constructor(
private readonly _splitOnBackslash: boolean = true,
private readonly _caseSensitive: boolean = true
) { }
reset(key: string): this {
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 === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash)) {
break;
}
}
return this.next();
}
hasNext(): boolean {
return this._to < this._valueLen;
}
next(): this {
// 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 === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash) {
if (justSeps) {
this._from++;
} else {
break;
}
} else {
justSeps = false;
}
}
return this;
}
cmp(a: string): number {
return this._caseSensitive
? compareSubstring(a, this._value, 0, a.length, this._from, this._to)
: compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to);
}
value(): string {
return this._value.substring(this._from, this._to);
}
}
const enum UriIteratorState {
Scheme = 1, Authority = 2, Path = 3, Query = 4, Fragment = 5
}
export class UriIterator implements IKeyIterator<URI> {
private _pathIterator!: PathIterator;
private _value!: URI;
private _states: UriIteratorState[] = [];
private _stateIdx: number = 0;
constructor(private readonly _ignorePathCasing: (uri: URI) => boolean) { }
reset(key: URI): this {
this._value = key;
this._states = [];
if (this._value.scheme) {
this._states.push(UriIteratorState.Scheme);
}
if (this._value.authority) {
this._states.push(UriIteratorState.Authority);
}
if (this._value.path) {
this._pathIterator = new PathIterator(false, !this._ignorePathCasing(key));
this._pathIterator.reset(key.path);
if (this._pathIterator.value()) {
this._states.push(UriIteratorState.Path);
}
}
if (this._value.query) {
this._states.push(UriIteratorState.Query);
}
if (this._value.fragment) {
this._states.push(UriIteratorState.Fragment);
}
this._stateIdx = 0;
return this;
}
next(): this {
if (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext()) {
this._pathIterator.next();
} else {
this._stateIdx += 1;
}
return this;
}
hasNext(): boolean {
return (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext())
|| this._stateIdx < this._states.length - 1;
}
cmp(a: string): number {
if (this._states[this._stateIdx] === UriIteratorState.Scheme) {
return compareIgnoreCase(a, this._value.scheme);
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
return compareIgnoreCase(a, this._value.authority);
} else if (this._states[this._stateIdx] === UriIteratorState.Path) {
return this._pathIterator.cmp(a);
} else if (this._states[this._stateIdx] === UriIteratorState.Query) {
return compare(a, this._value.query);
} else if (this._states[this._stateIdx] === UriIteratorState.Fragment) {
return compare(a, this._value.fragment);
}
throw new Error();
}
value(): string {
if (this._states[this._stateIdx] === UriIteratorState.Scheme) {
return this._value.scheme;
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
return this._value.authority;
} else if (this._states[this._stateIdx] === UriIteratorState.Path) {
return this._pathIterator.value();
} else if (this._states[this._stateIdx] === UriIteratorState.Query) {
return this._value.query;
} else if (this._states[this._stateIdx] === UriIteratorState.Fragment) {
return this._value.fragment;
}
throw new Error();
}
}
class TernarySearchTreeNode<K, V> {
height: number = 1;
segment!: string;
value: V | undefined;
key: K | undefined;
left: TernarySearchTreeNode<K, V> | undefined;
mid: TernarySearchTreeNode<K, V> | undefined;
right: TernarySearchTreeNode<K, V> | undefined;
isEmpty(): boolean {
return !this.left && !this.mid && !this.right && !this.value;
}
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;
}
}
const enum Dir {
Left = -1,
Mid = 0,
Right = 1,
}
export class TernarySearchTree<K, V> {
static forUris<E>(ignorePathCasing: (key: URI) => boolean = () => false): TernarySearchTree<URI, E> {
return new TernarySearchTree<URI, E>(new UriIterator(ignorePathCasing));
}
static forPaths<E>(): TernarySearchTree<string, E> {
return new TernarySearchTree<string, E>(new PathIterator());
}
static forStrings<E>(): TernarySearchTree<string, E> {
return new TernarySearchTree<string, E>(new StringIterator());
}
static forConfigKeys<E>(): TernarySearchTree<string, E> {
return new TernarySearchTree<string, E>(new ConfigKeysIterator());
}
private _iter: IKeyIterator<K>;
private _root: TernarySearchTreeNode<K, V> | undefined;
constructor(segments: IKeyIterator<K>) {
this._iter = segments;
}
clear(): void {
this._root = undefined;
}
/**
* Fill the tree with the same value of the given keys
*/
fill(element: V, keys: readonly K[]): void;
/**
* Fill the tree with given [key,value]-tuples
*/
fill(values: readonly [K, V][]): void;
fill(values: readonly [K, V][] | V, keys?: readonly K[]): void {
if (keys) {
const arr = keys.slice(0);
shuffle(arr);
for (let k of arr) {
this.set(k, (<V>values));
}
} else {
const arr = (<[K, V][]>values).slice(0);
shuffle(arr);
for (let entry of arr) {
this.set(entry[0], entry[1]);
}
}
}
set(key: K, element: V): V | undefined {
const iter = this._iter.reset(key);
let node: TernarySearchTreeNode<K, V>;
if (!this._root) {
this._root = new TernarySearchTreeNode<K, V>();
this._root.segment = iter.value();
}
const stack: [Dir, TernarySearchTreeNode<K, V>][] = [];
// 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<K, V>();
node.left.segment = iter.value();
}
stack.push([Dir.Left, node]);
node = node.left;
} else if (val < 0) {
// right
if (!node.right) {
node.right = new TernarySearchTreeNode<K, V>();
node.right.segment = iter.value();
}
stack.push([Dir.Right, node]);
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
if (!node.mid) {
node.mid = new TernarySearchTreeNode<K, V>();
node.mid.segment = iter.value();
}
stack.push([Dir.Mid, node]);
node = node.mid;
} else {
break;
}
}
// set value
const oldElement = node.value;
node.value = element;
node.key = key;
// balance
for (let i = stack.length - 1; i >= 0; i--) {
const node = stack[i][1];
node.updateHeight();
const bf = node.balanceFactor();
if (bf < -1 || bf > 1) {
// needs rotate
const d1 = stack[i][0];
const d2 = stack[i + 1][0];
if (d1 === Dir.Right && d2 === Dir.Right) {
//right, right -> rotate left
stack[i][1] = node.rotateLeft();
} else if (d1 === Dir.Left && d2 === Dir.Left) {
// left, left -> rotate right
stack[i][1] = node.rotateRight();
} else if (d1 === Dir.Right && d2 === 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 === Dir.Left && d2 === 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 Dir.Left:
stack[i - 1][1].left = stack[i][1];
break;
case Dir.Right:
stack[i - 1][1].right = stack[i][1];
break;
case Dir.Mid:
stack[i - 1][1].mid = stack[i][1];
break;
}
} else {
this._root = stack[0][1];
}
}
}
return oldElement;
}
get(key: K): V | undefined {
return this._getNode(key)?.value;
}
private _getNode(key: K) {
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: K): boolean {
const node = this._getNode(key);
return !(node?.value === undefined && node?.mid === undefined);
}
delete(key: K): void {
return this._delete(key, false);
}
deleteSuperstr(key: K): void {
return this._delete(key, true);
}
private _delete(key: K, superStr: boolean): void {
const iter = this._iter.reset(key);
const stack: [Dir, TernarySearchTreeNode<K, V>][] = [];
let node = this._root;
// find node
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
stack.push([Dir.Left, node]);
node = node.left;
} else if (val < 0) {
// right
stack.push([Dir.Right, node]);
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
stack.push([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
const min = this._min(node.right);
const { key, value, segment } = min;
this._delete(min.key!, false);
node.key = key;
node.value = value;
node.segment = segment;
} else {
// empty or half empty
const newChild = node.left ?? node.right;
if (stack.length > 0) {
const [dir, parent] = stack[stack.length - 1];
switch (dir) {
case Dir.Left: parent.left = newChild; break;
case Dir.Mid: parent.mid = newChild; break;
case Dir.Right: parent.right = newChild; break;
}
} else {
this._root = newChild;
}
}
}
// AVL balance
for (let i = stack.length - 1; i >= 0; i--) {
const node = stack[i][1];
node.updateHeight();
const bf = node.balanceFactor();
if (bf > 1) {
// right heavy
if (node.right!.balanceFactor() >= 0) {
// right, right -> rotate left
stack[i][1] = node.rotateLeft();
} else {
// right, left -> double rotate
node.right = node.right!.rotateRight();
stack[i][1] = node.rotateLeft();
}
} else if (bf < -1) {
// left heavy
if (node.left!.balanceFactor() <= 0) {
// left, left -> rotate right
stack[i][1] = node.rotateRight();
} else {
// left, right -> double rotate
node.left = node.left!.rotateLeft();
stack[i][1] = node.rotateRight();
}
}
// patch path to parent
if (i > 0) {
switch (stack[i - 1][0]) {
case Dir.Left:
stack[i - 1][1].left = stack[i][1];
break;
case Dir.Right:
stack[i - 1][1].right = stack[i][1];
break;
case Dir.Mid:
stack[i - 1][1].mid = stack[i][1];
break;
}
} else {
this._root = stack[0][1];
}
}
}
private _min(node: TernarySearchTreeNode<K, V>): TernarySearchTreeNode<K, V> {
while (node.left) {
node = node.left;
}
return node;
}
findSubstr(key: K): V | undefined {
const iter = this._iter.reset(key);
let node = this._root;
let candidate: V | undefined = undefined;
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
node = node.left;
} else if (val < 0) {
// right
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
candidate = node.value || candidate;
node = node.mid;
} else {
break;
}
}
return node && node.value || candidate;
}
findSuperstr(key: K): IterableIterator<[K, V]> | undefined {
const iter = this._iter.reset(key);
let node = this._root;
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
node = node.left;
} else if (val < 0) {
// right
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
node = node.mid;
} else {
// collect
if (!node.mid) {
return undefined;
} else {
return this._entries(node.mid);
}
}
}
return undefined;
}
forEach(callback: (value: V, index: K) => any): void {
for (const [key, value] of this) {
callback(value, key);
}
}
*[Symbol.iterator](): IterableIterator<[K, V]> {
yield* this._entries(this._root);
}
private *_entries(node: TernarySearchTreeNode<K, V> | undefined): IterableIterator<[K, V]> {
// DFS
if (!node) {
return;
}
if (node.left) {
yield* this._entries(node.left);
}
if (node.value) {
yield [node.key!, node.value];
}
if (node.mid) {
yield* this._entries(node.mid);
}
if (node.right) {
yield* this._entries(node.right);
}
}
// for debug/testing
_isBalanced(): boolean {
const nodeIsBalanced = (node: TernarySearchTreeNode<any, any> | undefined): boolean => {
if (!node) {
return true;
}
const bf = node.balanceFactor();
if (bf < -1 || bf > 1) {
return false;
}
return nodeIsBalanced(node.left) && nodeIsBalanced(node.right);
};
return nodeIsBalanced(this._root);
}
}
interface ResourceMapKeyFn {
(resource: URI): string;
}
class ResourceMapEntry<T> {
constructor(readonly uri: URI, readonly value: T) { }
}
export class ResourceMap<T> implements Map<URI, T> {
private static readonly defaultToKey = (resource: URI) => resource.toString();
readonly [Symbol.toStringTag] = 'ResourceMap';
private readonly map: Map<string, ResourceMapEntry<T>>;
private readonly toKey: ResourceMapKeyFn;
/**
*
* @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
*/
constructor(toKey?: ResourceMapKeyFn);
/**
*
* @param other Another resource which this maps is created from
* @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
*/
constructor(other?: ResourceMap<T>, toKey?: ResourceMapKeyFn);
constructor(mapOrKeyFn?: ResourceMap<T> | ResourceMapKeyFn, toKey?: ResourceMapKeyFn) {
if (mapOrKeyFn instanceof ResourceMap) {
this.map = new Map(mapOrKeyFn.map);
this.toKey = toKey ?? ResourceMap.defaultToKey;
} else {
this.map = new Map();
this.toKey = mapOrKeyFn ?? ResourceMap.defaultToKey;
}
}
set(resource: URI, value: T): this {
this.map.set(this.toKey(resource), new ResourceMapEntry(resource, value));
return this;
}
get(resource: URI): T | undefined {
return this.map.get(this.toKey(resource))?.value;
}
has(resource: URI): boolean {
return this.map.has(this.toKey(resource));
}
get size(): number {
return this.map.size;
}
clear(): void {
this.map.clear();
}
delete(resource: URI): boolean {
return this.map.delete(this.toKey(resource));
}
forEach(clb: (value: T, key: URI, map: Map<URI, T>) => void, thisArg?: any): void {
if (typeof thisArg !== 'undefined') {
clb = clb.bind(thisArg);
}
for (let [_, entry] of this.map) {
clb(entry.value, entry.uri, <any>this);
}
}
*values(): IterableIterator<T> {
for (let entry of this.map.values()) {
yield entry.value;
}
}
*keys(): IterableIterator<URI> {
for (let entry of this.map.values()) {
yield entry.uri;
}
}
*entries(): IterableIterator<[URI, T]> {
for (let entry of this.map.values()) {
yield [entry.uri, entry.value];
}
}
*[Symbol.iterator](): IterableIterator<[URI, T]> {
for (let [, entry] of this.map) {
yield [entry.uri, entry.value];
}
}
}
interface Item<K, V> {
previous: Item<K, V> | undefined;
next: Item<K, V> | undefined;
key: K;
value: V;
}
export const enum Touch {
None = 0,
AsOld = 1,
AsNew = 2
}
export class LinkedMap<K, V> implements Map<K, V> {
readonly [Symbol.toStringTag] = 'LinkedMap';
private _map: Map<K, Item<K, V>>;
private _head: Item<K, V> | undefined;
private _tail: Item<K, V> | undefined;
private _size: number;
private _state: number;
constructor() {
this._map = new Map<K, Item<K, V>>();
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state = 0;
}
clear(): void {
this._map.clear();
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state++;
}
isEmpty(): boolean {
return !this._head && !this._tail;
}
get size(): number {
return this._size;
}
get first(): V | undefined {
return this._head?.value;
}
get last(): V | undefined {
return this._tail?.value;
}
has(key: K): boolean {
return this._map.has(key);
}
get(key: K, touch: Touch = Touch.None): V | undefined {
const item = this._map.get(key);
if (!item) {
return undefined;
}
if (touch !== Touch.None) {
this.touch(item, touch);
}
return item.value;
}
set(key: K, value: V, touch: Touch = Touch.None): this {
let item = this._map.get(key);
if (item) {
item.value = value;
if (touch !== Touch.None) {
this.touch(item, touch);
}
} else {
item = { key, value, next: undefined, previous: undefined };
switch (touch) {
case Touch.None:
this.addItemLast(item);
break;
case Touch.AsOld:
this.addItemFirst(item);
break;
case Touch.AsNew:
this.addItemLast(item);
break;
default:
this.addItemLast(item);
break;
}
this._map.set(key, item);
this._size++;
}
return this;
}
delete(key: K): boolean {
return !!this.remove(key);
}
remove(key: K): V | undefined {
const item = this._map.get(key);
if (!item) {
return undefined;
}
this._map.delete(key);
this.removeItem(item);
this._size--;
return item.value;
}
shift(): V | undefined {
if (!this._head && !this._tail) {
return undefined;
}
if (!this._head || !this._tail) {
throw new Error('Invalid list');
}
const item = this._head;
this._map.delete(item.key);
this.removeItem(item);
this._size--;
return item.value;
}
forEach(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {
const state = this._state;
let current = this._head;
while (current) {
if (thisArg) {
callbackfn.bind(thisArg)(current.value, current.key, this);
} else {
callbackfn(current.value, current.key, this);
}
if (this._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
current = current.next;
}
}
keys(): IterableIterator<K> {
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<K> = {
[Symbol.iterator]() {
return iterator;
},
next(): IteratorResult<K> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result = { value: current.key, done: false };
current = current.next;
return result;
} else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
values(): IterableIterator<V> {
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<V> = {
[Symbol.iterator]() {
return iterator;
},
next(): IteratorResult<V> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result = { value: current.value, done: false };
current = current.next;
return result;
} else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
entries(): IterableIterator<[K, V]> {
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<[K, V]> = {
[Symbol.iterator]() {
return iterator;
},
next(): IteratorResult<[K, V]> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false };
current = current.next;
return result;
} else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
[Symbol.iterator](): IterableIterator<[K, V]> {
return this.entries();
}
protected trimOld(newSize: number) {
if (newSize >= this.size) {
return;
}
if (newSize === 0) {
this.clear();
return;
}
let current = this._head;
let currentSize = this.size;
while (current && currentSize > newSize) {
this._map.delete(current.key);
current = current.next;
currentSize--;
}
this._head = current;
this._size = currentSize;
if (current) {
current.previous = undefined;
}
this._state++;
}
private addItemFirst(item: Item<K, V>): void {
// First time Insert
if (!this._head && !this._tail) {
this._tail = item;
} else if (!this._head) {
throw new Error('Invalid list');
} else {
item.next = this._head;
this._head.previous = item;
}
this._head = item;
this._state++;
}
private addItemLast(item: Item<K, V>): void {
// First time Insert
if (!this._head && !this._tail) {
this._head = item;
} else if (!this._tail) {
throw new Error('Invalid list');
} else {
item.previous = this._tail;
this._tail.next = item;
}
this._tail = item;
this._state++;
}
private removeItem(item: Item<K, V>): void {
if (item === this._head && item === this._tail) {
this._head = undefined;
this._tail = undefined;
}
else if (item === this._head) {
// This can only 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++;
}
private touch(item: Item<K, V>, touch: Touch): void {
if (!this._head || !this._tail) {
throw new Error('Invalid list');
}
if ((touch !== Touch.AsOld && touch !== Touch.AsNew)) {
return;
}
if (touch === Touch.AsOld) {
if (item === this._head) {
return;
}
const next = item.next;
const previous = item.previous;
// Unlink the item
if (item === this._tail) {
// previous must be defined since item was not head but is tail
// So there are more than on item in the map
previous!.next = undefined;
this._tail = previous;
}
else {
// Both next and previous are not undefined since item was neither head nor tail.
next!.previous = previous;
previous!.next = next;
}
// Insert the node at head
item.previous = undefined;
item.next = this._head;
this._head.previous = item;
this._head = item;
this._state++;
} else if (touch === Touch.AsNew) {
if (item === this._tail) {
return;
}
const next = item.next;
const previous = item.previous;
// Unlink the item.
if (item === this._head) {
// next must be defined since item was not tail but is head
// So there are more than on item in the map
next!.previous = undefined;
this._head = next;
} else {
// Both next and previous are not undefined since item was neither head nor tail.
next!.previous = previous;
previous!.next = next;
}
item.next = undefined;
item.previous = this._tail;
this._tail.next = item;
this._tail = item;
this._state++;
}
}
toJSON(): [K, V][] {
const data: [K, V][] = [];
this.forEach((value, key) => {
data.push([key, value]);
});
return data;
}
fromJSON(data: [K, V][]): void {
this.clear();
for (const [key, value] of data) {
this.set(key, value);
}
}
}
export class LRUCache<K, V> extends LinkedMap<K, V> {
private _limit: number;
private _ratio: number;
constructor(limit: number, ratio: number = 1) {
super();
this._limit = limit;
this._ratio = Math.min(Math.max(0, ratio), 1);
}
get limit(): number {
return this._limit;
}
set limit(limit: number) {
this._limit = limit;
this.checkTrim();
}
get ratio(): number {
return this._ratio;
}
set ratio(ratio: number) {
this._ratio = Math.min(Math.max(0, ratio), 1);
this.checkTrim();
}
override get(key: K, touch: Touch = Touch.AsNew): V | undefined {
return super.get(key, touch);
}
peek(key: K): V | undefined {
return super.get(key, Touch.None);
}
override set(key: K, value: V): this {
super.set(key, value, Touch.AsNew);
this.checkTrim();
return this;
}
private checkTrim() {
if (this.size > this._limit) {
this.trimOld(Math.round(this._limit * this._ratio));
}
}
}
/**
* Wraps the map in type that only implements readonly properties. Useful
* in the extension host to prevent the consumer from making any mutations.
*/
export class ReadonlyMapView<K, V> implements ReadonlyMap<K, V>{
readonly #source: ReadonlyMap<K, V>;
public get size() {
return this.#source.size;
}
constructor(source: ReadonlyMap<K, V>) {
this.#source = source;
}
forEach(callbackfn: (value: V, key: K, map: ReadonlyMap<K, V>) => void, thisArg?: any): void {
this.#source.forEach(callbackfn, thisArg);
}
get(key: K): V | undefined {
return this.#source.get(key);
}
has(key: K): boolean {
return this.#source.has(key);
}
entries(): IterableIterator<[K, V]> {
return this.#source.entries();
}
keys(): IterableIterator<K> {
return this.#source.keys();
}
values(): IterableIterator<V> {
return this.#source.values();
}
[Symbol.iterator](): IterableIterator<[K, V]> {
return this.#source.entries();
}
}