src/vs/base/common/comparers.ts

356 LOC · 136 covered · 220 uncovered · 33 ranges · 1122 concepts · 2 introducers · 600 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- comparers.ts ×24
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { safeIntl } from './date.js';
7 > import { Lazy } from './lazy.js';
8 > import { sep } from './path.js';
9 >
10 > // When comparing large numbers of strings it's better for performance to create an
11 > // Intl.Collator object and use the function provided by its compare property
12 > // than it is to use String.prototype.localeCompare()
13 >
14 > // A collator with numeric sorting enabled, and no sensitivity to case, accents or diacritics.
15 > const intlFileNameCollatorBaseNumeric: Lazy<{ collator: Intl.Collator; collatorIsNumeric: boolean }> = new Lazy(() => {
16 > const collator = safeIntl.Collator(undefined, { numeric: true, sensitivity: 'base' }).value; comparers.ts ×9
17 > return {
18 > collator,
19 > collatorIsNumeric: collator.resolvedOptions().numeric
20 > };
21 > });
23 > // A collator with numeric sorting enabled.
24 > const intlFileNameCollatorNumeric: Lazy<{ collator: Intl.Collator }> = new Lazy(() => {
25 const collator = safeIntl.Collator(undefined, { numeric: true }).value;
26 return {
27 collator
28 };
29 });
31 > // A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
32 > const intlFileNameCollatorNumericCaseInsensitive: Lazy<{ collator: Intl.Collator }> = new Lazy(() => {
33 const collator = safeIntl.Collator(undefined, { numeric: true, sensitivity: 'accent' }).value;
34 return {
35 collator
36 };
37 });
39 > /** Compares filenames without distinguishing the name from the extension. Disambiguates by unicode comparison. */
40 > export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
41 > const a = one || ''; comparers.ts ×9
42 > const b = other || '';
43 > const result = intlFileNameCollatorBaseNumeric.value.collator.compare(a, b);
44 >
45 > // Using the numeric option will make compare(`foo1`, `foo01`) === 0. Disambiguate.
46 > if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && a !== b) {
47 return a < b ? -1 : 1;
48 }
50 > return result;
51 > }
53 > /** Compares full filenames without grouping by case. */
54 > export function compareFileNamesDefault(one: string | null, other: string | null): number {
55 const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
56 one = one || '';
57 other = other || '';
58
59 return compareAndDisambiguateByLength(collatorNumeric, one, other);
60 }
62 > /** Compares full filenames grouping uppercase names before lowercase. */
63 > export function compareFileNamesUpper(one: string | null, other: string | null) {
64 const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
65 one = one || '';
66 other = other || '';
67
68 return compareCaseUpperFirst(one, other) || compareAndDisambiguateByLength(collatorNumeric, one, other);
69 }
71 > /** Compares full filenames grouping lowercase names before uppercase. */
72 > export function compareFileNamesLower(one: string | null, other: string | null) {
73 const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
74 one = one || '';
75 other = other || '';
76
77 return compareCaseLowerFirst(one, other) || compareAndDisambiguateByLength(collatorNumeric, one, other);
78 }
80 > /** Compares full filenames by unicode value. */
81 > export function compareFileNamesUnicode(one: string | null, other: string | null) {
82 one = one || '';
83 other = other || '';
84
85 if (one === other) {
86 return 0;
87 }
88
89 return one < other ? -1 : 1;
90 }
92 > /** Compares filenames by extension, then by name. Disambiguates by unicode comparison. */
93 > export function compareFileExtensions(one: string | null, other: string | null): number {
94 const [oneName, oneExtension] = extractNameAndExtension(one);
95 const [otherName, otherExtension] = extractNameAndExtension(other);
96
97 let result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneExtension, otherExtension);
98
99 if (result === 0) {
100 // Using the numeric option will make compare(`foo1`, `foo01`) === 0. Disambiguate.
101 if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && oneExtension !== otherExtension) {
102 return oneExtension < otherExtension ? -1 : 1;
103 }
104
105 // Extensions are equal, compare filenames
106 result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneName, otherName);
107
108 if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && oneName !== otherName) {
109 return oneName < otherName ? -1 : 1;
110 }
111 }
112
113 return result;
114 }
116 > /** Compares filenames by extension, then by full filename. Mixes uppercase and lowercase names together. */
117 > export function compareFileExtensionsDefault(one: string | null, other: string | null): number {
118 one = one || '';
119 other = other || '';
120 const oneExtension = extractExtension(one);
121 const otherExtension = extractExtension(other);
122 const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
123 const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsensitive.value.collator;
124
125 return compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension) ||
126 compareAndDisambiguateByLength(collatorNumeric, one, other);
127 }
129 > /** Compares filenames by extension, then case, then full filename. Groups uppercase names before lowercase. */
130 > export function compareFileExtensionsUpper(one: string | null, other: string | null): number {
131 one = one || '';
132 other = other || '';
133 const oneExtension = extractExtension(one);
134 const otherExtension = extractExtension(other);
135 const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
136 const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsensitive.value.collator;
137
138 return compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension) ||
139 compareCaseUpperFirst(one, other) ||
140 compareAndDisambiguateByLength(collatorNumeric, one, other);
141 }
143 > /** Compares filenames by extension, then case, then full filename. Groups lowercase names before uppercase. */
144 > export function compareFileExtensionsLower(one: string | null, other: string | null): number {
145 one = one || '';
146 other = other || '';
147 const oneExtension = extractExtension(one);
148 const otherExtension = extractExtension(other);
149 const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
150 const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsensitive.value.collator;
151
152 return compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension) ||
153 compareCaseLowerFirst(one, other) ||
154 compareAndDisambiguateByLength(collatorNumeric, one, other);
155 }
157 > /** Compares filenames by case-insensitive extension unicode value, then by full filename unicode value. */
158 > export function compareFileExtensionsUnicode(one: string | null, other: string | null) {
159 one = one || '';
160 other = other || '';
161 const oneExtension = extractExtension(one).toLowerCase();
162 const otherExtension = extractExtension(other).toLowerCase();
163
164 // Check for extension differences
165 if (oneExtension !== otherExtension) {
166 return oneExtension < otherExtension ? -1 : 1;
167 }
168
169 // Check for full filename differences.
170 if (one !== other) {
171 return one < other ? -1 : 1;
172 }
173
174 return 0;
175 }
177 > const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
178 >
179 > /** Extracts the name and extension from a full filename, with optional special handling for dotfiles */
180 function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] {
181 const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
182
183 let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || ''];
184
185 // if the dotfilesAsNames option is selected, treat an empty filename with an extension
186 // or a filename that starts with a dot, as a dotfile name
187 if (dotfilesAsNames && (!result[0] && result[1] || result[0] && result[0].charAt(0) === '.')) {
188 result = [result[0] + '.' + result[1], ''];
189 }
190
191 return result;
192 }
194 > /** Extracts the extension from a full filename. Treats dotfiles as names, not extensions. */
195 function extractExtension(str?: string | null): string {
196 const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
197
198 return (match && match[1] && match[1].charAt(0) !== '.' && match[3]) || '';
199 }
201 function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
202 // Check for differences
203 const result = collator.compare(one, other);
204 if (result !== 0) {
205 return result;
206 }
207
208 // In a numeric comparison, `foo1` and `foo01` will compare as equivalent.
209 // Disambiguate by sorting the shorter string first.
210 if (one.length !== other.length) {
211 return one.length < other.length ? -1 : 1;
212 }
213
214 return 0;
215 }
217 > /** @returns `true` if the string is starts with a lowercase letter. Otherwise, `false`. */
218 function startsWithLower(string: string) {
219 const character = string.charAt(0);
220
221 return (character.toLocaleUpperCase() !== character) ? true : false;
222 }
224 > /** @returns `true` if the string starts with an uppercase letter. Otherwise, `false`. */
225 function startsWithUpper(string: string) {
226 const character = string.charAt(0);
227
228 return (character.toLocaleLowerCase() !== character) ? true : false;
229 }
231 > /**
232 > * Compares the case of the provided strings - lowercase before uppercase
233 > *
234 > * @returns
235 > * ```text
236 > * -1 if one is lowercase and other is uppercase
237 > * 1 if one is uppercase and other is lowercase
238 > * 0 otherwise
239 > * ```
240 > */
241 function compareCaseLowerFirst(one: string, other: string): number {
242 if (startsWithLower(one) && startsWithUpper(other)) {
243 return -1;
244 }
245 return (startsWithUpper(one) && startsWithLower(other)) ? 1 : 0;
246 }
248 > /**
249 > * Compares the case of the provided strings - uppercase before lowercase
250 > *
251 > * @returns
252 > * ```text
253 > * -1 if one is uppercase and other is lowercase
254 > * 1 if one is lowercase and other is uppercase
255 > * 0 otherwise
256 > * ```
257 > */
258 function compareCaseUpperFirst(one: string, other: string): number {
259 if (startsWithUpper(one) && startsWithLower(other)) {
260 return -1;
261 }
262 return (startsWithLower(one) && startsWithUpper(other)) ? 1 : 0;
263 }
265 function comparePathComponents(one: string, other: string, caseSensitive = false): number {
266 if (!caseSensitive) {
267 one = one && one.toLowerCase();
268 other = other && other.toLowerCase();
269 }
270
271 if (one === other) {
272 return 0;
273 }
274
275 return one < other ? -1 : 1;
276 }
278 > export function comparePaths(one: string, other: string, caseSensitive = false): number {
279 const oneParts = one.split(sep);
280 const otherParts = other.split(sep);
281
282 const lastOne = oneParts.length - 1;
283 const lastOther = otherParts.length - 1;
284 let endOne: boolean, endOther: boolean;
285
286 for (let i = 0; ; i++) {
287 endOne = lastOne === i;
288 endOther = lastOther === i;
289
290 if (endOne && endOther) {
291 return compareFileNames(oneParts[i], otherParts[i], caseSensitive);
292 } else if (endOne) {
293 return -1;
294 } else if (endOther) {
295 return 1;
296 }
297
298 const result = comparePathComponents(oneParts[i], otherParts[i], caseSensitive);
299
300 if (result !== 0) {
301 return result;
302 }
303 }
304 }
306 > export function compareAnything(one: string, other: string, lookFor: string): number {
307 > const elementAName = one.toLowerCase(); comparers.ts ×9
308 > const elementBName = other.toLowerCase();
309 >
310 > // Sort prefix matches over non prefix matches
311 > const prefixCompare = compareByPrefix(one, other, lookFor);
312 > if (prefixCompare) {
313 return prefixCompare;
314 }
316 > // Sort suffix matches over non suffix matches
317 > const elementASuffixMatch = elementAName.endsWith(lookFor);
318 > const elementBSuffixMatch = elementBName.endsWith(lookFor);
319 > if (elementASuffixMatch !== elementBSuffixMatch) {
320 return elementASuffixMatch ? -1 : 1;
321 }
323 > // Understand file names
324 > const r = compareFileNames(elementAName, elementBName);
325 > if (r !== 0) {
326 > return r;
327 > }
328
329 // Compare by name
330 return elementAName.localeCompare(elementBName);
331 }
333 > export function compareByPrefix(one: string, other: string, lookFor: string): number {
334 > const elementAName = one.toLowerCase(); comparers.ts ×9
335 > const elementBName = other.toLowerCase();
336 >
337 > // Sort prefix matches over non prefix matches
338 > const elementAPrefixMatch = elementAName.startsWith(lookFor);
339 > const elementBPrefixMatch = elementBName.startsWith(lookFor);
340 > if (elementAPrefixMatch !== elementBPrefixMatch) {
341 return elementAPrefixMatch ? -1 : 1;
342 }
344 > // Same prefix: Sort shorter matches to the top to have those on top that match more precisely
345 > else if (elementAPrefixMatch && elementBPrefixMatch) {
346 if (elementAName.length < elementBName.length) {
347 return -1;
348 }
349
350 if (elementAName.length > elementBName.length) {
351 return 1;
352 }
353 }
355 > return 0;
356 > }