mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-07 09:35:41 -05:00
* Revert "Revert "Merge from vscode merge-base (#22769)" (#22779)"
This reverts commit 47a1745180.
* Fix notebook download task
* Remove done call from extensions-ci
376 lines
13 KiB
TypeScript
376 lines
13 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { IdleValue } from 'vs/base/common/async';
|
|
import { sep } from 'vs/base/common/path';
|
|
|
|
// When comparing large numbers of strings it's better for performance to create an
|
|
// Intl.Collator object and use the function provided by its compare property
|
|
// than it is to use String.prototype.localeCompare()
|
|
|
|
// A collator with numeric sorting enabled, and no sensitivity to case, accents or diacritics.
|
|
const intlFileNameCollatorBaseNumeric: IdleValue<{ collator: Intl.Collator; collatorIsNumeric: boolean }> = new IdleValue(() => {
|
|
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
|
return {
|
|
collator: collator,
|
|
collatorIsNumeric: collator.resolvedOptions().numeric
|
|
};
|
|
});
|
|
|
|
// A collator with numeric sorting enabled.
|
|
const intlFileNameCollatorNumeric: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
|
|
const collator = new Intl.Collator(undefined, { numeric: true });
|
|
return {
|
|
collator: collator
|
|
};
|
|
});
|
|
|
|
// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
|
|
const intlFileNameCollatorNumericCaseInsensitive: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
|
|
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' });
|
|
return {
|
|
collator: collator
|
|
};
|
|
});
|
|
|
|
/** Compares filenames without distinguishing the name from the extension. Disambiguates by unicode comparison. */
|
|
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
|
const a = one || '';
|
|
const b = other || '';
|
|
const result = intlFileNameCollatorBaseNumeric.value.collator.compare(a, b);
|
|
|
|
// Using the numeric option will make compare(`foo1`, `foo01`) === 0. Disambiguate.
|
|
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && a !== b) {
|
|
return a < b ? -1 : 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/** Compares full filenames without grouping by case. */
|
|
export function compareFileNamesDefault(one: string | null, other: string | null): number {
|
|
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
|
one = one || '';
|
|
other = other || '';
|
|
|
|
return compareAndDisambiguateByLength(collatorNumeric, one, other);
|
|
}
|
|
|
|
/** Compares full filenames grouping uppercase names before lowercase. */
|
|
export function compareFileNamesUpper(one: string | null, other: string | null) {
|
|
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
|
one = one || '';
|
|
other = other || '';
|
|
|
|
return compareCaseUpperFirst(one, other) || compareAndDisambiguateByLength(collatorNumeric, one, other);
|
|
}
|
|
|
|
/** Compares full filenames grouping lowercase names before uppercase. */
|
|
export function compareFileNamesLower(one: string | null, other: string | null) {
|
|
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
|
one = one || '';
|
|
other = other || '';
|
|
|
|
return compareCaseLowerFirst(one, other) || compareAndDisambiguateByLength(collatorNumeric, one, other);
|
|
}
|
|
|
|
/** Compares full filenames by unicode value. */
|
|
export function compareFileNamesUnicode(one: string | null, other: string | null) {
|
|
one = one || '';
|
|
other = other || '';
|
|
|
|
if (one === other) {
|
|
return 0;
|
|
}
|
|
|
|
return one < other ? -1 : 1;
|
|
}
|
|
|
|
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
|
if (!caseSensitive) {
|
|
one = one && one.toLowerCase();
|
|
other = other && other.toLowerCase();
|
|
}
|
|
|
|
const [oneName, oneExtension] = extractNameAndExtension(one);
|
|
const [otherName, otherExtension] = extractNameAndExtension(other);
|
|
|
|
if (oneName !== otherName) {
|
|
return oneName < otherName ? -1 : 1;
|
|
}
|
|
|
|
if (oneExtension === otherExtension) {
|
|
return 0;
|
|
}
|
|
|
|
return oneExtension < otherExtension ? -1 : 1;
|
|
}
|
|
|
|
/** Compares filenames by extension, then by name. Disambiguates by unicode comparison. */
|
|
export function compareFileExtensions(one: string | null, other: string | null): number {
|
|
const [oneName, oneExtension] = extractNameAndExtension(one);
|
|
const [otherName, otherExtension] = extractNameAndExtension(other);
|
|
|
|
let result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneExtension, otherExtension);
|
|
|
|
if (result === 0) {
|
|
// Using the numeric option will make compare(`foo1`, `foo01`) === 0. Disambiguate.
|
|
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && oneExtension !== otherExtension) {
|
|
return oneExtension < otherExtension ? -1 : 1;
|
|
}
|
|
|
|
// Extensions are equal, compare filenames
|
|
result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneName, otherName);
|
|
|
|
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && oneName !== otherName) {
|
|
return oneName < otherName ? -1 : 1;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/** Compares filenames by extension, then by full filename. Mixes uppercase and lowercase names together. */
|
|
export function compareFileExtensionsDefault(one: string | null, other: string | null): number {
|
|
one = one || '';
|
|
other = other || '';
|
|
const oneExtension = extractExtension(one);
|
|
const otherExtension = extractExtension(other);
|
|
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
|
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsensitive.value.collator;
|
|
|
|
return compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension) ||
|
|
compareAndDisambiguateByLength(collatorNumeric, one, other);
|
|
}
|
|
|
|
/** Compares filenames by extension, then case, then full filename. Groups uppercase names before lowercase. */
|
|
export function compareFileExtensionsUpper(one: string | null, other: string | null): number {
|
|
one = one || '';
|
|
other = other || '';
|
|
const oneExtension = extractExtension(one);
|
|
const otherExtension = extractExtension(other);
|
|
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
|
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsensitive.value.collator;
|
|
|
|
return compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension) ||
|
|
compareCaseUpperFirst(one, other) ||
|
|
compareAndDisambiguateByLength(collatorNumeric, one, other);
|
|
}
|
|
|
|
/** Compares filenames by extension, then case, then full filename. Groups lowercase names before uppercase. */
|
|
export function compareFileExtensionsLower(one: string | null, other: string | null): number {
|
|
one = one || '';
|
|
other = other || '';
|
|
const oneExtension = extractExtension(one);
|
|
const otherExtension = extractExtension(other);
|
|
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
|
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsensitive.value.collator;
|
|
|
|
return compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension) ||
|
|
compareCaseLowerFirst(one, other) ||
|
|
compareAndDisambiguateByLength(collatorNumeric, one, other);
|
|
}
|
|
|
|
/** Compares filenames by case-insensitive extension unicode value, then by full filename unicode value. */
|
|
export function compareFileExtensionsUnicode(one: string | null, other: string | null) {
|
|
one = one || '';
|
|
other = other || '';
|
|
const oneExtension = extractExtension(one).toLowerCase();
|
|
const otherExtension = extractExtension(other).toLowerCase();
|
|
|
|
// Check for extension differences
|
|
if (oneExtension !== otherExtension) {
|
|
return oneExtension < otherExtension ? -1 : 1;
|
|
}
|
|
|
|
// Check for full filename differences.
|
|
if (one !== other) {
|
|
return one < other ? -1 : 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
|
|
|
|
/** Extracts the name and extension from a full filename, with optional special handling for dotfiles */
|
|
function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] {
|
|
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
|
|
|
|
let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || ''];
|
|
|
|
// if the dotfilesAsNames option is selected, treat an empty filename with an extension
|
|
// or a filename that starts with a dot, as a dotfile name
|
|
if (dotfilesAsNames && (!result[0] && result[1] || result[0] && result[0].charAt(0) === '.')) {
|
|
result = [result[0] + '.' + result[1], ''];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/** Extracts the extension from a full filename. Treats dotfiles as names, not extensions. */
|
|
function extractExtension(str?: string | null): string {
|
|
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
|
|
|
|
return (match && match[1] && match[1].charAt(0) !== '.' && match[3]) || '';
|
|
}
|
|
|
|
function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
|
|
// Check for differences
|
|
const result = collator.compare(one, other);
|
|
if (result !== 0) {
|
|
return result;
|
|
}
|
|
|
|
// In a numeric comparison, `foo1` and `foo01` will compare as equivalent.
|
|
// Disambiguate by sorting the shorter string first.
|
|
if (one.length !== other.length) {
|
|
return one.length < other.length ? -1 : 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @returns `true` if the string is starts with a lowercase letter. Otherwise, `false`. */
|
|
function startsWithLower(string: string) {
|
|
const character = string.charAt(0);
|
|
|
|
return (character.toLocaleUpperCase() !== character) ? true : false;
|
|
}
|
|
|
|
/** @returns `true` if the string starts with an uppercase letter. Otherwise, `false`. */
|
|
function startsWithUpper(string: string) {
|
|
const character = string.charAt(0);
|
|
|
|
return (character.toLocaleLowerCase() !== character) ? true : false;
|
|
}
|
|
|
|
/**
|
|
* Compares the case of the provided strings - lowercase before uppercase
|
|
*
|
|
* @returns
|
|
* ```text
|
|
* -1 if one is lowercase and other is uppercase
|
|
* 1 if one is uppercase and other is lowercase
|
|
* 0 otherwise
|
|
* ```
|
|
*/
|
|
function compareCaseLowerFirst(one: string, other: string): number {
|
|
if (startsWithLower(one) && startsWithUpper(other)) {
|
|
return -1;
|
|
}
|
|
return (startsWithUpper(one) && startsWithLower(other)) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* Compares the case of the provided strings - uppercase before lowercase
|
|
*
|
|
* @returns
|
|
* ```text
|
|
* -1 if one is uppercase and other is lowercase
|
|
* 1 if one is lowercase and other is uppercase
|
|
* 0 otherwise
|
|
* ```
|
|
*/
|
|
function compareCaseUpperFirst(one: string, other: string): number {
|
|
if (startsWithUpper(one) && startsWithLower(other)) {
|
|
return -1;
|
|
}
|
|
return (startsWithLower(one) && startsWithUpper(other)) ? 1 : 0;
|
|
}
|
|
|
|
function comparePathComponents(one: string, other: string, caseSensitive = false): number {
|
|
if (!caseSensitive) {
|
|
one = one && one.toLowerCase();
|
|
other = other && other.toLowerCase();
|
|
}
|
|
|
|
if (one === other) {
|
|
return 0;
|
|
}
|
|
|
|
return one < other ? -1 : 1;
|
|
}
|
|
|
|
export function comparePaths(one: string, other: string, caseSensitive = false): number {
|
|
const oneParts = one.split(sep);
|
|
const otherParts = other.split(sep);
|
|
|
|
const lastOne = oneParts.length - 1;
|
|
const lastOther = otherParts.length - 1;
|
|
let endOne: boolean, endOther: boolean;
|
|
|
|
for (let i = 0; ; i++) {
|
|
endOne = lastOne === i;
|
|
endOther = lastOther === i;
|
|
|
|
if (endOne && endOther) {
|
|
return compareFileNames(oneParts[i], otherParts[i], caseSensitive);
|
|
} else if (endOne) {
|
|
return -1;
|
|
} else if (endOther) {
|
|
return 1;
|
|
}
|
|
|
|
const result = comparePathComponents(oneParts[i], otherParts[i], caseSensitive);
|
|
|
|
if (result !== 0) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function compareAnything(one: string, other: string, lookFor: string): number {
|
|
const elementAName = one.toLowerCase();
|
|
const elementBName = other.toLowerCase();
|
|
|
|
// Sort prefix matches over non prefix matches
|
|
const prefixCompare = compareByPrefix(one, other, lookFor);
|
|
if (prefixCompare) {
|
|
return prefixCompare;
|
|
}
|
|
|
|
// Sort suffix matches over non suffix matches
|
|
const elementASuffixMatch = elementAName.endsWith(lookFor);
|
|
const elementBSuffixMatch = elementBName.endsWith(lookFor);
|
|
if (elementASuffixMatch !== elementBSuffixMatch) {
|
|
return elementASuffixMatch ? -1 : 1;
|
|
}
|
|
|
|
// Understand file names
|
|
const r = compareFileNames(elementAName, elementBName);
|
|
if (r !== 0) {
|
|
return r;
|
|
}
|
|
|
|
// Compare by name
|
|
return elementAName.localeCompare(elementBName);
|
|
}
|
|
|
|
export function compareByPrefix(one: string, other: string, lookFor: string): number {
|
|
const elementAName = one.toLowerCase();
|
|
const elementBName = other.toLowerCase();
|
|
|
|
// Sort prefix matches over non prefix matches
|
|
const elementAPrefixMatch = elementAName.startsWith(lookFor);
|
|
const elementBPrefixMatch = elementBName.startsWith(lookFor);
|
|
if (elementAPrefixMatch !== elementBPrefixMatch) {
|
|
return elementAPrefixMatch ? -1 : 1;
|
|
}
|
|
|
|
// Same prefix: Sort shorter matches to the top to have those on top that match more precisely
|
|
else if (elementAPrefixMatch && elementBPrefixMatch) {
|
|
if (elementAName.length < elementBName.length) {
|
|
return -1;
|
|
}
|
|
|
|
if (elementAName.length > elementBName.length) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|