comparers.ts 5.4 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

B
Benjamin Pasero 已提交
6
import * as strings from 'vs/base/common/strings';
7
import { sep } from 'vs/base/common/path';
8
import { IdleValue } from 'vs/base/common/async';
E
Erich Gamma 已提交
9

10 11 12 13 14 15 16
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
	};
});
17

18
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
19 20 21 22 23 24 25 26
	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;
27 28
	}

29
	return result;
30 31
}

32
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
33

34
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
J
Joao Moreno 已提交
35 36 37 38 39 40 41
	if (!caseSensitive) {
		one = one && one.toLowerCase();
		other = other && other.toLowerCase();
	}

	const [oneName, oneExtension] = extractNameAndExtension(one);
	const [otherName, otherExtension] = extractNameAndExtension(other);
42 43 44 45 46 47 48 49 50 51

	if (oneName !== otherName) {
		return oneName < otherName ? -1 : 1;
	}

	if (oneExtension === otherExtension) {
		return 0;
	}

	return oneExtension < otherExtension ? -1 : 1;
E
Erich Gamma 已提交
52 53
}

54
export function compareFileExtensions(one: string | null, other: string | null): number {
55 56
	const [oneName, oneExtension] = extractNameAndExtension(one);
	const [otherName, otherExtension] = extractNameAndExtension(other);
57

58
	let result = intlFileNameCollator.getValue().collator.compare(oneExtension, otherExtension);
59

60 61 62 63 64
	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;
65 66
		}

67 68
		// Extensions are equal, compare filenames
		result = intlFileNameCollator.getValue().collator.compare(oneName, otherName);
69

70 71 72
		if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && oneName !== otherName) {
			return oneName < otherName ? -1 : 1;
		}
73 74
	}

75
	return result;
76 77
}

78
function extractNameAndExtension(str?: string | null): [string, string] {
79
	const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
80 81 82 83

	return [(match && match[1]) || '', (match && match[3]) || ''];
}

J
Joao Moreno 已提交
84 85 86 87 88 89 90 91 92 93 94 95 96 97
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 {
B
Benjamin Pasero 已提交
98 99
	const oneParts = one.split(sep);
	const otherParts = other.split(sep);
J
Joao Moreno 已提交
100 101 102

	const lastOne = oneParts.length - 1;
	const lastOther = otherParts.length - 1;
J
Joao Moreno 已提交
103
	let endOne: boolean, endOther: boolean;
J
Joao Moreno 已提交
104 105 106 107 108 109

	for (let i = 0; ; i++) {
		endOne = lastOne === i;
		endOther = lastOther === i;

		if (endOne && endOther) {
J
Joao Moreno 已提交
110
			return compareFileNames(oneParts[i], otherParts[i], caseSensitive);
J
Joao Moreno 已提交
111 112 113 114
		} else if (endOne) {
			return -1;
		} else if (endOther) {
			return 1;
J
Joao Moreno 已提交
115 116 117 118 119 120
		}

		const result = comparePathComponents(oneParts[i], otherParts[i], caseSensitive);

		if (result !== 0) {
			return result;
J
Joao Moreno 已提交
121 122 123 124
		}
	}
}

E
Erich Gamma 已提交
125
export function compareAnything(one: string, other: string, lookFor: string): number {
126 127
	const elementAName = one.toLowerCase();
	const elementBName = other.toLowerCase();
E
Erich Gamma 已提交
128 129

	// Sort prefix matches over non prefix matches
130 131 132
	const prefixCompare = compareByPrefix(one, other, lookFor);
	if (prefixCompare) {
		return prefixCompare;
E
Erich Gamma 已提交
133 134 135
	}

	// Sort suffix matches over non suffix matches
136 137
	const elementASuffixMatch = strings.endsWith(elementAName, lookFor);
	const elementBSuffixMatch = strings.endsWith(elementBName, lookFor);
E
Erich Gamma 已提交
138 139 140 141 142
	if (elementASuffixMatch !== elementBSuffixMatch) {
		return elementASuffixMatch ? -1 : 1;
	}

	// Understand file names
143
	const r = compareFileNames(elementAName, elementBName);
E
Erich Gamma 已提交
144 145 146 147 148
	if (r !== 0) {
		return r;
	}

	// Compare by name
149
	return elementAName.localeCompare(elementBName);
150 151 152
}

export function compareByPrefix(one: string, other: string, lookFor: string): number {
153 154
	const elementAName = one.toLowerCase();
	const elementBName = other.toLowerCase();
155 156

	// Sort prefix matches over non prefix matches
157 158
	const elementAPrefixMatch = strings.startsWith(elementAName, lookFor);
	const elementBPrefixMatch = strings.startsWith(elementBName, lookFor);
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
	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;
175
}