/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as strings from 'vs/base/common/strings'; import { sep } from 'vs/base/common/path'; import { IdleValue } from 'vs/base/common/async'; const intlFileNameCollator: 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 }; }); export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number { const a = one || ''; const b = other || ''; const result = intlFileNameCollator.getValue().collator.compare(a, b); // Using the numeric option in the collator will // make compare(`foo1`, `foo01`) === 0. We must disambiguate. if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && a !== b) { return a < b ? -1 : 1; } return result; } const FileNameMatch = /^(.*?)(\.([^.]*))?$/; 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; } export function compareFileExtensions(one: string | null, other: string | null): number { const [oneName, oneExtension] = extractNameAndExtension(one); const [otherName, otherExtension] = extractNameAndExtension(other); let result = intlFileNameCollator.getValue().collator.compare(oneExtension, otherExtension); if (result === 0) { // Using the numeric option in the collator will // make compare(`foo1`, `foo01`) === 0. We must disambiguate. if (intlFileNameCollator.getValue().collatorIsNumeric && oneExtension !== otherExtension) { return oneExtension < otherExtension ? -1 : 1; } // Extensions are equal, compare filenames result = intlFileNameCollator.getValue().collator.compare(oneName, otherName); if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && oneName !== otherName) { return oneName < otherName ? -1 : 1; } } return result; } function extractNameAndExtension(str?: string | null): [string, string] { const match = str ? FileNameMatch.exec(str) as Array : ([] as Array); return [(match && match[1]) || '', (match && match[3]) || '']; } 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 = strings.endsWith(elementAName, lookFor); const elementBSuffixMatch = strings.endsWith(elementBName, 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 = strings.startsWith(elementAName, lookFor); const elementBPrefixMatch = strings.startsWith(elementBName, 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; }