Merge from vscode 79a1f5a5ca0c6c53db617aa1fa5a2396d2caebe2

This commit is contained in:
ADS Merger
2020-05-31 19:47:51 +00:00
parent 84492049e8
commit 28be33cfea
913 changed files with 28242 additions and 15549 deletions

View File

@@ -764,7 +764,7 @@ export class IdleValue<T> {
this._handle.dispose();
}
getValue(): T {
get value(): T {
if (!this._didRun) {
this._handle.dispose();
this._executor();

View File

@@ -342,7 +342,17 @@ export const enum CharCode {
* Unicode Character 'LINE SEPARATOR' (U+2028)
* http://www.fileformat.info/info/unicode/char/2028/index.htm
*/
LINE_SEPARATOR_2028 = 8232,
LINE_SEPARATOR = 0x2028,
/**
* Unicode Character 'PARAGRAPH SEPARATOR' (U+2029)
* http://www.fileformat.info/info/unicode/char/2029/index.htm
*/
PARAGRAPH_SEPARATOR = 0x2029,
/**
* Unicode Character 'NEXT LINE' (U+0085)
* http://www.fileformat.info/info/unicode/char/0085/index.htm
*/
NEXT_LINE = 0x0085,
// http://www.fileformat.info/info/unicode/category/Sk/list.htm
U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX
@@ -422,4 +432,4 @@ export const enum CharCode {
* http://www.fileformat.info/info/unicode/char/feff/index.htm
*/
UTF8_BOM = 65279
}
}

View File

@@ -244,7 +244,7 @@ export namespace Codicon {
export const collapseAll = new Codicon('collapse-all', { character: '\\eac5' });
export const colorMode = new Codicon('color-mode', { character: '\\eac6' });
export const commentDiscussion = new Codicon('comment-discussion', { character: '\\eac7' });
export const compareChanges = new Codicon('compare-changes', { character: '\\eac8' });
export const compareChanges = new Codicon('compare-changes', { character: '\\eafd' });
export const creditCard = new Codicon('credit-card', { character: '\\eac9' });
export const dash = new Codicon('dash', { character: '\\eacc' });
export const dashboard = new Codicon('dashboard', { character: '\\eacd' });
@@ -448,7 +448,6 @@ export namespace Codicon {
export const debugReverseContinue = new Codicon('debug-reverse-continue', { character: '\\eb8e' });
export const debugStepBack = new Codicon('debug-step-back', { character: '\\eb8f' });
export const debugRestartFrame = new Codicon('debug-restart-frame', { character: '\\eb90' });
export const debugAlternate = new Codicon('debug-alternate', { character: '\\eb91' });
export const callIncoming = new Codicon('call-incoming', { character: '\\eb92' });
export const callOutgoing = new Codicon('call-outgoing', { character: '\\eb93' });
export const menu = new Codicon('menu', { character: '\\eb94' });
@@ -465,8 +464,13 @@ export namespace Codicon {
export const syncIgnored = new Codicon('sync-ignored', { character: '\\eb9f' });
export const pinned = new Codicon('pinned', { character: '\\eba0' });
export const githubInverted = new Codicon('github-inverted', { character: '\\eba1' });
export const debugAlt2 = new Codicon('debug-alt-2', { character: '\\f101' });
export const debugAlt = new Codicon('debug-alt', { character: '\\f102' });
export const debugAlt = new Codicon('debug-alt', { character: '\\eb91' });
export const serverProcess = new Codicon('server-process', { character: '\\eba2' });
export const serverEnvironment = new Codicon('server-environment', { character: '\\eba3' });
export const pass = new Codicon('pass', { character: '\\eba4' });
export const stopCircle = new Codicon('stop-circle', { character: '\\eba5' });
export const playCircle = new Codicon('play-circle', { character: '\\eba6' });
export const record = new Codicon('record', { character: '\\eba7' });
}

View File

@@ -6,7 +6,12 @@
import { sep } from 'vs/base/common/path';
import { IdleValue } from 'vs/base/common/async';
const intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => {
// When comparing large numbers of strings, such as in sorting large arrays, is better for
// performance to create an Intl.Collator object and use the function provided by its compare
// property than it is to use String.prototype.localeCompare()
// A collator with numeric sorting enabled, and no sensitivity to case or to accents
const intlFileNameCollatorBaseNumeric: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => {
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
return {
collator: collator,
@@ -14,20 +19,64 @@ const intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumer
};
});
// A collator with numeric sorting enabled.
const intlFileNameCollatorNumeric: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
const collator = new Intl.Collator(undefined, { numeric: true });
return {
collator: collator
};
});
// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
const intlFileNameCollatorNumericCaseInsenstive: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' });
return {
collator: collator
};
});
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
const a = one || '';
const b = other || '';
const result = intlFileNameCollator.getValue().collator.compare(a, b);
const result = intlFileNameCollatorBaseNumeric.value.collator.compare(a, b);
// Using the numeric option in the collator will
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && a !== b) {
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && a !== b) {
return a < b ? -1 : 1;
}
return result;
}
/** Compares filenames by name then extension, sorting numbers numerically instead of alphabetically. */
export function compareFileNamesNumeric(one: string | null, other: string | null): number {
const [oneName, oneExtension] = extractNameAndExtension(one, true);
const [otherName, otherExtension] = extractNameAndExtension(other, true);
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
let result;
// Check for name differences, comparing numbers numerically instead of alphabetically.
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
if (result !== 0) {
return result;
}
// Check for case insensitive extension differences, comparing numbers numerically instead of alphabetically.
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
if (result !== 0) {
return result;
}
// Disambiguate the extension case if needed.
if (oneExtension !== otherExtension) {
return collatorNumeric.compare(oneExtension, otherExtension);
}
return 0;
}
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
@@ -54,19 +103,19 @@ export function compareFileExtensions(one: string | null, other: string | null):
const [oneName, oneExtension] = extractNameAndExtension(one);
const [otherName, otherExtension] = extractNameAndExtension(other);
let result = intlFileNameCollator.getValue().collator.compare(oneExtension, otherExtension);
let result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneExtension, otherExtension);
if (result === 0) {
// Using the numeric option in the collator will
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
if (intlFileNameCollator.getValue().collatorIsNumeric && oneExtension !== otherExtension) {
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && oneExtension !== otherExtension) {
return oneExtension < otherExtension ? -1 : 1;
}
// Extensions are equal, compare filenames
result = intlFileNameCollator.getValue().collator.compare(oneName, otherName);
result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneName, otherName);
if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && oneName !== otherName) {
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && oneName !== otherName) {
return oneName < otherName ? -1 : 1;
}
}
@@ -74,10 +123,63 @@ export function compareFileExtensions(one: string | null, other: string | null):
return result;
}
function extractNameAndExtension(str?: string | null): [string, string] {
/** Compares filenames by extenson, then by name. Sorts numbers numerically, not alphabetically. */
export function compareFileExtensionsNumeric(one: string | null, other: string | null): number {
const [oneName, oneExtension] = extractNameAndExtension(one, true);
const [otherName, otherExtension] = extractNameAndExtension(other, true);
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
let result;
// Check for extension differences, ignoring differences in case and comparing numbers numerically.
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
if (result !== 0) {
return result;
}
// Compare names.
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
if (result !== 0) {
return result;
}
// Disambiguate extension case if needed.
if (oneExtension !== otherExtension) {
return collatorNumeric.compare(oneExtension, otherExtension);
}
return 0;
}
/** Extracts the name and extension from a full filename, with optional special handling for dotfiles */
function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] {
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
return [(match && match[1]) || '', (match && match[3]) || ''];
let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || ''];
// if the dotfilesAsNames option is selected, treat an empty filename with an extension,
// or a filename that starts with a dot, as a dotfile name
if (dotfilesAsNames && (!result[0] && result[1] || result[0] && result[0].charAt(0) === '.')) {
result = [result[0] + '.' + result[1], ''];
}
return result;
}
function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
// Check for differences
let result = collator.compare(one, other);
if (result !== 0) {
return result;
}
// In a numeric comparison, `foo1` and `foo01` will compare as equivalent.
// Disambiguate by sorting the shorter string first.
if (one.length !== other.length) {
return one.length < other.length ? -1 : 1;
}
return 0;
}
function comparePathComponents(one: string, other: string, caseSensitive = false): number {

View File

@@ -328,8 +328,8 @@ export namespace Event {
}
export interface NodeEventEmitter {
on(event: string | symbol, listener: Function): this;
removeListener(event: string | symbol, listener: Function): this;
on(event: string | symbol, listener: Function): unknown;
removeListener(event: string | symbol, listener: Function): unknown;
}
export function fromNodeEventEmitter<T>(emitter: NodeEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {

View File

@@ -7,6 +7,7 @@ import { isWindows } from 'vs/base/common/platform';
import { startsWithIgnoreCase, equalsIgnoreCase, rtrim } from 'vs/base/common/strings';
import { CharCode } from 'vs/base/common/charCode';
import { sep, posix, isAbsolute, join, normalize } from 'vs/base/common/path';
import { isNumber } from 'vs/base/common/types';
export function isPathSeparator(code: number) {
return code === CharCode.Slash || code === CharCode.Backslash;
@@ -300,3 +301,38 @@ export function indexOfPath(path: string, candidate: string, ignoreCase: boolean
return path.indexOf(candidate);
}
export interface IPathWithLineAndColumn {
path: string;
line?: number;
column?: number;
}
export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
let path: string | undefined = undefined;
let line: number | undefined = undefined;
let column: number | undefined = undefined;
segments.forEach(segment => {
const segmentAsNumber = Number(segment);
if (!isNumber(segmentAsNumber)) {
path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
} else if (line === undefined) {
line = segmentAsNumber;
} else if (column === undefined) {
column = segmentAsNumber;
}
});
if (!path) {
throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`');
}
return {
path,
line: line !== undefined ? line : undefined,
column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set
};
}

View File

@@ -833,7 +833,7 @@ export interface IPreparedQuery extends IPreparedQueryPiece {
values: IPreparedQueryPiece[] | undefined;
/**
* Wether the query contains path separator(s) or not.
* Whether the query contains path separator(s) or not.
*/
containsPathSeparator: boolean;
}

View File

@@ -9,6 +9,7 @@ export function insane(
readonly allowedSchemes?: readonly string[],
readonly allowedTags?: readonly string[],
readonly allowedAttributes?: { readonly [key: string]: string[] },
readonly filter?: (token: { tag: string, attrs: { readonly [key: string]: string } }) => boolean,
},
strict?: boolean,
): string;

View File

@@ -5,6 +5,10 @@
export namespace Iterable {
export function is<T = any>(thing: any): thing is IterableIterator<T> {
return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function';
}
const _empty: Iterable<any> = Object.freeze([]);
export function empty<T = any>(): Iterable<T> {
return _empty;

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { once } from 'vs/base/common/functional';
import { Iterable } from 'vs/base/common/iterator';
/**
* Enables logging of potentially leaked disposables.
@@ -54,26 +55,26 @@ export function isDisposable<E extends object>(thing: E): thing is E & IDisposab
export function dispose<T extends IDisposable>(disposable: T): T;
export function dispose<T extends IDisposable>(disposable: T | undefined): T | undefined;
export function dispose<T extends IDisposable, A extends IterableIterator<T> = IterableIterator<T>>(disposables: IterableIterator<T>): A;
export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>;
export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>;
export function dispose<T extends IDisposable>(disposables: T | T[] | undefined): T | T[] | undefined {
if (Array.isArray(disposables)) {
disposables.forEach(d => {
export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | undefined): any {
if (Iterable.is(arg)) {
for (let d of arg) {
if (d) {
markTracked(d);
d.dispose();
}
});
return [];
} else if (disposables) {
markTracked(disposables);
disposables.dispose();
return disposables;
} else {
return undefined;
}
return arg;
} else if (arg) {
markTracked(arg);
arg.dispose();
return arg;
}
}
export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
disposables.forEach(markTracked);
return trackDisposable({ dispose: () => dispose(disposables) });

View File

@@ -206,7 +206,7 @@ export class UriIterator implements IKeyIterator<URI> {
cmp(a: string): number {
if (this._states[this._stateIdx] === UriIteratorState.Scheme) {
return compareSubstringIgnoreCase(a, this._value.scheme);
return compare(a, this._value.scheme);
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
return compareSubstringIgnoreCase(a, this._value.authority);
} else if (this._states[this._stateIdx] === UriIteratorState.Path) {
@@ -486,11 +486,9 @@ export class ResourceMap<T> implements Map<URI, T> {
readonly [Symbol.toStringTag] = 'ResourceMap';
protected readonly map: Map<string, T>;
protected readonly ignoreCase?: boolean;
constructor() {
this.map = new Map<string, T>();
this.ignoreCase = false; // in the future this should be an uri-comparator
constructor(other?: ResourceMap<T>) {
this.map = other ? new Map(other.map) : new Map();
}
set(resource: URI, value: T): this {
@@ -550,20 +548,7 @@ export class ResourceMap<T> implements Map<URI, T> {
}
private toKey(resource: URI): string {
let key = resource.toString();
if (this.ignoreCase) {
key = key.toLowerCase();
}
return key;
}
clone(): ResourceMap<T> {
const resourceMap = new ResourceMap<T>();
this.map.forEach((value, key) => resourceMap.map.set(key, value));
return resourceMap;
return resource.toString();
}
}
@@ -580,18 +565,23 @@ export const enum Touch {
AsNew = 2
}
export class LinkedMap<K, V> {
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 {
@@ -599,6 +589,7 @@ export class LinkedMap<K, V> {
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state++;
}
isEmpty(): boolean {
@@ -632,7 +623,7 @@ export class LinkedMap<K, V> {
return item.value;
}
set(key: K, value: V, touch: Touch = Touch.None): void {
set(key: K, value: V, touch: Touch = Touch.None): this {
let item = this._map.get(key);
if (item) {
item.value = value;
@@ -658,6 +649,7 @@ export class LinkedMap<K, V> {
this._map.set(key, item);
this._size++;
}
return this;
}
delete(key: K): boolean {
@@ -690,6 +682,7 @@ export class LinkedMap<K, V> {
}
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) {
@@ -697,38 +690,25 @@ export class LinkedMap<K, V> {
} else {
callbackfn(current.value, current.key, this);
}
if (this._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
current = current.next;
}
}
values(): V[] {
const result: V[] = [];
let current = this._head;
while (current) {
result.push(current.value);
current = current.next;
}
return result;
}
keys(): K[] {
const result: K[] = [];
let current = this._head;
while (current) {
result.push(current.key);
current = current.next;
}
return result;
}
/* VS Code / Monaco editor runs on es5 which has no Symbol.iterator
keys(): IterableIterator<K> {
const current = this._head;
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<K> = {
[Symbol.iterator]() {
return iterator;
},
next():IteratorResult<K> {
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;
@@ -742,12 +722,17 @@ export class LinkedMap<K, V> {
}
values(): IterableIterator<V> {
const current = this._head;
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<V> = {
[Symbol.iterator]() {
return iterator;
},
next():IteratorResult<V> {
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;
@@ -759,7 +744,34 @@ export class LinkedMap<K, V> {
};
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) {
@@ -781,6 +793,7 @@ export class LinkedMap<K, V> {
if (current) {
current.previous = undefined;
}
this._state++;
}
private addItemFirst(item: Item<K, V>): void {
@@ -794,6 +807,7 @@ export class LinkedMap<K, V> {
this._head.previous = item;
}
this._head = item;
this._state++;
}
private addItemLast(item: Item<K, V>): void {
@@ -807,6 +821,7 @@ export class LinkedMap<K, V> {
this._tail.next = item;
}
this._tail = item;
this._state++;
}
private removeItem(item: Item<K, V>): void {
@@ -843,6 +858,7 @@ export class LinkedMap<K, V> {
}
item.next = undefined;
item.previous = undefined;
this._state++;
}
private touch(item: Item<K, V>, touch: Touch): void {
@@ -879,6 +895,7 @@ export class LinkedMap<K, V> {
item.next = this._head;
this._head.previous = item;
this._head = item;
this._state++;
} else if (touch === Touch.AsNew) {
if (item === this._tail) {
return;
@@ -902,6 +919,7 @@ export class LinkedMap<K, V> {
item.previous = this._tail;
this._tail.next = item;
this._tail = item;
this._state++;
}
}
@@ -953,17 +971,18 @@ export class LRUCache<K, V> extends LinkedMap<K, V> {
this.checkTrim();
}
get(key: K): V | undefined {
return super.get(key, Touch.AsNew);
get(key: K, touch: Touch = Touch.AsNew): V | undefined {
return super.get(key, touch);
}
peek(key: K): V | undefined {
return super.get(key, Touch.None);
}
set(key: K, value: V): void {
set(key: K, value: V): this {
super.set(key, value, Touch.AsNew);
this.checkTrim();
return this;
}
private checkTrim() {

View File

@@ -335,3 +335,13 @@ export function getMediaMime(path: string): string | undefined {
const ext = extname(path);
return mapExtToMediaMimes[ext.toLowerCase()];
}
export function getExtensionForMimeType(mimeType: string): string | undefined {
for (const extension in mapExtToMediaMimes) {
if (mapExtToMediaMimes[extension] === mimeType) {
return extension;
}
}
return undefined;
}

View File

@@ -59,6 +59,8 @@ export namespace Schemas {
export const vscodeSettings = 'vscode-settings';
export const webviewPanel = 'webview-panel';
export const vscodeWebviewResource = 'vscode-webview-resource';
}
class RemoteAuthoritiesImpl {

View File

@@ -6,7 +6,7 @@
import * as extpath from 'vs/base/common/extpath';
import * as paths from 'vs/base/common/path';
import { URI, uriToFsPath } from 'vs/base/common/uri';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { equalsIgnoreCase, compare as strCompare, compareIgnoreCase } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { CharCode } from 'vs/base/common/charCode';
@@ -17,248 +17,363 @@ export function originalFSPath(uri: URI): string {
return uriToFsPath(uri, true);
}
/**
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
* URI queries are included, fragments are ignored.
*/
export function getComparisonKey(resource: URI, caseInsensitivePath = hasToIgnoreCase(resource)): string {
let path = resource.path || '/';
if (caseInsensitivePath) {
path = path.toLowerCase();
}
return resource.with({ authority: resource.authority.toLowerCase(), path: path, fragment: null }).toString();
//#region IExtUri
export interface IExtUri {
// --- identity
/**
* Compares two uris.
*
* @param uri1 Uri
* @param uri2 Uri
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
compare(uri1: URI, uri2: URI, ignoreFragment?: boolean): number;
/**
* Tests whether two uris are equal
*
* @param uri1 Uri
* @param uri2 Uri
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment?: boolean): boolean;
/**
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
*
* @param base A uri which is "longer"
* @param parentCandidate A uri which is "shorter" then `base`
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment?: boolean): boolean;
/**
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
* @see ResourceMap
* @param uri Uri
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
getComparisonKey(uri: URI, ignoreFragment?: boolean): string;
// --- path math
basenameOrAuthority(resource: URI): string;
/**
* Returns the basename of the path component of an uri.
* @param resource
*/
basename(resource: URI): string;
/**
* Returns the extension of the path component of an uri.
* @param resource
*/
extname(resource: URI): string;
/**
* Return a URI representing the directory of a URI path.
*
* @param resource The input URI.
* @returns The URI representing the directory of the input URI.
*/
dirname(resource: URI): URI;
/**
* Join a URI path with path fragments and normalizes the resulting path.
*
* @param resource The input URI.
* @param pathFragment The path fragment to add to the URI path.
* @returns The resulting URI.
*/
joinPath(resource: URI, ...pathFragment: string[]): URI
/**
* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
*
* @param resource The URI to normalize the path.
* @returns The URI with the normalized path.
*/
normalizePath(resource: URI): URI;
/**
*
* @param from
* @param to
*/
relativePath(from: URI, to: URI): string | undefined;
/**
* Resolves an absolute or relative path against a base URI.
* The path can be relative or absolute posix or a Windows path
*/
resolvePath(base: URI, path: string): URI;
// --- misc
/**
* Returns true if the URI path is absolute.
*/
isAbsolutePath(resource: URI): boolean;
/**
* Tests whether the two authorities are the same
*/
isEqualAuthority(a1: string, a2: string): boolean;
/**
* Returns true if the URI path has a trailing path separator
*/
hasTrailingPathSeparator(resource: URI, sep?: string): boolean;
/**
* Removes a trailing path separator, if there's one.
* Important: Doesn't remove the first slash, it would make the URI invalid
*/
removeTrailingPathSeparator(resource: URI, sep?: string): URI;
/**
* Adds a trailing path separator to the URI if there isn't one already.
* For example, c:\ would be unchanged, but c:\users would become c:\users\
*/
addTrailingPathSeparator(resource: URI, sep?: string): URI;
}
export function hasToIgnoreCase(resource: URI | undefined): boolean {
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
return resource && resource.scheme === Schemas.file ? !isLinux : true;
}
export class ExtUri implements IExtUri {
export function basenameOrAuthority(resource: URI): string {
return basename(resource) || resource.authority;
}
constructor(private _ignorePathCasing: (uri: URI) => boolean) { }
/**
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
* URI queries must match, fragments are ignored.
* @param base A uri which is "longer"
* @param parentCandidate A uri which is "shorter" then `base`
*/
export function isEqualOrParent(base: URI, parentCandidate: URI, ignoreCase = hasToIgnoreCase(base)): boolean {
if (base.scheme === parentCandidate.scheme) {
if (base.scheme === Schemas.file) {
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), ignoreCase) && base.query === parentCandidate.query;
}
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
return extpath.isEqualOrParent(base.path || '/', parentCandidate.path || '/', ignoreCase, '/') && base.query === parentCandidate.query;
}
}
return false;
}
/**
* Tests whether the two authorities are the same
*/
export function isEqualAuthority(a1: string, a2: string) {
return a1 === a2 || equalsIgnoreCase(a1, a2);
}
/**
* Tests whether two resources are the same. URI queries must match, fragments are ignored unless requested.
*/
export function isEqual(first: URI | undefined, second: URI | undefined, caseInsensitivePath = hasToIgnoreCase(first), ignoreFragment = true): boolean {
if (first === second) {
return true;
}
if (!first || !second) {
return false;
}
if (first.scheme !== second.scheme || !isEqualAuthority(first.authority, second.authority)) {
return false;
}
const p1 = first.path || '/', p2 = second.path || '/';
return (p1 === p2 || caseInsensitivePath && equalsIgnoreCase(p1, p2)) && first.query === second.query && (ignoreFragment || first.fragment === second.fragment);
}
export function basename(resource: URI): string {
return paths.posix.basename(resource.path);
}
export function extname(resource: URI): string {
return paths.posix.extname(resource.path);
}
/**
* Return a URI representing the directory of a URI path.
*
* @param resource The input URI.
* @returns The URI representing the directory of the input URI.
*/
export function dirname(resource: URI): URI {
if (resource.path.length === 0) {
return resource;
}
let dirname;
if (resource.scheme === Schemas.file) {
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
} else {
dirname = paths.posix.dirname(resource.path);
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
console.error(`dirname("${resource.toString})) resulted in a relative path`);
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
}
}
return resource.with({
path: dirname
});
}
/**
* Join a URI path with path fragments and normalizes the resulting path.
*
* @param resource The input URI.
* @param pathFragment The path fragment to add to the URI path.
* @returns The resulting URI.
*/
export function joinPath(resource: URI, ...pathFragment: string[]): URI {
let joinedPath: string;
if (resource.scheme === 'file') {
joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path;
} else {
joinedPath = paths.posix.join(resource.path || '/', ...pathFragment);
}
return resource.with({
path: joinedPath
});
}
/**
* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
*
* @param resource The URI to normalize the path.
* @returns The URI with the normalized path.
*/
export function normalizePath(resource: URI): URI {
if (!resource.path.length) {
return resource;
}
let normalizedPath: string;
if (resource.scheme === Schemas.file) {
normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
} else {
normalizedPath = paths.posix.normalize(resource.path);
}
return resource.with({
path: normalizedPath
});
}
/**
* Returns true if the URI path is absolute.
*/
export function isAbsolutePath(resource: URI): boolean {
return !!resource.path && resource.path[0] === '/';
}
/**
* Returns true if the URI path has a trailing path separator
*/
export function hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
} else {
const p = resource.path;
return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
}
}
/**
* Removes a trailing path separator, if there's one.
* Important: Doesn't remove the first slash, it would make the URI invalid
*/
export function removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
if (hasTrailingPathSeparator(resource, sep)) {
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
}
return resource;
}
/**
* Adds a trailing path separator to the URI if there isn't one already.
* For example, c:\ would be unchanged, but c:\users would become c:\users\
*/
export function addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
let isRootSep: boolean = false;
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
} else {
sep = '/';
const p = resource.path;
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;
}
if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
return resource.with({ path: resource.path + '/' });
}
return resource;
}
/**
* Returns a relative path between two URIs. If the URIs don't have the same schema or authority, `undefined` is returned.
* The returned relative path always uses forward slashes.
*/
export function relativePath(from: URI, to: URI, caseInsensitivePath = hasToIgnoreCase(from)): string | undefined {
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
return undefined;
}
if (from.scheme === Schemas.file) {
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
}
let fromPath = from.path || '/', toPath = to.path || '/';
if (caseInsensitivePath) {
// 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;
compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number {
// scheme
let ret = strCompare(uri1.scheme, uri2.scheme);
if (ret === 0) {
// authority
ret = compareIgnoreCase(uri1.authority, uri2.authority);
if (ret === 0) {
// path
ret = this._ignorePathCasing(uri1) ? compareIgnoreCase(uri1.path, uri2.path) : strCompare(uri1.path, uri2.path);
// query
if (ret === 0) {
ret = strCompare(uri1.query, uri2.query);
// fragment
if (ret === 0 && !ignoreFragment) {
ret = strCompare(uri1.fragment, uri2.fragment);
}
}
}
}
fromPath = toPath.substr(0, i) + fromPath.substr(i);
return ret;
}
getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
return uri.with({
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
fragment: ignoreFragment ? null : undefined
}).toString();
}
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean {
if (uri1 === uri2) {
return true;
}
if (!uri1 || !uri2) {
return false;
}
if (uri1.scheme !== uri2.scheme || !isEqualAuthority(uri1.authority, uri2.authority)) {
return false;
}
const p1 = uri1.path, p2 = uri2.path;
return (p1 === p2 || this._ignorePathCasing(uri1) && equalsIgnoreCase(p1, p2)) && uri1.query === uri2.query && (ignoreFragment || uri1.fragment === uri2.fragment);
}
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {
if (base.scheme === parentCandidate.scheme) {
if (base.scheme === Schemas.file) {
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), this._ignorePathCasing(base)) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
}
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
return extpath.isEqualOrParent(base.path, parentCandidate.path, this._ignorePathCasing(base), '/') && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
}
}
return false;
}
// --- path math
joinPath(resource: URI, ...pathFragment: string[]): URI {
return URI.joinPath(resource, ...pathFragment);
}
basenameOrAuthority(resource: URI): string {
return basename(resource) || resource.authority;
}
basename(resource: URI): string {
return paths.posix.basename(resource.path);
}
extname(resource: URI): string {
return paths.posix.extname(resource.path);
}
dirname(resource: URI): URI {
if (resource.path.length === 0) {
return resource;
}
let dirname;
if (resource.scheme === Schemas.file) {
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
} else {
dirname = paths.posix.dirname(resource.path);
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
console.error(`dirname("${resource.toString})) resulted in a relative path`);
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
}
}
return resource.with({
path: dirname
});
}
normalizePath(resource: URI): URI {
if (!resource.path.length) {
return resource;
}
let normalizedPath: string;
if (resource.scheme === Schemas.file) {
normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
} else {
normalizedPath = paths.posix.normalize(resource.path);
}
return resource.with({
path: normalizedPath
});
}
relativePath(from: URI, to: URI): string | undefined {
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
return undefined;
}
if (from.scheme === Schemas.file) {
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
}
let fromPath = from.path || '/', toPath = to.path || '/';
if (this._ignorePathCasing(from)) {
// make casing of fromPath match toPath
let i = 0;
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
break;
}
}
}
fromPath = toPath.substr(0, i) + fromPath.substr(i);
}
return paths.posix.relative(fromPath, toPath);
}
resolvePath(base: URI, path: string): URI {
if (base.scheme === Schemas.file) {
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
return base.with({
authority: newURI.authority,
path: newURI.path
});
}
if (path.indexOf('/') === -1) { // no slashes? it's likely a Windows path
path = extpath.toSlashes(path);
if (/^[a-zA-Z]:(\/|$)/.test(path)) { // starts with a drive letter
path = '/' + path;
}
}
return base.with({
path: paths.posix.resolve(base.path, path)
});
}
// --- misc
isAbsolutePath(resource: URI): boolean {
return !!resource.path && resource.path[0] === '/';
}
isEqualAuthority(a1: string, a2: string) {
return a1 === a2 || equalsIgnoreCase(a1, a2);
}
hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
} else {
const p = resource.path;
return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
}
}
removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
if (hasTrailingPathSeparator(resource, sep)) {
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
}
return resource;
}
addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
let isRootSep: boolean = false;
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
} else {
sep = '/';
const p = resource.path;
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;
}
if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
return resource.with({ path: resource.path + '/' });
}
return resource;
}
return paths.posix.relative(fromPath, toPath);
}
/**
* Resolves an absolute or relative path against a base URI.
* The path can be relative or absolute posix or a Windows path
* Unbiased utility that takes uris "as they are". This means it can be interchanged with
* uri#toString() usages. The following is true
* ```
* assertEqual(aUri.toString() === bUri.toString(), exturi.isEqual(aUri, bUri))
* ```
*/
export function resolvePath(base: URI, path: string): URI {
if (base.scheme === Schemas.file) {
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
return base.with({
authority: newURI.authority,
path: newURI.path
});
}
if (path.indexOf('/') === -1) { // no slashes? it's likely a Windows path
path = extpath.toSlashes(path);
if (/^[a-zA-Z]:(\/|$)/.test(path)) { // starts with a drive letter
path = '/' + path;
}
}
return base.with({
path: paths.posix.resolve(base.path, path)
});
}
export const extUri = new ExtUri(() => false);
/**
* BIASED utility that always ignores the casing of uris path. ONLY use these util if you
* understand what you are doing.
*
* Note that `IUriIdentityService#extUri` is a better replacement for this because that utility
* knows when path casing matters and when not.
*/
export const extUriIgnorePathCase = new ExtUri(_ => true);
const exturiBiasedIgnorePathCase = new ExtUri(uri => {
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
return uri && uri.scheme === Schemas.file ? !isLinux : true;
});
export const isEqual = exturiBiasedIgnorePathCase.isEqual.bind(exturiBiasedIgnorePathCase);
export const isEqualOrParent = exturiBiasedIgnorePathCase.isEqualOrParent.bind(exturiBiasedIgnorePathCase);
export const getComparisonKey = exturiBiasedIgnorePathCase.getComparisonKey.bind(exturiBiasedIgnorePathCase);
export const basenameOrAuthority = exturiBiasedIgnorePathCase.basenameOrAuthority.bind(exturiBiasedIgnorePathCase);
export const basename = exturiBiasedIgnorePathCase.basename.bind(exturiBiasedIgnorePathCase);
export const extname = exturiBiasedIgnorePathCase.extname.bind(exturiBiasedIgnorePathCase);
export const dirname = exturiBiasedIgnorePathCase.dirname.bind(exturiBiasedIgnorePathCase);
export const joinPath = extUri.joinPath.bind(extUri);
export const normalizePath = exturiBiasedIgnorePathCase.normalizePath.bind(exturiBiasedIgnorePathCase);
export const relativePath = exturiBiasedIgnorePathCase.relativePath.bind(exturiBiasedIgnorePathCase);
export const resolvePath = exturiBiasedIgnorePathCase.resolvePath.bind(exturiBiasedIgnorePathCase);
export const isAbsolutePath = exturiBiasedIgnorePathCase.isAbsolutePath.bind(exturiBiasedIgnorePathCase);
export const isEqualAuthority = exturiBiasedIgnorePathCase.isEqualAuthority.bind(exturiBiasedIgnorePathCase);
export const hasTrailingPathSeparator = exturiBiasedIgnorePathCase.hasTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
export const removeTrailingPathSeparator = exturiBiasedIgnorePathCase.removeTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
export const addTrailingPathSeparator = exturiBiasedIgnorePathCase.addTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
//#endregion
export function distinctParents<T>(items: T[], resourceAccessor: (item: T) => URI): T[] {
const distinctParents: T[] = [];

View File

@@ -13,10 +13,18 @@ export const enum ScrollbarVisibility {
}
export interface ScrollEvent {
oldWidth: number;
oldScrollWidth: number;
oldScrollLeft: number;
width: number;
scrollWidth: number;
scrollLeft: number;
oldHeight: number;
oldScrollHeight: number;
oldScrollTop: number;
height: number;
scrollHeight: number;
scrollTop: number;
@@ -134,10 +142,18 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
const scrollTopChanged = (this.scrollTop !== previous.scrollTop);
return {
oldWidth: previous.width,
oldScrollWidth: previous.scrollWidth,
oldScrollLeft: previous.scrollLeft,
width: this.width,
scrollWidth: this.scrollWidth,
scrollLeft: this.scrollLeft,
oldHeight: previous.height,
oldScrollHeight: previous.scrollHeight,
oldScrollTop: previous.scrollTop,
height: this.height,
scrollHeight: this.scrollHeight,
scrollTop: this.scrollTop,

View File

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

View File

@@ -728,6 +728,14 @@ export function isBasicASCII(str: string): boolean {
return IS_BASIC_ASCII.test(str);
}
export const UNUSUAL_LINE_TERMINATORS = /[\u2028\u2029\u0085]/; // LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL)
/**
* Returns true if `str` contains unusual line terminators, like LS, PS or NEL
*/
export function containsUnusualLineTerminators(str: string): boolean {
return UNUSUAL_LINE_TERMINATORS.test(str);
}
export function containsFullWidthCharacter(str: string): boolean {
for (let i = 0, len = str.length; i < len; i++) {
if (isFullWidthCharacter(str.charCodeAt(i))) {