mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode cfc1ab4c5f816765b91fb7ead3c3427a7c8581a3
This commit is contained in:
@@ -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];
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 || '');
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
637
src/vs/base/common/fuzzyScorer.ts
Normal file
637
src/vs/base/common/fuzzyScorer.ts
Normal 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;
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user