Merge from vscode cfc1ab4c5f816765b91fb7ead3c3427a7c8581a3

This commit is contained in:
ADS Merger
2020-03-11 04:19:23 +00:00
parent 16fab722d5
commit 4c3e48773d
880 changed files with 20441 additions and 11232 deletions

View File

@@ -372,12 +372,6 @@ export function distinctES6<T>(array: ReadonlyArray<T>): T[] {
});
}
export function fromSet<T>(set: Set<T>): T[] {
const result: T[] = [];
set.forEach(o => result.push(o));
return result;
}
export function uniqueFilter<T>(keyFn: (t: T) => string): (t: T) => boolean {
const seen: { [key: string]: boolean; } = Object.create(null);
@@ -405,6 +399,9 @@ export function lastIndex<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean):
return -1;
}
/**
* @deprecated ES6: use `Array.findIndex`
*/
export function firstIndex<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean): number {
for (let i = 0; i < array.length; i++) {
const element = array[i];
@@ -417,6 +414,10 @@ export function firstIndex<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean)
return -1;
}
/**
* @deprecated ES6: use `Array.find`
*/
export function first<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean, notFoundValue: T): T;
export function first<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean): T | undefined;
export function first<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean, notFoundValue: T | undefined = undefined): T | undefined {
@@ -471,6 +472,9 @@ export function range(arg: number, to?: number): number[] {
return result;
}
/**
* @deprecated ES6: use `Array.fill`
*/
export function fill<T>(num: number, value: T, arr: T[] = []): T[] {
for (let i = 0; i < num; i++) {
arr[i] = value;
@@ -564,6 +568,10 @@ export function pushToEnd<T>(arr: T[], value: T): void {
}
}
/**
* @deprecated ES6: use `Array.find`
*/
export function find<T>(arr: ArrayLike<T>, predicate: (value: T, index: number, arr: ArrayLike<T>) => any): T | undefined {
for (let i = 0; i < arr.length; i++) {
const element = arr[i];

View File

@@ -6,8 +6,8 @@
/**
* Throws an error with the provided message if the provided value does not evaluate to a true Javascript value.
*/
export function ok(value?: any, message?: string) {
export function ok(value?: unknown, message?: string) {
if (!value) {
throw new Error(message ? 'Assertion failed (' + message + ')' : 'Assertion Failed');
throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed');
}
}

View File

@@ -837,7 +837,7 @@ export class TaskSequentializer {
this._pending?.cancel();
}
setPending(taskId: number, promise: Promise<void>, onCancel?: () => void, ): Promise<void> {
setPending(taskId: number, promise: Promise<void>, onCancel?: () => void,): Promise<void> {
this._pending = { taskId: taskId, cancel: () => onCancel?.(), promise };
promise.then(() => this.donePending(taskId), () => this.donePending(taskId));

View File

@@ -31,7 +31,7 @@ const shortcutEvent: Event<any> = Object.freeze(function (callback, context?): I
export namespace CancellationToken {
export function isCancellationToken(thing: any): thing is CancellationToken {
export function isCancellationToken(thing: unknown): thing is CancellationToken {
if (thing === CancellationToken.None || thing === CancellationToken.Cancelled) {
return true;
}

View File

@@ -27,3 +27,8 @@ export function renderCodicons(text: string): string {
: `<span class="codicon codicon-${name}${animation ? ` codicon-animation-${animation}` : ''}"></span>`;
});
}
const stripCodiconsRegex = /(\s)?(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)(\s)?/gi;
export function stripCodicons(text: string): string {
return text.replace(stripCodiconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || '');
}

View File

@@ -97,11 +97,6 @@ export function fromMap<T>(original: Map<string, T>): IStringDictionary<T> {
return result;
}
export function mapValues<V>(map: Map<any, V>): V[] {
const result: V[] = [];
map.forEach(v => result.push(v));
return result;
}
export class SetMap<K, V> {

View File

@@ -13,7 +13,7 @@ export interface IErrorWithActions {
actions?: ReadonlyArray<IAction>;
}
export function isErrorWithActions(obj: any): obj is IErrorWithActions {
export function isErrorWithActions(obj: unknown): obj is IErrorWithActions {
return obj instanceof Error && Array.isArray((obj as IErrorWithActions).actions);
}

View File

@@ -576,8 +576,8 @@ export class Emitter<T> {
this._deliveryQueue = new LinkedList();
}
for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
this._deliveryQueue.push([e.value, event]);
for (let listener of this._listeners) {
this._deliveryQueue.push([listener, event]);
}
while (this._deliveryQueue.size > 0) {
@@ -671,8 +671,8 @@ export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
this._asyncDeliveryQueue = new LinkedList();
}
for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
this._asyncDeliveryQueue.push([e.value, data]);
for (const listener of this._listeners) {
this._asyncDeliveryQueue.push([listener, data]);
}
while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) {

View File

@@ -3,10 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function once<T extends Function>(this: any, fn: T): T {
export function once<T extends Function>(this: unknown, fn: T): T {
const _this = this;
let didCall = false;
let result: any;
let result: unknown;
return function () {
if (didCall) {
@@ -17,5 +17,5 @@ export function once<T extends Function>(this: any, fn: T): T {
result = fn.apply(_this, arguments);
return result;
} as any as T;
}
} as unknown as T;
}

View File

@@ -0,0 +1,637 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { compareAnything } from 'vs/base/common/comparers';
import { matchesPrefix, IMatch, matchesCamelCase, isUpper } from 'vs/base/common/filters';
import { sep } from 'vs/base/common/path';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings';
import { CharCode } from 'vs/base/common/charCode';
export type Score = [number /* score */, number[] /* match positions */];
export type ScorerCache = { [key: string]: IItemScore };
const NO_MATCH = 0;
const NO_SCORE: Score = [NO_MATCH, []];
// const DEBUG = false;
// const DEBUG_MATRIX = false;
export function score(target: string, query: string, queryLower: string, fuzzy: boolean): Score {
if (!target || !query) {
return NO_SCORE; // return early if target or query are undefined
}
const targetLength = target.length;
const queryLength = query.length;
if (targetLength < queryLength) {
return NO_SCORE; // impossible for query to be contained in target
}
// if (DEBUG) {
// console.group(`Target: ${target}, Query: ${query}`);
// }
const targetLower = target.toLowerCase();
// When not searching fuzzy, we require the query to be contained fully
// in the target string contiguously.
if (!fuzzy) {
const indexOfQueryInTarget = targetLower.indexOf(queryLower);
if (indexOfQueryInTarget === -1) {
// if (DEBUG) {
// console.log(`Characters not matching consecutively ${queryLower} within ${targetLower}`);
// }
return NO_SCORE;
}
}
const res = doScore(query, queryLower, queryLength, target, targetLower, targetLength);
// if (DEBUG) {
// console.log(`%cFinal Score: ${res[0]}`, 'font-weight: bold');
// console.groupEnd();
// }
return res;
}
function doScore(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number): Score {
const scores: number[] = [];
const matches: number[] = [];
//
// Build Scorer Matrix:
//
// The matrix is composed of query q and target t. For each index we score
// q[i] with t[i] and compare that with the previous score. If the score is
// equal or larger, we keep the match. In addition to the score, we also keep
// the length of the consecutive matches to use as boost for the score.
//
// t a r g e t
// q
// u
// e
// r
// y
//
for (let queryIndex = 0; queryIndex < queryLength; queryIndex++) {
const queryIndexOffset = queryIndex * targetLength;
const queryIndexPreviousOffset = queryIndexOffset - targetLength;
const queryIndexGtNull = queryIndex > 0;
const queryCharAtIndex = query[queryIndex];
const queryLowerCharAtIndex = queryLower[queryIndex];
for (let targetIndex = 0; targetIndex < targetLength; targetIndex++) {
const targetIndexGtNull = targetIndex > 0;
const currentIndex = queryIndexOffset + targetIndex;
const leftIndex = currentIndex - 1;
const diagIndex = queryIndexPreviousOffset + targetIndex - 1;
const leftScore = targetIndexGtNull ? scores[leftIndex] : 0;
const diagScore = queryIndexGtNull && targetIndexGtNull ? scores[diagIndex] : 0;
const matchesSequenceLength = queryIndexGtNull && targetIndexGtNull ? matches[diagIndex] : 0;
// If we are not matching on the first query character any more, we only produce a
// score if we had a score previously for the last query index (by looking at the diagScore).
// This makes sure that the query always matches in sequence on the target. For example
// given a target of "ede" and a query of "de", we would otherwise produce a wrong high score
// for query[1] ("e") matching on target[0] ("e") because of the "beginning of word" boost.
let score: number;
if (!diagScore && queryIndexGtNull) {
score = 0;
} else {
score = computeCharScore(queryCharAtIndex, queryLowerCharAtIndex, target, targetLower, targetIndex, matchesSequenceLength);
}
// We have a score and its equal or larger than the left score
// Match: sequence continues growing from previous diag value
// Score: increases by diag score value
if (score && diagScore + score >= leftScore) {
matches[currentIndex] = matchesSequenceLength + 1;
scores[currentIndex] = diagScore + score;
}
// We either have no score or the score is lower than the left score
// Match: reset to 0
// Score: pick up from left hand side
else {
matches[currentIndex] = NO_MATCH;
scores[currentIndex] = leftScore;
}
}
}
// Restore Positions (starting from bottom right of matrix)
const positions: number[] = [];
let queryIndex = queryLength - 1;
let targetIndex = targetLength - 1;
while (queryIndex >= 0 && targetIndex >= 0) {
const currentIndex = queryIndex * targetLength + targetIndex;
const match = matches[currentIndex];
if (match === NO_MATCH) {
targetIndex--; // go left
} else {
positions.push(targetIndex);
// go up and left
queryIndex--;
targetIndex--;
}
}
// Print matrix
// if (DEBUG_MATRIX) {
// printMatrix(query, target, matches, scores);
// }
return [scores[queryLength * targetLength - 1], positions.reverse()];
}
function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: string, target: string, targetLower: string, targetIndex: number, matchesSequenceLength: number): number {
let score = 0;
if (queryLowerCharAtIndex !== targetLower[targetIndex]) {
return score; // no match of characters
}
// Character match bonus
score += 1;
// if (DEBUG) {
// console.groupCollapsed(`%cCharacter match bonus: +1 (char: ${queryLower[queryIndex]} at index ${targetIndex}, total score: ${score})`, 'font-weight: normal');
// }
// Consecutive match bonus
if (matchesSequenceLength > 0) {
score += (matchesSequenceLength * 5);
// if (DEBUG) {
// console.log('Consecutive match bonus: ' + (matchesSequenceLength * 5));
// }
}
// Same case bonus
if (queryCharAtIndex === target[targetIndex]) {
score += 1;
// if (DEBUG) {
// console.log('Same case bonus: +1');
// }
}
// Start of word bonus
if (targetIndex === 0) {
score += 8;
// if (DEBUG) {
// console.log('Start of word bonus: +8');
// }
}
else {
// After separator bonus
const separatorBonus = scoreSeparatorAtPos(target.charCodeAt(targetIndex - 1));
if (separatorBonus) {
score += separatorBonus;
// if (DEBUG) {
// console.log('After separtor bonus: +4');
// }
}
// Inside word upper case bonus (camel case)
else if (isUpper(target.charCodeAt(targetIndex))) {
score += 1;
// if (DEBUG) {
// console.log('Inside word upper case bonus: +1');
// }
}
}
// if (DEBUG) {
// console.groupEnd();
// }
return score;
}
function scoreSeparatorAtPos(charCode: number): number {
switch (charCode) {
case CharCode.Slash:
case CharCode.Backslash:
return 5; // prefer path separators...
case CharCode.Underline:
case CharCode.Dash:
case CharCode.Period:
case CharCode.Space:
case CharCode.SingleQuote:
case CharCode.DoubleQuote:
case CharCode.Colon:
return 4; // ...over other separators
default:
return 0;
}
}
// function printMatrix(query: string, target: string, matches: number[], scores: number[]): void {
// console.log('\t' + target.split('').join('\t'));
// for (let queryIndex = 0; queryIndex < query.length; queryIndex++) {
// let line = query[queryIndex] + '\t';
// for (let targetIndex = 0; targetIndex < target.length; targetIndex++) {
// const currentIndex = queryIndex * target.length + targetIndex;
// line = line + 'M' + matches[currentIndex] + '/' + 'S' + scores[currentIndex] + '\t';
// }
// console.log(line);
// }
// }
/**
* Scoring on structural items that have a label and optional description.
*/
export interface IItemScore {
/**
* Overall score.
*/
score: number;
/**
* Matches within the label.
*/
labelMatch?: IMatch[];
/**
* Matches within the description.
*/
descriptionMatch?: IMatch[];
}
const NO_ITEM_SCORE: IItemScore = Object.freeze({ score: 0 });
export interface IItemAccessor<T> {
/**
* Just the label of the item to score on.
*/
getItemLabel(item: T): string | undefined;
/**
* The optional description of the item to score on.
*/
getItemDescription(item: T): string | undefined;
/**
* If the item is a file, the path of the file to score on.
*/
getItemPath(file: T): string | undefined;
}
const PATH_IDENTITY_SCORE = 1 << 18;
const LABEL_PREFIX_SCORE = 1 << 17;
const LABEL_CAMELCASE_SCORE = 1 << 16;
const LABEL_SCORE_THRESHOLD = 1 << 15;
export interface IPreparedQuery {
original: string;
value: string;
lowercase: string;
containsPathSeparator: boolean;
}
/**
* Helper function to prepare a search value for scoring by removing unwanted characters.
*/
export function prepareQuery(original: string): IPreparedQuery {
if (!original) {
original = '';
}
let value = stripWildcards(original).replace(/\s/g, ''); // get rid of all wildcards and whitespace
if (isWindows) {
value = value.replace(/\//g, sep); // Help Windows users to search for paths when using slash
}
const lowercase = value.toLowerCase();
const containsPathSeparator = value.indexOf(sep) >= 0;
return { original, value, lowercase, containsPathSeparator };
}
export function scoreItem<T>(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache): IItemScore {
if (!item || !query.value) {
return NO_ITEM_SCORE; // we need an item and query to score on at least
}
const label = accessor.getItemLabel(item);
if (!label) {
return NO_ITEM_SCORE; // we need a label at least
}
const description = accessor.getItemDescription(item);
let cacheHash: string;
if (description) {
cacheHash = `${label}${description}${query.value}${fuzzy}`;
} else {
cacheHash = `${label}${query.value}${fuzzy}`;
}
const cached = cache[cacheHash];
if (cached) {
return cached;
}
const itemScore = doScoreItem(label, description, accessor.getItemPath(item), query, fuzzy);
cache[cacheHash] = itemScore;
return itemScore;
}
function createMatches(offsets: undefined | number[]): IMatch[] {
let ret: IMatch[] = [];
if (!offsets) {
return ret;
}
let last: IMatch | undefined;
for (const pos of offsets) {
if (last && last.end === pos) {
last.end += 1;
} else {
last = { start: pos, end: pos + 1 };
ret.push(last);
}
}
return ret;
}
function doScoreItem(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore {
// 1.) treat identity matches on full path highest
if (path && (isLinux ? query.original === path : equalsIgnoreCase(query.original, path))) {
return { score: PATH_IDENTITY_SCORE, labelMatch: [{ start: 0, end: label.length }], descriptionMatch: description ? [{ start: 0, end: description.length }] : undefined };
}
// We only consider label matches if the query is not including file path separators
const preferLabelMatches = !path || !query.containsPathSeparator;
if (preferLabelMatches) {
// 2.) treat prefix matches on the label second highest
const prefixLabelMatch = matchesPrefix(query.value, label);
if (prefixLabelMatch) {
return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch };
}
// 3.) treat camelcase matches on the label third highest
const camelcaseLabelMatch = matchesCamelCase(query.value, label);
if (camelcaseLabelMatch) {
return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch };
}
// 4.) prefer scores on the label if any
const [labelScore, labelPositions] = score(label, query.value, query.lowercase, fuzzy);
if (labelScore) {
return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) };
}
}
// 5.) finally compute description + label scores if we have a description
if (description) {
let descriptionPrefix = description;
if (!!path) {
descriptionPrefix = `${description}${sep}`; // assume this is a file path
}
const descriptionPrefixLength = descriptionPrefix.length;
const descriptionAndLabel = `${descriptionPrefix}${label}`;
const [labelDescriptionScore, labelDescriptionPositions] = score(descriptionAndLabel, query.value, query.lowercase, fuzzy);
if (labelDescriptionScore) {
const labelDescriptionMatches = createMatches(labelDescriptionPositions);
const labelMatch: IMatch[] = [];
const descriptionMatch: IMatch[] = [];
// We have to split the matches back onto the label and description portions
labelDescriptionMatches.forEach(h => {
// Match overlaps label and description part, we need to split it up
if (h.start < descriptionPrefixLength && h.end > descriptionPrefixLength) {
labelMatch.push({ start: 0, end: h.end - descriptionPrefixLength });
descriptionMatch.push({ start: h.start, end: descriptionPrefixLength });
}
// Match on label part
else if (h.start >= descriptionPrefixLength) {
labelMatch.push({ start: h.start - descriptionPrefixLength, end: h.end - descriptionPrefixLength });
}
// Match on description part
else {
descriptionMatch.push(h);
}
});
return { score: labelDescriptionScore, labelMatch, descriptionMatch };
}
}
return NO_ITEM_SCORE;
}
export function compareItemsByScore<T>(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache, fallbackComparer = fallbackCompare): number {
const itemScoreA = scoreItem(itemA, query, fuzzy, accessor, cache);
const itemScoreB = scoreItem(itemB, query, fuzzy, accessor, cache);
const scoreA = itemScoreA.score;
const scoreB = itemScoreB.score;
// 1.) prefer identity matches
if (scoreA === PATH_IDENTITY_SCORE || scoreB === PATH_IDENTITY_SCORE) {
if (scoreA !== scoreB) {
return scoreA === PATH_IDENTITY_SCORE ? -1 : 1;
}
}
// 2.) prefer label prefix matches
if (scoreA === LABEL_PREFIX_SCORE || scoreB === LABEL_PREFIX_SCORE) {
if (scoreA !== scoreB) {
return scoreA === LABEL_PREFIX_SCORE ? -1 : 1;
}
const labelA = accessor.getItemLabel(itemA) || '';
const labelB = accessor.getItemLabel(itemB) || '';
// prefer shorter names when both match on label prefix
if (labelA.length !== labelB.length) {
return labelA.length - labelB.length;
}
}
// 3.) prefer camelcase matches
if (scoreA === LABEL_CAMELCASE_SCORE || scoreB === LABEL_CAMELCASE_SCORE) {
if (scoreA !== scoreB) {
return scoreA === LABEL_CAMELCASE_SCORE ? -1 : 1;
}
const labelA = accessor.getItemLabel(itemA) || '';
const labelB = accessor.getItemLabel(itemB) || '';
// prefer more compact camel case matches over longer
const comparedByMatchLength = compareByMatchLength(itemScoreA.labelMatch, itemScoreB.labelMatch);
if (comparedByMatchLength !== 0) {
return comparedByMatchLength;
}
// prefer shorter names when both match on label camelcase
if (labelA.length !== labelB.length) {
return labelA.length - labelB.length;
}
}
// 4.) prefer label scores
if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) {
if (scoreB < LABEL_SCORE_THRESHOLD) {
return -1;
}
if (scoreA < LABEL_SCORE_THRESHOLD) {
return 1;
}
}
// 5.) compare by score
if (scoreA !== scoreB) {
return scoreA > scoreB ? -1 : 1;
}
// 6.) scores are identical, prefer more compact matches (label and description)
const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor);
const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor);
if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) {
return itemBMatchDistance > itemAMatchDistance ? -1 : 1;
}
// 7.) at this point, scores are identical and match compactness as well
// for both items so we start to use the fallback compare
return fallbackComparer(itemA, itemB, query, accessor);
}
function computeLabelAndDescriptionMatchDistance<T>(item: T, score: IItemScore, accessor: IItemAccessor<T>): number {
let matchStart: number = -1;
let matchEnd: number = -1;
// If we have description matches, the start is first of description match
if (score.descriptionMatch && score.descriptionMatch.length) {
matchStart = score.descriptionMatch[0].start;
}
// Otherwise, the start is the first label match
else if (score.labelMatch && score.labelMatch.length) {
matchStart = score.labelMatch[0].start;
}
// If we have label match, the end is the last label match
// If we had a description match, we add the length of the description
// as offset to the end to indicate this.
if (score.labelMatch && score.labelMatch.length) {
matchEnd = score.labelMatch[score.labelMatch.length - 1].end;
if (score.descriptionMatch && score.descriptionMatch.length) {
const itemDescription = accessor.getItemDescription(item);
if (itemDescription) {
matchEnd += itemDescription.length;
}
}
}
// If we have just a description match, the end is the last description match
else if (score.descriptionMatch && score.descriptionMatch.length) {
matchEnd = score.descriptionMatch[score.descriptionMatch.length - 1].end;
}
return matchEnd - matchStart;
}
function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number {
if ((!matchesA && !matchesB) || ((!matchesA || !matchesA.length) && (!matchesB || !matchesB.length))) {
return 0; // make sure to not cause bad comparing when matches are not provided
}
if (!matchesB || !matchesB.length) {
return -1;
}
if (!matchesA || !matchesA.length) {
return 1;
}
// Compute match length of A (first to last match)
const matchStartA = matchesA[0].start;
const matchEndA = matchesA[matchesA.length - 1].end;
const matchLengthA = matchEndA - matchStartA;
// Compute match length of B (first to last match)
const matchStartB = matchesB[0].start;
const matchEndB = matchesB[matchesB.length - 1].end;
const matchLengthB = matchEndB - matchStartB;
// Prefer shorter match length
return matchLengthA === matchLengthB ? 0 : matchLengthB < matchLengthA ? 1 : -1;
}
function fallbackCompare<T>(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor<T>): number {
// check for label + description length and prefer shorter
const labelA = accessor.getItemLabel(itemA) || '';
const labelB = accessor.getItemLabel(itemB) || '';
const descriptionA = accessor.getItemDescription(itemA);
const descriptionB = accessor.getItemDescription(itemB);
const labelDescriptionALength = labelA.length + (descriptionA ? descriptionA.length : 0);
const labelDescriptionBLength = labelB.length + (descriptionB ? descriptionB.length : 0);
if (labelDescriptionALength !== labelDescriptionBLength) {
return labelDescriptionALength - labelDescriptionBLength;
}
// check for path length and prefer shorter
const pathA = accessor.getItemPath(itemA);
const pathB = accessor.getItemPath(itemB);
if (pathA && pathB && pathA.length !== pathB.length) {
return pathA.length - pathB.length;
}
// 7.) finally we have equal scores and equal length, we fallback to comparer
// compare by label
if (labelA !== labelB) {
return compareAnything(labelA, labelB, query.value);
}
// compare by description
if (descriptionA && descriptionB && descriptionA !== descriptionB) {
return compareAnything(descriptionA, descriptionB, query.value);
}
// compare by path
if (pathA && pathB && pathA !== pathB) {
return compareAnything(pathA, pathB, query.value);
}
// equal
return 0;
}

View File

@@ -34,6 +34,28 @@ export interface NativeIterator<T> {
next(): NativeIteratorResult<T>;
}
export namespace Iterable {
export function first<T>(iterable: Iterable<T>): T | undefined {
return iterable[Symbol.iterator]().next().value;
}
export function some<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): boolean {
for (const element of iterable) {
if (predicate(element)) {
return true;
}
}
return false;
}
export function* map<T, R>(iterable: Iterable<T>, fn: (t: T) => R): IterableIterator<R> {
for (const element of iterable) {
return yield fn(element);
}
}
}
export module Iterator {
const _empty: Iterator<any> = {
next() {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { sep, posix, normalize, win32 } from 'vs/base/common/path';
import { posix, normalize, win32, sep } from 'vs/base/common/path';
import { endsWith, startsWithIgnoreCase, rtrim, startsWith } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform';
@@ -160,7 +160,7 @@ export function untildify(path: string, userHome: string): string {
const ellipsis = '\u2026';
const unc = '\\\\';
const home = '~';
export function shorten(paths: string[]): string[] {
export function shorten(paths: string[], pathSeparator: string = sep): string[] {
const shortenedPaths: string[] = new Array(paths.length);
// for every path
@@ -169,7 +169,7 @@ export function shorten(paths: string[]): string[] {
let path = paths[pathIndex];
if (path === '') {
shortenedPaths[pathIndex] = `.${sep}`;
shortenedPaths[pathIndex] = `.${pathSeparator}`;
continue;
}
@@ -185,20 +185,20 @@ export function shorten(paths: string[]): string[] {
if (path.indexOf(unc) === 0) {
prefix = path.substr(0, path.indexOf(unc) + unc.length);
path = path.substr(path.indexOf(unc) + unc.length);
} else if (path.indexOf(sep) === 0) {
prefix = path.substr(0, path.indexOf(sep) + sep.length);
path = path.substr(path.indexOf(sep) + sep.length);
} else if (path.indexOf(pathSeparator) === 0) {
prefix = path.substr(0, path.indexOf(pathSeparator) + pathSeparator.length);
path = path.substr(path.indexOf(pathSeparator) + pathSeparator.length);
} else if (path.indexOf(home) === 0) {
prefix = path.substr(0, path.indexOf(home) + home.length);
path = path.substr(path.indexOf(home) + home.length);
}
// pick the first shortest subpath found
const segments: string[] = path.split(sep);
const segments: string[] = path.split(pathSeparator);
for (let subpathLength = 1; match && subpathLength <= segments.length; subpathLength++) {
for (let start = segments.length - subpathLength; match && start >= 0; start--) {
match = false;
let subpath = segments.slice(start, start + subpathLength).join(sep);
let subpath = segments.slice(start, start + subpathLength).join(pathSeparator);
// that is unique to any other path
for (let otherPathIndex = 0; !match && otherPathIndex < paths.length; otherPathIndex++) {
@@ -209,7 +209,7 @@ export function shorten(paths: string[]): string[] {
// Adding separator as prefix for subpath, such that 'endsWith(src, trgt)' considers subpath as directory name instead of plain string.
// prefix is not added when either subpath is root directory or path[otherPathIndex] does not have multiple directories.
const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(sep) > -1) ? sep + subpath : subpath;
const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(pathSeparator) > -1) ? pathSeparator + subpath : subpath;
const isOtherPathEnding: boolean = endsWith(paths[otherPathIndex], subpathWithSep);
match = !isSubpathEnding || isOtherPathEnding;
@@ -226,11 +226,11 @@ export function shorten(paths: string[]): string[] {
// extend subpath to include disk drive prefix
start = 0;
subpathLength++;
subpath = segments[0] + sep + subpath;
subpath = segments[0] + pathSeparator + subpath;
}
if (start > 0) {
result = segments[0] + sep;
result = segments[0] + pathSeparator;
}
result = prefix + result;
@@ -238,14 +238,14 @@ export function shorten(paths: string[]): string[] {
// add ellipsis at the beginning if neeeded
if (start > 0) {
result = result + ellipsis + sep;
result = result + ellipsis + pathSeparator;
}
result = result + subpath;
// add ellipsis at the end if needed
if (start + subpathLength < segments.length) {
result = result + sep + ellipsis;
result = result + pathSeparator + ellipsis;
}
shortenedPaths[pathIndex] = result;

View File

@@ -21,7 +21,7 @@ export class Lazy<T> {
private _didRun: boolean = false;
private _value?: T;
private _error: any;
private _error: Error | undefined;
constructor(
private readonly executor: () => T,

View File

@@ -49,8 +49,7 @@ export interface IDisposable {
}
export function isDisposable<E extends object>(thing: E): thing is E & IDisposable {
return typeof (<IDisposable><any>thing).dispose === 'function'
&& (<IDisposable><any>thing).dispose.length === 0;
return typeof (<IDisposable>thing).dispose === 'function' && (<IDisposable>thing).dispose.length === 0;
}
export function dispose<T extends IDisposable>(disposable: T): T;
@@ -124,7 +123,7 @@ export class DisposableStore implements IDisposable {
if (!t) {
return t;
}
if ((t as any as DisposableStore) === this) {
if ((t as unknown as DisposableStore) === this) {
throw new Error('Cannot register a disposable on itself!');
}
@@ -158,7 +157,7 @@ export abstract class Disposable implements IDisposable {
}
protected _register<T extends IDisposable>(t: T): T {
if ((t as any as Disposable) === this) {
if ((t as unknown as Disposable) === this) {
throw new Error('Cannot register a disposable on itself!');
}
return this._store.add(t);

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Iterator, IteratorResult, FIN } from 'vs/base/common/iterator';
class Node<E> {
static readonly Undefined = new Node<any>(undefined);
@@ -126,24 +124,12 @@ export class LinkedList<E> {
this._size -= 1;
}
iterator(): Iterator<E> {
let element: { done: false; value: E; };
*[Symbol.iterator](): Iterator<E> {
let node = this._first;
return {
next(): IteratorResult<E> {
if (node === Node.Undefined) {
return FIN;
}
if (!element) {
element = { done: false, value: node.element };
} else {
element.value = node.element;
}
node = node.next;
return element;
}
};
while (node !== Node.Undefined) {
yield node.element;
node = node.next;
}
}
toArray(): E[] {

View File

@@ -5,9 +5,11 @@
import { URI } from 'vs/base/common/uri';
import { CharCode } from 'vs/base/common/charCode';
import { Iterator, IteratorResult, FIN } from './iterator';
import { FIN } from './iterator';
/**
* @deprecated ES6: use `[...SetOrMap.values()]`
*/
export function values<V = any>(set: Set<V>): V[];
export function values<K = any, V = any>(map: Map<K, V>): V[];
export function values<V>(forEachable: { forEach(callback: (value: V, ...more: any[]) => any): void }): V[] {
@@ -16,6 +18,9 @@ export function values<V>(forEachable: { forEach(callback: (value: V, ...more: a
return result;
}
/**
* @deprecated ES6: use `[...map.keys()]`
*/
export function keys<K, V>(map: Map<K, V>): K[] {
const result: K[] = [];
map.forEach((_value, key) => result.push(key));
@@ -51,6 +56,9 @@ export function setToString<K>(set: Set<K>): string {
return `Set(${set.size}) {${entries.join(', ')}}`;
}
/**
* @deprecated ES6: use `...Map.entries()`
*/
export function mapToSerializable(map: Map<string, string>): [string, string][] {
const serializable: [string, string][] = [];
@@ -61,6 +69,9 @@ export function mapToSerializable(map: Map<string, string>): [string, string][]
return serializable;
}
/**
* @deprecated ES6: use `new Map([[key1, value1],[key2, value2]])`
*/
export function serializableToMap(serializable: [string, string][]): Map<string, string> {
const items = new Map<string, string>();

View File

@@ -11,7 +11,7 @@ import { LRUCache } from 'vs/base/common/map';
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize}
*/
export const canNormalize = typeof ((<any>'').normalize) === 'function';
export const canNormalize = typeof (String.prototype as any /* standalone editor compilation */).normalize === 'function';
const nfcCache = new LRUCache<string, string>(10000); // bounded to 10000 elements
export function normalizeNFC(str: string): string {
@@ -46,3 +46,17 @@ function normalize(str: string, form: string, normalizedCache: LRUCache<string,
return res;
}
export const removeAccents: (str: string) => string = (function () {
if (!canNormalize) {
// no ES6 features...
return function (str: string) { return str; };
} else {
// transform into NFD form and remove accents
// see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463
const regex = /[\u0300-\u036f]/g;
return function (str: string) {
return normalizeNFD(str).replace(regex, '');
};
}
})();

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,7 @@ import * as paths from 'vs/base/common/path';
import { Iterator } from 'vs/base/common/iterator';
import { relativePath, joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { mapValues } from 'vs/base/common/collections';
import { PathIterator } from 'vs/base/common/map';
import { PathIterator, values } from 'vs/base/common/map';
export interface IResourceNode<T, C = void> {
readonly uri: URI;
@@ -32,7 +31,7 @@ class Node<T, C> implements IResourceNode<T, C> {
}
get children(): Iterator<Node<T, C>> {
return Iterator.fromArray(mapValues(this._children));
return Iterator.fromArray(values(this._children));
}
@memoize

View File

@@ -5,7 +5,7 @@
import * as extpath from 'vs/base/common/extpath';
import * as paths from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import { URI, originalFSPath as uriOriginalFSPath } from 'vs/base/common/uri';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { isLinux, isWindows } from 'vs/base/common/platform';
@@ -13,6 +13,8 @@ import { CharCode } from 'vs/base/common/charCode';
import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
import { TernarySearchTree } from 'vs/base/common/map';
export const originalFSPath = uriOriginalFSPath;
export function getComparisonKey(resource: URI): string {
return hasToIgnoreCase(resource) ? resource.toString().toLowerCase() : resource.toString();
}
@@ -107,15 +109,7 @@ export function dirname(resource: URI): URI {
* @returns The resulting URI.
*/
export function joinPath(resource: URI, ...pathFragment: string[]): URI {
let joinedPath: string;
if (resource.scheme === Schemas.file) {
joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path;
} else {
joinedPath = paths.posix.join(resource.path || '/', ...pathFragment);
}
return resource.with({
path: joinedPath
});
return URI.joinPaths(resource, ...pathFragment);
}
/**
@@ -139,33 +133,6 @@ export function normalizePath(resource: URI): URI {
});
}
/**
* Returns the fsPath of an URI where the drive letter is not normalized.
* See #56403.
*/
export function originalFSPath(uri: URI): string {
let value: string;
const uriPath = uri.path;
if (uri.authority && uriPath.length > 1 && uri.scheme === Schemas.file) {
// unc path: file://shares/c$/far/boo
value = `//${uri.authority}${uriPath}`;
} else if (
isWindows
&& uriPath.charCodeAt(0) === CharCode.Slash
&& extpath.isWindowsDriveLetter(uriPath.charCodeAt(1))
&& uriPath.charCodeAt(2) === CharCode.Colon
) {
value = uriPath.substr(1);
} else {
// other path
value = uriPath;
}
if (isWindows) {
value = value.replace(/\//g, '\\');
}
return value;
}
/**
* Returns true if the URI path is absolute.
*/

View File

@@ -95,8 +95,8 @@ export interface WriteableStream<T> extends ReadableStream<T> {
end(result?: T | Error): void;
}
export function isReadableStream<T>(obj: any): obj is ReadableStream<T> {
const candidate: ReadableStream<T> = obj;
export function isReadableStream<T>(obj: unknown): obj is ReadableStream<T> {
const candidate = obj as ReadableStream<T>;
return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
}

View File

@@ -14,7 +14,7 @@ export function isFalsyOrWhitespace(str: string | undefined): boolean {
}
/**
* @returns the provided number with the given number of preceding zeros.
* @deprecated ES6: use `String.padStart`
*/
export function pad(n: number, l: number, char: string = '0'): string {
const str = '' + n;
@@ -145,7 +145,7 @@ export function stripWildcards(pattern: string): string {
}
/**
* Determines if haystack starts with needle.
* @deprecated ES6: use `String.startsWith`
*/
export function startsWith(haystack: string, needle: string): boolean {
if (haystack.length < needle.length) {
@@ -166,7 +166,7 @@ export function startsWith(haystack: string, needle: string): boolean {
}
/**
* Determines if haystack ends with needle.
* @deprecated ES6: use `String.endsWith`
*/
export function endsWith(haystack: string, needle: string): boolean {
const diff = haystack.length - needle.length;
@@ -240,7 +240,7 @@ export function regExpFlags(regexp: RegExp): string {
return (regexp.global ? 'g' : '')
+ (regexp.ignoreCase ? 'i' : '')
+ (regexp.multiline ? 'm' : '')
+ ((regexp as any).unicode ? 'u' : '');
+ ((regexp as any /* standalone editor compilation */).unicode ? 'u' : '');
}
/**
@@ -428,43 +428,6 @@ export function commonSuffixLength(a: string, b: string): number {
return len;
}
function substrEquals(a: string, aStart: number, aEnd: number, b: string, bStart: number, bEnd: number): boolean {
while (aStart < aEnd && bStart < bEnd) {
if (a[aStart] !== b[bStart]) {
return false;
}
aStart += 1;
bStart += 1;
}
return true;
}
/**
* Return the overlap between the suffix of `a` and the prefix of `b`.
* For instance `overlap("foobar", "arr, I'm a pirate") === 2`.
*/
export function overlap(a: string, b: string): number {
const aEnd = a.length;
let bEnd = b.length;
let aStart = aEnd - bEnd;
if (aStart === 0) {
return a === b ? aEnd : 0;
} else if (aStart < 0) {
bEnd += aStart;
aStart = 0;
}
while (aStart < aEnd && bEnd > 0) {
if (substrEquals(a, aStart, aEnd, b, 0, bEnd)) {
return bEnd;
}
bEnd -= 1;
aStart += 1;
}
return 0;
}
// --- unicode
// http://en.wikipedia.org/wiki/Surrogate_pair
// Returns the code point starting at a specified index in a string
@@ -852,21 +815,6 @@ export function removeAnsiEscapeCodes(str: string): string {
return str;
}
export const removeAccents: (str: string) => string = (function () {
if (typeof (String.prototype as any).normalize !== 'function') {
// ☹️ no ES6 features...
return function (str: string) { return str; };
} else {
// transform into NFD form and remove accents
// see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463
const regex = /[\u0300-\u036f]/g;
return function (str: string) {
return (str as any).normalize('NFD').replace(regex, '');
};
}
})();
// -- UTF-8 BOM
export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM);

View File

@@ -5,6 +5,8 @@
import { isWindows } from 'vs/base/common/platform';
import { CharCode } from 'vs/base/common/charCode';
import * as paths from 'vs/base/common/path';
import * as extpath from 'vs/base/common/extpath';
const _schemePattern = /^\w[\w\d+.-]*$/;
const _singleSlashStart = /^\//;
@@ -333,6 +335,25 @@ export class URI implements UriComponents {
);
}
/**
* 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.
*/
static joinPaths(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
});
}
// ---- printing/externalize ---------------------------
/**
@@ -672,3 +693,29 @@ function percentDecode(str: string): string {
}
return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
}
// --- utils
export function originalFSPath(uri: URI): string {
let value: string;
const uriPath = uri.path;
if (uri.authority && uriPath.length > 1 && uri.scheme === 'file') {
// unc path: file://shares/c$/far/boo
value = `//${uri.authority}${uriPath}`;
} else if (
isWindows
&& uriPath.charCodeAt(0) === CharCode.Slash
&& extpath.isWindowsDriveLetter(uriPath.charCodeAt(1))
&& uriPath.charCodeAt(2) === CharCode.Colon
) {
value = uriPath.substr(1);
} else {
// other path
value = uriPath;
}
if (isWindows) {
value = value.replace(/\//g, '\\');
}
return value;
}

View File

@@ -3,87 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Represents a UUID as defined by rfc4122.
*/
export interface UUID {
/**
* @returns the canonical representation in sets of hexadecimal numbers separated by dashes.
*/
asHex(): string;
}
class ValueUUID implements UUID {
constructor(public _value: string) {
// empty
}
public asHex(): string {
return this._value;
}
}
class V4UUID extends ValueUUID {
private static readonly _chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
private static readonly _timeHighBits = ['8', '9', 'a', 'b'];
private static _oneOf(array: string[]): string {
return array[Math.floor(array.length * Math.random())];
}
private static _randomHex(): string {
return V4UUID._oneOf(V4UUID._chars);
}
constructor() {
super([
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
'-',
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
'-',
'4',
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
'-',
V4UUID._oneOf(V4UUID._timeHighBits),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
'-',
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
V4UUID._randomHex(),
].join(''));
}
}
export function v4(): UUID {
return new V4UUID();
}
const _UUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -91,18 +10,52 @@ export function isUUID(value: string): boolean {
return _UUIDPattern.test(value);
}
/**
* Parses a UUID that is of the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
* @param value A uuid string.
*/
export function parse(value: string): UUID {
if (!isUUID(value)) {
throw new Error('invalid uuid');
}
return new ValueUUID(value);
// prep-work
const _data = new Uint8Array(16);
const _hex: string[] = [];
for (let i = 0; i < 256; i++) {
_hex.push(i.toString(16).padStart(2, '0'));
}
// todo@joh node nodejs use `crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback
// todo@joh use browser-crypto
const _fillRandomValues = function (bucket: Uint8Array): Uint8Array {
for (let i = 0; i < bucket.length; i++) {
bucket[i] = Math.floor(Math.random() * 256);
}
return bucket;
};
export function generateUuid(): string {
return v4().asHex();
// get data
_fillRandomValues(_data);
// set version bits
_data[6] = (_data[6] & 0x0f) | 0x40;
_data[8] = (_data[8] & 0x3f) | 0x80;
// print as string
let i = 0;
let result = '';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
return result;
}