diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index a25e2bb9c16ed..7b45ce5abb6dc 100644 --- a/src/vs/base/common/comparers.ts +++ b/src/vs/base/common/comparers.ts @@ -33,8 +33,7 @@ const intlFileNameCollatorNumericCaseInsenstive: IdleValue<{ collator: Intl.Coll 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 || ''; @@ -49,36 +48,16 @@ export function compareFileNames(one: string | null, other: string | null, caseS return result; } -/** Compares filenames by name then extension, sorting numbers numerically instead of alphabetically. */ -export function compareFileNamesNumeric(one: string | null, other: string | null): number { - const [oneName, oneExtension] = extractNameAndExtension(one, true); - const [otherName, otherExtension] = extractNameAndExtension(other, true); +/** Compares filenames without distinguishing the name from the extension. Disambiguates by length, not unicode comparison. */ +export function compareFileNamesDefault(one: string | null, other: string | null): number { const collatorNumeric = intlFileNameCollatorNumeric.value.collator; - const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator; - let result; - - // Check for name differences, comparing numbers numerically instead of alphabetically. - result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName); - if (result !== 0) { - return result; - } + one = one || ''; + other = other || ''; - // Check for case insensitive extension differences, comparing numbers numerically instead of alphabetically. - result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension); - if (result !== 0) { - return result; - } - - // Disambiguate the extension case if needed. - if (oneExtension !== otherExtension) { - return collatorNumeric.compare(oneExtension, otherExtension); - } - - return 0; + // Compare the entire filename - both name and extension - and disambiguate by length if needed + return compareAndDisambiguateByLength(collatorNumeric, one, other); } -const FileNameMatch = /^(.*?)(\.([^.]*))?$/; - export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number { if (!caseSensitive) { one = one && one.toLowerCase(); @@ -123,10 +102,12 @@ export function compareFileExtensions(one: string | null, other: string | null): return result; } -/** Compares filenames by extenson, then by name. Sorts numbers numerically, not alphabetically. */ -export function compareFileExtensionsNumeric(one: string | null, other: string | null): number { - const [oneName, oneExtension] = extractNameAndExtension(one, true); - const [otherName, otherExtension] = extractNameAndExtension(other, true); +/** Compares filenames by extenson, then by full filename */ +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 = intlFileNameCollatorNumericCaseInsenstive.value.collator; let result; @@ -137,20 +118,12 @@ export function compareFileExtensionsNumeric(one: string | null, other: string | return result; } - // Compare names. - result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName); - if (result !== 0) { - return result; - } - - // Disambiguate extension case if needed. - if (oneExtension !== otherExtension) { - return collatorNumeric.compare(oneExtension, otherExtension); - } - - return 0; + // Compare full filenames + return compareAndDisambiguateByLength(collatorNumeric, one, other); } +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 : ([] as Array); @@ -166,6 +139,13 @@ function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): 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 : ([] as Array); + + return (match && match[1] && match[1].charAt(0) !== '.' && match[3]) || ''; +} + function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) { // Check for differences let result = collator.compare(one, other); diff --git a/src/vs/base/test/browser/comparers.test.ts b/src/vs/base/test/browser/comparers.test.ts index 90dff8c283563..4c40ea2a065d7 100644 --- a/src/vs/base/test/browser/comparers.test.ts +++ b/src/vs/base/test/browser/comparers.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareFileNames, compareFileExtensions, compareFileNamesNumeric, compareFileExtensionsNumeric } from 'vs/base/common/comparers'; +import { compareFileNames, compareFileExtensions, compareFileNamesDefault, compareFileExtensionsDefault } from 'vs/base/common/comparers'; import * as assert from 'assert'; const compareLocale = (a: string, b: string) => a.localeCompare(b); @@ -15,7 +15,7 @@ suite('Comparers', () => { test('compareFileNames', () => { // - // Comparisons with the same results as compareFileNamesNumeric + // Comparisons with the same results as compareFileNamesDefault // // name-only comparisons @@ -28,6 +28,7 @@ suite('Comparers', () => { // name plus extension comparisons assert(compareFileNames('bbb.aaa', 'aaa.bbb') > 0, 'files with extensions are compared first by filename'); + assert(compareFileNames('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole name all at once by locale'); // dotfile comparisons assert(compareFileNames('.abc', '.abc') === 0, 'equal dotfile names should be equal'); @@ -52,7 +53,7 @@ suite('Comparers', () => { assert(compareFileNames('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number'); // - // Comparisons with different results than compareFileNamesNumeric + // Comparisons with different results than compareFileNamesDefault // // name-only comparisons @@ -61,9 +62,6 @@ suite('Comparers', () => { assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNames), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order'); assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNames), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents do not sort in locale order'); - // name plus extension comparisons - assert(compareFileNames('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole name all at once by locale'); - // numeric comparisons assert(compareFileNames('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order'); assert(compareFileNames('abc.txt1', 'abc.txt01') > 0, 'same name plus extensions with equal numbers sort in unicode order'); @@ -75,7 +73,7 @@ suite('Comparers', () => { test('compareFileExtensions', () => { // - // Comparisons with the same results as compareFileExtensionsNumeric + // Comparisons with the same results as compareFileExtensionsDefault // // name-only comparisons @@ -118,12 +116,8 @@ suite('Comparers', () => { assert(compareFileExtensions('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared'); assert(compareFileExtensions('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically'); - // Same extension comparison that has the same result as compareFileExtensionsNumeric, but a different result than compareFileNames - // This is an edge case caused by compareFileNames comparing the whole name all at once instead of the name and then the extension. - assert(compareFileExtensions('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order'); - // - // Comparisons with different results from compareFileExtensionsNumeric + // Comparisons with different results from compareFileExtensionsDefault // // name-only comparisions @@ -135,6 +129,7 @@ suite('Comparers', () => { // name plus extension comparisons assert(compareFileExtensions('a.MD', 'a.md') !== compareLocale('MD', 'md'), 'case differences in extensions do not sort by locale'); assert(compareFileExtensions('a.md', 'A.md') !== compareLocale('a', 'A'), 'case differences in names do not sort by locale'); + assert(compareFileExtensions('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order'); // dotfile comparisons assert(compareFileExtensions('.env', '.aaa.env') < 0, 'a dotfile with an extension is treated as a name plus an extension - equal extensions'); @@ -152,145 +147,139 @@ suite('Comparers', () => { }); - test('compareFileNamesNumeric', () => { + test('compareFileNamesDefault', () => { // // Comparisons with the same results as compareFileNames // // name-only comparisons - assert(compareFileNamesNumeric(null, null) === 0, 'null should be equal'); - assert(compareFileNamesNumeric(null, 'abc') < 0, 'null should be come before real values'); - assert(compareFileNamesNumeric('', '') === 0, 'empty should be equal'); - assert(compareFileNamesNumeric('abc', 'abc') === 0, 'equal names should be equal'); - assert(compareFileNamesNumeric('z', 'A') > 0, 'z comes is after A regardless of case'); - assert(compareFileNamesNumeric('Z', 'a') > 0, 'Z comes after a regardless of case'); + assert(compareFileNamesDefault(null, null) === 0, 'null should be equal'); + assert(compareFileNamesDefault(null, 'abc') < 0, 'null should be come before real values'); + assert(compareFileNamesDefault('', '') === 0, 'empty should be equal'); + assert(compareFileNamesDefault('abc', 'abc') === 0, 'equal names should be equal'); + assert(compareFileNamesDefault('z', 'A') > 0, 'z comes is after A regardless of case'); + assert(compareFileNamesDefault('Z', 'a') > 0, 'Z comes after a regardless of case'); // name plus extension comparisons - assert(compareFileNamesNumeric('file.ext', 'file.ext') === 0, 'equal full names should be equal'); - assert(compareFileNamesNumeric('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared'); - assert(compareFileNamesNumeric('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions'); - assert(compareFileNamesNumeric('bbb.aaa', 'aaa.bbb') > 0, 'files should be compared by names even if extensions compare differently'); + assert(compareFileNamesDefault('file.ext', 'file.ext') === 0, 'equal full names should be equal'); + assert(compareFileNamesDefault('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared'); + assert(compareFileNamesDefault('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions'); + assert(compareFileNamesDefault('bbb.aaa', 'aaa.bbb') > 0, 'files should be compared by names even if extensions compare differently'); + assert(compareFileNamesDefault('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole filename in locale order'); // dotfile comparisons - assert(compareFileNamesNumeric('.abc', '.abc') === 0, 'equal dotfile names should be equal'); - assert(compareFileNamesNumeric('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly'); - assert(compareFileNamesNumeric('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots'); - assert(compareFileNamesNumeric('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first'); - assert(compareFileNamesNumeric('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot'); + assert(compareFileNamesDefault('.abc', '.abc') === 0, 'equal dotfile names should be equal'); + assert(compareFileNamesDefault('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly'); + assert(compareFileNamesDefault('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots'); + assert(compareFileNamesDefault('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first'); + assert(compareFileNamesDefault('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot'); // dotfile vs non-dotfile comparisons - assert(compareFileNamesNumeric(null, '.abc') < 0, 'null should come before dotfiles'); - assert(compareFileNamesNumeric('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions'); - assert(compareFileNamesNumeric('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions'); - assert(compareFileNamesNumeric('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files'); - assert(compareFileNamesNumeric('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files'); + assert(compareFileNamesDefault(null, '.abc') < 0, 'null should come before dotfiles'); + assert(compareFileNamesDefault('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions'); + assert(compareFileNamesDefault('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions'); + assert(compareFileNamesDefault('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files'); + assert(compareFileNamesDefault('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files'); // numeric comparisons - assert(compareFileNamesNumeric('1', '1') === 0, 'numerically equal full names should be equal'); - assert(compareFileNamesNumeric('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal'); - assert(compareFileNamesNumeric('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order'); - assert(compareFileNamesNumeric('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long'); - assert(compareFileNamesNumeric('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically'); - assert(compareFileNamesNumeric('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number'); + assert(compareFileNamesDefault('1', '1') === 0, 'numerically equal full names should be equal'); + assert(compareFileNamesDefault('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal'); + assert(compareFileNamesDefault('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order'); + assert(compareFileNamesDefault('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long'); + assert(compareFileNamesDefault('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically'); + assert(compareFileNamesDefault('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number'); // // Comparisons with different results than compareFileNames // // name-only comparisons - assert(compareFileNamesNumeric('a', 'A') === compareLocale('a', 'A'), 'the same letter sorts by locale'); - assert(compareFileNamesNumeric('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter sorts by locale'); - assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesNumeric), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order'); - assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNamesNumeric), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents sort in locale order'); - - // name plus extensions comparisons - assert(compareFileNamesNumeric('aggregate.go', 'aggregate_repo.go') < 0, 'compares the name first, then the extension'); + assert(compareFileNamesDefault('a', 'A') === compareLocale('a', 'A'), 'the same letter sorts by locale'); + assert(compareFileNamesDefault('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter sorts by locale'); + assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order'); + assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNamesDefault), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents sort in locale order'); // numeric comparisons - assert(compareFileNamesNumeric('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest number first'); - assert(compareFileNamesNumeric('abc.txt1', 'abc.txt01') < 0, 'same name plus extensions with equal numbers sort shortest number first'); - assert(compareFileNamesNumeric('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale'); + assert(compareFileNamesDefault('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest number first'); + assert(compareFileNamesDefault('abc.txt1', 'abc.txt01') < 0, 'same name plus extensions with equal numbers sort shortest number first'); + assert(compareFileNamesDefault('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale'); }); - test('compareFileExtensionsNumeric', () => { + test('compareFileExtensionsDefault', () => { // // Comparisons with the same result as compareFileExtensions // // name-only comparisons - assert(compareFileExtensionsNumeric(null, null) === 0, 'null should be equal'); - assert(compareFileExtensionsNumeric(null, 'abc') < 0, 'null should come before real files without extensions'); - assert(compareFileExtensionsNumeric('', '') === 0, 'empty should be equal'); - assert(compareFileExtensionsNumeric('abc', 'abc') === 0, 'equal names should be equal'); - assert(compareFileExtensionsNumeric('z', 'A') > 0, 'z comes after A'); - assert(compareFileExtensionsNumeric('Z', 'a') > 0, 'Z comes after a'); + assert(compareFileExtensionsDefault(null, null) === 0, 'null should be equal'); + assert(compareFileExtensionsDefault(null, 'abc') < 0, 'null should come before real files without extensions'); + assert(compareFileExtensionsDefault('', '') === 0, 'empty should be equal'); + assert(compareFileExtensionsDefault('abc', 'abc') === 0, 'equal names should be equal'); + assert(compareFileExtensionsDefault('z', 'A') > 0, 'z comes after A'); + assert(compareFileExtensionsDefault('Z', 'a') > 0, 'Z comes after a'); // name plus extension comparisons - assert(compareFileExtensionsNumeric('file.ext', 'file.ext') === 0, 'equal full filenames should be equal'); - assert(compareFileExtensionsNumeric('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared'); - assert(compareFileExtensionsNumeric('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions'); - assert(compareFileExtensionsNumeric('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extension first'); - assert(compareFileExtensionsNumeric('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names'); - assert(compareFileExtensionsNumeric('agg.go', 'agg_repo.go') < 0, 'shorter names short before longer names even when the longer name contains an underscore'); - assert(compareFileExtensionsNumeric('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name'); + assert(compareFileExtensionsDefault('file.ext', 'file.ext') === 0, 'equal full filenames should be equal'); + assert(compareFileExtensionsDefault('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared'); + assert(compareFileExtensionsDefault('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions'); + assert(compareFileExtensionsDefault('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extension first'); + assert(compareFileExtensionsDefault('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names'); + assert(compareFileExtensionsDefault('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name'); // dotfile comparisons - assert(compareFileExtensionsNumeric('.abc', '.abc') === 0, 'equal dotfiles should be equal'); - assert(compareFileExtensionsNumeric('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case'); + assert(compareFileExtensionsDefault('.abc', '.abc') === 0, 'equal dotfiles should be equal'); + assert(compareFileExtensionsDefault('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case'); // dotfile vs non-dotfile comparisons - assert(compareFileExtensionsNumeric(null, '.abc') < 0, 'null should come before dotfiles'); - assert(compareFileExtensionsNumeric('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions'); - assert(compareFileExtensionsNumeric('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files'); + assert(compareFileExtensionsDefault(null, '.abc') < 0, 'null should come before dotfiles'); + assert(compareFileExtensionsDefault('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions'); + assert(compareFileExtensionsDefault('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files'); // numeric comparisons - assert(compareFileExtensionsNumeric('1', '1') === 0, 'numerically equal full names should be equal'); - assert(compareFileExtensionsNumeric('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal'); - assert(compareFileExtensionsNumeric('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order'); - assert(compareFileExtensionsNumeric('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order'); - assert(compareFileExtensionsNumeric('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically'); - assert(compareFileExtensionsNumeric('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number'); - assert(compareFileExtensionsNumeric('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order'); - assert(compareFileExtensionsNumeric('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal'); - assert(compareFileExtensionsNumeric('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order'); - assert(compareFileExtensionsNumeric('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long'); - assert(compareFileExtensionsNumeric('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared'); - assert(compareFileExtensionsNumeric('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically'); - - // Same extension comparison that has the same result as compareFileExtensions, but a different result than compareFileNames - // This is an edge case caused by compareFileNames comparing the whole name all at once instead of the name and then the extension. - assert(compareFileExtensionsNumeric('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order'); + assert(compareFileExtensionsDefault('1', '1') === 0, 'numerically equal full names should be equal'); + assert(compareFileExtensionsDefault('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal'); + assert(compareFileExtensionsDefault('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order'); + assert(compareFileExtensionsDefault('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order'); + assert(compareFileExtensionsDefault('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically'); + assert(compareFileExtensionsDefault('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number'); + assert(compareFileExtensionsDefault('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order'); + assert(compareFileExtensionsDefault('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal'); + assert(compareFileExtensionsDefault('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order'); + assert(compareFileExtensionsDefault('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long'); + assert(compareFileExtensionsDefault('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared'); + assert(compareFileExtensionsDefault('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically'); // // Comparisons with different results than compareFileExtensions // // name-only comparisons - assert(compareFileExtensionsNumeric('a', 'A') === compareLocale('a', 'A'), 'the same letter of different case sorts by locale'); - assert(compareFileExtensionsNumeric('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter of different case sorts by locale'); - assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsNumeric), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order'); - assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensionsNumeric), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents sort in locale order'); + assert(compareFileExtensionsDefault('a', 'A') === compareLocale('a', 'A'), 'the same letter of different case sorts by locale'); + assert(compareFileExtensionsDefault('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter of different case sorts by locale'); + assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order'); + assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensionsDefault), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents sort in locale order'); // name plus extension comparisons - assert(compareFileExtensionsNumeric('a.MD', 'a.md') === compareLocale('MD', 'md'), 'case differences in extensions sort by locale'); - assert(compareFileExtensionsNumeric('a.md', 'A.md') === compareLocale('a', 'A'), 'case differences in names sort by locale'); + assert(compareFileExtensionsDefault('a.MD', 'a.md') === compareLocale('MD', 'md'), 'case differences in extensions sort by locale'); + assert(compareFileExtensionsDefault('a.md', 'A.md') === compareLocale('a', 'A'), 'case differences in names sort by locale'); + assert(compareFileExtensionsDefault('aggregate.go', 'aggregate_repo.go') > 0, 'names with the same extension sort in full filename locale order'); // dotfile comparisons - assert(compareFileExtensionsNumeric('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots'); - assert(compareFileExtensionsNumeric('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first'); + assert(compareFileExtensionsDefault('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots'); + assert(compareFileExtensionsDefault('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first'); // dotfile vs non-dotfile comparisons - assert(compareFileExtensionsNumeric('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions'); - assert(compareFileExtensionsNumeric('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files'); + assert(compareFileExtensionsDefault('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions'); + assert(compareFileExtensionsDefault('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files'); // numeric comparisons - assert(compareFileExtensionsNumeric('abc.txt01', 'abc.txt1') > 0, 'extensions with equal numbers should be in shortest-first order'); - assert(compareFileExtensionsNumeric('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale'); - assert(compareFileExtensionsNumeric('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest string first'); - assert(compareFileExtensionsNumeric('txt.abc01', 'txt.abc1') > 0, 'extensions with equivalent numbers sort shortest extension first'); + assert(compareFileExtensionsDefault('abc.txt01', 'abc.txt1') > 0, 'extensions with equal numbers should be in shortest-first order'); + assert(compareFileExtensionsDefault('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale'); + assert(compareFileExtensionsDefault('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest string first'); + assert(compareFileExtensionsDefault('txt.abc01', 'txt.abc1') > 0, 'extensions with equivalent numbers sort shortest extension first'); }); }); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 08a100254f841..2d940ea198a29 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -29,7 +29,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { equals, deepClone } from 'vs/base/common/objects'; import * as path from 'vs/base/common/path'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; -import { compareFileExtensionsNumeric, compareFileNamesNumeric } from 'vs/base/common/comparers'; +import { compareFileNamesDefault, compareFileExtensionsDefault } from 'vs/base/common/comparers'; import { fillResourceDataTransfers, CodeDataTransfers, extractResources, containsDragType } from 'vs/workbench/browser/dnd'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; @@ -665,7 +665,7 @@ export class FileSorter implements ITreeSorter { } if (statA.isDirectory && statB.isDirectory) { - return compareFileNamesNumeric(statA.name, statB.name); + return compareFileNamesDefault(statA.name, statB.name); } break; @@ -699,17 +699,17 @@ export class FileSorter implements ITreeSorter { // Sort Files switch (sortOrder) { case 'type': - return compareFileExtensionsNumeric(statA.name, statB.name); + return compareFileExtensionsDefault(statA.name, statB.name); case 'modified': if (statA.mtime !== statB.mtime) { return (statA.mtime && statB.mtime && statA.mtime < statB.mtime) ? 1 : -1; } - return compareFileNamesNumeric(statA.name, statB.name); + return compareFileNamesDefault(statA.name, statB.name); default: /* 'default', 'mixed', 'filesFirst' */ - return compareFileNamesNumeric(statA.name, statB.name); + return compareFileNamesDefault(statA.name, statB.name); } } }