mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-12 02:58:31 -05:00
Merge from vscode 79a1f5a5ca0c6c53db617aa1fa5a2396d2caebe2
This commit is contained in:
@@ -764,7 +764,7 @@ export class IdleValue<T> {
|
||||
this._handle.dispose();
|
||||
}
|
||||
|
||||
getValue(): T {
|
||||
get value(): T {
|
||||
if (!this._didRun) {
|
||||
this._handle.dispose();
|
||||
this._executor();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
1
src/vs/base/common/insane/insane.d.ts
vendored
1
src/vs/base/common/insane/insane.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) });
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -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,
|
||||
|
||||
203
src/vs/base/common/skipList.ts
Normal file
203
src/vs/base/common/skipList.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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))) {
|
||||
|
||||
Reference in New Issue
Block a user