@opensumi/ide-utils 2.21.13 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/lib/arrays.d.ts +3 -3
  2. package/lib/arrays.d.ts.map +1 -1
  3. package/lib/arrays.js.map +1 -1
  4. package/lib/async.d.ts +3 -3
  5. package/lib/async.d.ts.map +1 -1
  6. package/lib/buffer.js +4 -4
  7. package/lib/buffer.js.map +1 -1
  8. package/lib/character-classifier.js +3 -3
  9. package/lib/character-classifier.js.map +1 -1
  10. package/lib/decorators.d.ts +1 -1
  11. package/lib/decorators.d.ts.map +1 -1
  12. package/lib/encoding.d.ts +7 -1
  13. package/lib/encoding.d.ts.map +1 -1
  14. package/lib/encoding.js +15 -2
  15. package/lib/encoding.js.map +1 -1
  16. package/lib/errors.d.ts +2 -2
  17. package/lib/errors.d.ts.map +1 -1
  18. package/lib/event.d.ts +2 -2
  19. package/lib/event.d.ts.map +1 -1
  20. package/lib/filters.d.ts +3 -3
  21. package/lib/filters.d.ts.map +1 -1
  22. package/lib/filters.js +25 -25
  23. package/lib/filters.js.map +1 -1
  24. package/lib/glob.d.ts +2 -2
  25. package/lib/glob.d.ts.map +1 -1
  26. package/lib/glob.js +1 -1
  27. package/lib/glob.js.map +1 -1
  28. package/lib/hash.js +10 -10
  29. package/lib/hash.js.map +1 -1
  30. package/lib/iterator.d.ts +1 -1
  31. package/lib/iterator.d.ts.map +1 -1
  32. package/lib/linked-text.d.ts +1 -1
  33. package/lib/linked-text.d.ts.map +1 -1
  34. package/lib/linked-text.js.map +1 -1
  35. package/lib/map.js +17 -17
  36. package/lib/map.js.map +1 -1
  37. package/lib/objects.d.ts +1 -0
  38. package/lib/objects.d.ts.map +1 -1
  39. package/lib/objects.js +38 -1
  40. package/lib/objects.js.map +1 -1
  41. package/lib/os.d.ts +1 -8
  42. package/lib/os.d.ts.map +1 -1
  43. package/lib/os.js +4 -8
  44. package/lib/os.js.map +1 -1
  45. package/lib/path.d.ts +1 -1
  46. package/lib/path.d.ts.map +1 -1
  47. package/lib/path.js +29 -29
  48. package/lib/path.js.map +1 -1
  49. package/lib/platform.js +8 -8
  50. package/lib/platform.js.map +1 -1
  51. package/lib/process.d.ts.map +1 -1
  52. package/lib/process.js +28 -13
  53. package/lib/process.js.map +1 -1
  54. package/lib/progress.js +3 -3
  55. package/lib/progress.js.map +1 -1
  56. package/lib/strings.js +9 -9
  57. package/lib/strings.js.map +1 -1
  58. package/lib/types.d.ts +5 -1
  59. package/lib/types.d.ts.map +1 -1
  60. package/lib/types.js +8 -1
  61. package/lib/types.js.map +1 -1
  62. package/lib/uint.js +4 -4
  63. package/lib/uint.js.map +1 -1
  64. package/lib/uri.js +8 -8
  65. package/lib/uri.js.map +1 -1
  66. package/lib/uuid.d.ts +1 -1
  67. package/lib/uuid.d.ts.map +1 -1
  68. package/lib/uuid.js +3 -2
  69. package/lib/uuid.js.map +1 -1
  70. package/package.json +8 -6
  71. package/src/ansi.ts +10 -0
  72. package/src/argv.ts +57 -0
  73. package/src/arrays.ts +328 -0
  74. package/src/async.ts +580 -0
  75. package/src/buffer.ts +290 -0
  76. package/src/cache.ts +37 -0
  77. package/src/cancellation.ts +136 -0
  78. package/src/charCode.ts +425 -0
  79. package/src/character-classifier.ts +82 -0
  80. package/src/const/encoding.ts +242 -0
  81. package/src/const/index.ts +1 -0
  82. package/src/date.ts +36 -0
  83. package/src/decorators.ts +166 -0
  84. package/src/disposable.ts +377 -0
  85. package/src/encoding.ts +278 -0
  86. package/src/errors.ts +192 -0
  87. package/src/event.ts +1024 -0
  88. package/src/file-uri.ts +41 -0
  89. package/src/filters.ts +887 -0
  90. package/src/functional.ts +43 -0
  91. package/src/glob.ts +752 -0
  92. package/src/hash.ts +336 -0
  93. package/src/iconLabels.ts +149 -0
  94. package/src/index.ts +40 -0
  95. package/src/iterator.ts +36 -0
  96. package/src/lifecycle.ts +48 -0
  97. package/src/linked-list.ts +144 -0
  98. package/src/linked-text.ts +55 -0
  99. package/src/lru-map.ts +133 -0
  100. package/src/map.ts +934 -0
  101. package/src/marshalling.ts +62 -0
  102. package/src/objects.ts +145 -0
  103. package/src/os.ts +76 -0
  104. package/src/path.ts +1885 -0
  105. package/src/platform.ts +194 -0
  106. package/src/process.ts +43 -0
  107. package/src/progress.ts +83 -0
  108. package/src/promises.ts +23 -0
  109. package/src/sequence.ts +28 -0
  110. package/src/strings.ts +1002 -0
  111. package/src/types.ts +214 -0
  112. package/src/uint.ts +59 -0
  113. package/src/uri.ts +298 -0
  114. package/src/uuid.ts +6 -0
package/src/filters.ts ADDED
@@ -0,0 +1,887 @@
1
+ /* ---------------------------------------------------------------------------------------------
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
+ // Some code copied and modified from https://github.com/microsoft/vscode/blob/1.44.0/src/vs/base/common/filters.ts
6
+
7
+ import { CharCode } from './charCode';
8
+ import { LRUCache } from './map';
9
+ import { startsWithIgnoreCase, convertSimple2RegExpPattern } from './strings';
10
+
11
+ // Returns null if word doesn't match.
12
+ export type IFilter = (word: string, wordToMatchAgainst: string) => IMatch[] | null;
13
+
14
+ export interface IMatch {
15
+ start: number;
16
+ end: number;
17
+ }
18
+
19
+ // Combined filters
20
+
21
+ /**
22
+ * @returns A filter which combines the provided set
23
+ * of filters with an or. The *first* filters that
24
+ * matches defined the return value of the returned
25
+ * filter.
26
+ */
27
+ export function or(...filter: IFilter[]): IFilter {
28
+ return function (word: string, wordToMatchAgainst: string): IMatch[] | null {
29
+ for (let i = 0, len = filter.length; i < len; i++) {
30
+ const match = filter[i](word, wordToMatchAgainst);
31
+ if (match) {
32
+ return match;
33
+ }
34
+ }
35
+ return null;
36
+ };
37
+ }
38
+
39
+ // Prefix
40
+
41
+ export const matchesStrictPrefix: IFilter = _matchesPrefix.bind(undefined, false);
42
+ export const matchesPrefix: IFilter = _matchesPrefix.bind(undefined, true);
43
+
44
+ function _matchesPrefix(ignoreCase: boolean, word: string, wordToMatchAgainst: string): IMatch[] | null {
45
+ if (!wordToMatchAgainst || wordToMatchAgainst.length < word.length) {
46
+ return null;
47
+ }
48
+
49
+ let matches: boolean;
50
+ if (ignoreCase) {
51
+ matches = startsWithIgnoreCase(wordToMatchAgainst, word);
52
+ } else {
53
+ matches = wordToMatchAgainst.indexOf(word) === 0;
54
+ }
55
+
56
+ if (!matches) {
57
+ return null;
58
+ }
59
+
60
+ return word.length > 0 ? [{ start: 0, end: word.length }] : [];
61
+ }
62
+
63
+ // Contiguous Substring
64
+
65
+ export function matchesContiguousSubString(word: string, wordToMatchAgainst: string): IMatch[] | null {
66
+ const index = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase());
67
+ if (index === -1) {
68
+ return null;
69
+ }
70
+
71
+ return [{ start: index, end: index + word.length }];
72
+ }
73
+
74
+ // Substring
75
+
76
+ export function matchesSubString(word: string, wordToMatchAgainst: string): IMatch[] | null {
77
+ return _matchesSubString(word.toLowerCase(), wordToMatchAgainst.toLowerCase(), 0, 0);
78
+ }
79
+
80
+ function _matchesSubString(word: string, wordToMatchAgainst: string, i: number, j: number): IMatch[] | null {
81
+ if (i === word.length) {
82
+ return [];
83
+ } else if (j === wordToMatchAgainst.length) {
84
+ return null;
85
+ } else {
86
+ if (word[i] === wordToMatchAgainst[j]) {
87
+ let result: IMatch[] | null = null;
88
+ if ((result = _matchesSubString(word, wordToMatchAgainst, i + 1, j + 1))) {
89
+ return join({ start: j, end: j + 1 }, result);
90
+ }
91
+ return null;
92
+ }
93
+
94
+ return _matchesSubString(word, wordToMatchAgainst, i, j + 1);
95
+ }
96
+ }
97
+
98
+ // CamelCase
99
+
100
+ function isLower(code: number): boolean {
101
+ return CharCode.a <= code && code <= CharCode.z;
102
+ }
103
+
104
+ export function isUpper(code: number): boolean {
105
+ return CharCode.A <= code && code <= CharCode.Z;
106
+ }
107
+
108
+ function isNumber(code: number): boolean {
109
+ return CharCode.Digit0 <= code && code <= CharCode.Digit9;
110
+ }
111
+
112
+ function isWhitespace(code: number): boolean {
113
+ return (
114
+ code === CharCode.Space || code === CharCode.Tab || code === CharCode.LineFeed || code === CharCode.CarriageReturn
115
+ );
116
+ }
117
+
118
+ const wordSeparators = new Set<number>();
119
+ '`~!@#$%^&*()-=+[{]}\\|;:\'",.<>/?'.split('').forEach((s) => wordSeparators.add(s.charCodeAt(0)));
120
+
121
+ function isWordSeparator(code: number): boolean {
122
+ return isWhitespace(code) || wordSeparators.has(code);
123
+ }
124
+
125
+ function charactersMatch(codeA: number, codeB: number): boolean {
126
+ return codeA === codeB || (isWordSeparator(codeA) && isWordSeparator(codeB));
127
+ }
128
+
129
+ function isAlphanumeric(code: number): boolean {
130
+ return isLower(code) || isUpper(code) || isNumber(code);
131
+ }
132
+
133
+ function join(head: IMatch, tail: IMatch[]): IMatch[] {
134
+ if (tail.length === 0) {
135
+ tail = [head];
136
+ } else if (head.end === tail[0].start) {
137
+ tail[0].start = head.start;
138
+ } else {
139
+ tail.unshift(head);
140
+ }
141
+ return tail;
142
+ }
143
+
144
+ function nextAnchor(camelCaseWord: string, start: number): number {
145
+ for (let i = start; i < camelCaseWord.length; i++) {
146
+ const c = camelCaseWord.charCodeAt(i);
147
+ if (isUpper(c) || isNumber(c) || (i > 0 && !isAlphanumeric(camelCaseWord.charCodeAt(i - 1)))) {
148
+ return i;
149
+ }
150
+ }
151
+ return camelCaseWord.length;
152
+ }
153
+
154
+ function _matchesCamelCase(word: string, camelCaseWord: string, i: number, j: number): IMatch[] | null {
155
+ if (i === word.length) {
156
+ return [];
157
+ } else if (j === camelCaseWord.length) {
158
+ return null;
159
+ } else if (word[i] !== camelCaseWord[j].toLowerCase()) {
160
+ return null;
161
+ } else {
162
+ let result: IMatch[] | null = null;
163
+ let nextUpperIndex = j + 1;
164
+ result = _matchesCamelCase(word, camelCaseWord, i + 1, j + 1);
165
+ while (!result && (nextUpperIndex = nextAnchor(camelCaseWord, nextUpperIndex)) < camelCaseWord.length) {
166
+ result = _matchesCamelCase(word, camelCaseWord, i + 1, nextUpperIndex);
167
+ nextUpperIndex++;
168
+ }
169
+ return result === null ? null : join({ start: j, end: j + 1 }, result);
170
+ }
171
+ }
172
+
173
+ interface ICamelCaseAnalysis {
174
+ upperPercent: number;
175
+ lowerPercent: number;
176
+ alphaPercent: number;
177
+ numericPercent: number;
178
+ }
179
+
180
+ // Heuristic to avoid computing camel case matcher for words that don't
181
+ // look like camelCaseWords.
182
+ function analyzeCamelCaseWord(word: string): ICamelCaseAnalysis {
183
+ let upper = 0;
184
+ let lower = 0;
185
+ let alpha = 0;
186
+ let numeric = 0;
187
+ let code = 0;
188
+
189
+ for (let i = 0; i < word.length; i++) {
190
+ code = word.charCodeAt(i);
191
+
192
+ if (isUpper(code)) {
193
+ upper++;
194
+ }
195
+ if (isLower(code)) {
196
+ lower++;
197
+ }
198
+ if (isAlphanumeric(code)) {
199
+ alpha++;
200
+ }
201
+ if (isNumber(code)) {
202
+ numeric++;
203
+ }
204
+ }
205
+
206
+ const upperPercent = upper / word.length;
207
+ const lowerPercent = lower / word.length;
208
+ const alphaPercent = alpha / word.length;
209
+ const numericPercent = numeric / word.length;
210
+
211
+ return { upperPercent, lowerPercent, alphaPercent, numericPercent };
212
+ }
213
+
214
+ function isUpperCaseWord(analysis: ICamelCaseAnalysis): boolean {
215
+ const { upperPercent, lowerPercent } = analysis;
216
+ return lowerPercent === 0 && upperPercent > 0.6;
217
+ }
218
+
219
+ function isCamelCaseWord(analysis: ICamelCaseAnalysis): boolean {
220
+ const { upperPercent, lowerPercent, alphaPercent, numericPercent } = analysis;
221
+ return lowerPercent > 0.2 && upperPercent < 0.8 && alphaPercent > 0.6 && numericPercent < 0.2;
222
+ }
223
+
224
+ // Heuristic to avoid computing camel case matcher for words that don't
225
+ // look like camel case patterns.
226
+ function isCamelCasePattern(word: string): boolean {
227
+ let upper = 0;
228
+ let lower = 0;
229
+ let code = 0;
230
+ let whitespace = 0;
231
+
232
+ for (let i = 0; i < word.length; i++) {
233
+ code = word.charCodeAt(i);
234
+
235
+ if (isUpper(code)) {
236
+ upper++;
237
+ }
238
+ if (isLower(code)) {
239
+ lower++;
240
+ }
241
+ if (isWhitespace(code)) {
242
+ whitespace++;
243
+ }
244
+ }
245
+
246
+ if ((upper === 0 || lower === 0) && whitespace === 0) {
247
+ return word.length <= 30;
248
+ } else {
249
+ return upper <= 5;
250
+ }
251
+ }
252
+
253
+ export function matchesCamelCase(word: string, camelCaseWord: string): IMatch[] | null {
254
+ if (!camelCaseWord) {
255
+ return null;
256
+ }
257
+
258
+ camelCaseWord = camelCaseWord.trim();
259
+
260
+ if (camelCaseWord.length === 0) {
261
+ return null;
262
+ }
263
+
264
+ if (!isCamelCasePattern(word)) {
265
+ return null;
266
+ }
267
+
268
+ if (camelCaseWord.length > 60) {
269
+ return null;
270
+ }
271
+
272
+ const analysis = analyzeCamelCaseWord(camelCaseWord);
273
+
274
+ if (!isCamelCaseWord(analysis)) {
275
+ if (!isUpperCaseWord(analysis)) {
276
+ return null;
277
+ }
278
+
279
+ camelCaseWord = camelCaseWord.toLowerCase();
280
+ }
281
+
282
+ let result: IMatch[] | null = null;
283
+ let i = 0;
284
+
285
+ word = word.toLowerCase();
286
+ while (i < camelCaseWord.length && (result = _matchesCamelCase(word, camelCaseWord, 0, i)) === null) {
287
+ i = nextAnchor(camelCaseWord, i + 1);
288
+ }
289
+
290
+ return result;
291
+ }
292
+
293
+ // Matches beginning of words supporting non-ASCII languages
294
+ // If `contiguous` is true then matches word with beginnings of the words in the target. E.g. "pul" will match "Git: Pull"
295
+ // Otherwise also matches sub string of the word with beginnings of the words in the target. E.g. "gp" or "g p" will match "Git: Pull"
296
+ // Useful in cases where the target is words (e.g. command labels)
297
+
298
+ export function matchesWords(word: string, target: string, contiguous = false): IMatch[] | null {
299
+ if (!target || target.length === 0) {
300
+ return null;
301
+ }
302
+
303
+ let result: IMatch[] | null = null;
304
+ let i = 0;
305
+
306
+ word = word.toLowerCase();
307
+ target = target.toLowerCase();
308
+ while (i < target.length && (result = _matchesWords(word, target, 0, i, contiguous)) === null) {
309
+ i = nextWord(target, i + 1);
310
+ }
311
+
312
+ return result;
313
+ }
314
+
315
+ function _matchesWords(word: string, target: string, i: number, j: number, contiguous: boolean): IMatch[] | null {
316
+ if (i === word.length) {
317
+ return [];
318
+ } else if (j === target.length) {
319
+ return null;
320
+ } else if (!charactersMatch(word.charCodeAt(i), target.charCodeAt(j))) {
321
+ return null;
322
+ } else {
323
+ let result: IMatch[] | null = null;
324
+ let nextWordIndex = j + 1;
325
+ result = _matchesWords(word, target, i + 1, j + 1, contiguous);
326
+ if (!contiguous) {
327
+ while (!result && (nextWordIndex = nextWord(target, nextWordIndex)) < target.length) {
328
+ result = _matchesWords(word, target, i + 1, nextWordIndex, contiguous);
329
+ nextWordIndex++;
330
+ }
331
+ }
332
+ return result === null ? null : join({ start: j, end: j + 1 }, result);
333
+ }
334
+ }
335
+
336
+ function nextWord(word: string, start: number): number {
337
+ for (let i = start; i < word.length; i++) {
338
+ if (isWordSeparator(word.charCodeAt(i)) || (i > 0 && isWordSeparator(word.charCodeAt(i - 1)))) {
339
+ return i;
340
+ }
341
+ }
342
+ return word.length;
343
+ }
344
+
345
+ // Fuzzy
346
+
347
+ const fuzzyContiguousFilter = or(matchesPrefix, matchesCamelCase, matchesContiguousSubString);
348
+ const fuzzySeparateFilter = or(matchesPrefix, matchesCamelCase, matchesSubString);
349
+ const fuzzyRegExpCache = new LRUCache<string, RegExp>(10000); // bounded to 10000 elements
350
+
351
+ export function matchesFuzzy(
352
+ word: string,
353
+ wordToMatchAgainst: string,
354
+ enableSeparateSubstringMatching = false,
355
+ ): IMatch[] | null {
356
+ if (typeof word !== 'string' || typeof wordToMatchAgainst !== 'string') {
357
+ return null; // return early for invalid input
358
+ }
359
+
360
+ // Form RegExp for wildcard matches
361
+ let regexp = fuzzyRegExpCache.get(word);
362
+ if (!regexp) {
363
+ regexp = new RegExp(convertSimple2RegExpPattern(word), 'i');
364
+ fuzzyRegExpCache.set(word, regexp);
365
+ }
366
+
367
+ // RegExp Filter
368
+ const match = regexp.exec(wordToMatchAgainst);
369
+ if (match) {
370
+ return [{ start: match.index, end: match.index + match[0].length }];
371
+ }
372
+
373
+ // Default Filter
374
+ return enableSeparateSubstringMatching
375
+ ? fuzzySeparateFilter(word, wordToMatchAgainst)
376
+ : fuzzyContiguousFilter(word, wordToMatchAgainst);
377
+ }
378
+
379
+ /**
380
+ * Match pattern againt word in a fuzzy way. As in IntelliSense and faster and more
381
+ * powerfull than `matchesFuzzy`
382
+ */
383
+ export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null {
384
+ const score = fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, true);
385
+ return score ? createMatches(score) : null;
386
+ }
387
+
388
+ export function anyScore(
389
+ pattern: string,
390
+ lowPattern: string,
391
+ _patternPos: number,
392
+ word: string,
393
+ lowWord: string,
394
+ _wordPos: number,
395
+ ): FuzzyScore {
396
+ const result = fuzzyScore(pattern, lowPattern, 0, word, lowWord, 0, true);
397
+ if (result) {
398
+ return result;
399
+ }
400
+ let matches = 0;
401
+ let score = 0;
402
+ let idx = _wordPos;
403
+ for (let patternPos = 0; patternPos < lowPattern.length && patternPos < _maxLen; ++patternPos) {
404
+ const wordPos = lowWord.indexOf(lowPattern.charAt(patternPos), idx);
405
+ if (wordPos >= 0) {
406
+ score += 1;
407
+ matches += 2 ** wordPos;
408
+ idx = wordPos + 1;
409
+ } else if (matches !== 0) {
410
+ // once we have started matching things
411
+ // we need to match the remaining pattern
412
+ // characters
413
+ break;
414
+ }
415
+ }
416
+ return [score, matches, _wordPos];
417
+ }
418
+
419
+ // #region --- fuzzyScore ---
420
+
421
+ export function createMatches(score: undefined | FuzzyScore): IMatch[] {
422
+ if (typeof score === 'undefined') {
423
+ return [];
424
+ }
425
+
426
+ const matches = score[1].toString(2);
427
+ const wordStart = score[2];
428
+ const res: IMatch[] = [];
429
+
430
+ for (let pos = wordStart; pos < _maxLen; pos++) {
431
+ if (matches[matches.length - (pos + 1)] === '1') {
432
+ const last = res[res.length - 1];
433
+ if (last && last.end === pos) {
434
+ last.end = pos + 1;
435
+ } else {
436
+ res.push({ start: pos, end: pos + 1 });
437
+ }
438
+ }
439
+ }
440
+ return res;
441
+ }
442
+
443
+ const _maxLen = 128;
444
+
445
+ function initTable() {
446
+ const table: number[][] = [];
447
+ const row: number[] = [0];
448
+ for (let i = 1; i <= _maxLen; i++) {
449
+ row.push(-i);
450
+ }
451
+ for (let i = 0; i <= _maxLen; i++) {
452
+ const thisRow = row.slice(0);
453
+ thisRow[0] = -i;
454
+ table.push(thisRow);
455
+ }
456
+ return table;
457
+ }
458
+
459
+ const _table = initTable();
460
+ const _scores = initTable();
461
+ const _arrows = initTable() as Arrow[][];
462
+ const _debug = false;
463
+
464
+ function printTable(table: number[][], pattern: string, patternLen: number, word: string, wordLen: number): string {
465
+ function pad(s: string, n: number, pad = ' ') {
466
+ while (s.length < n) {
467
+ s = pad + s;
468
+ }
469
+ return s;
470
+ }
471
+ let ret = ` | |${word
472
+ .split('')
473
+ .map((c) => pad(c, 3))
474
+ .join('|')}\n`;
475
+
476
+ for (let i = 0; i <= patternLen; i++) {
477
+ if (i === 0) {
478
+ ret += ' |';
479
+ } else {
480
+ ret += `${pattern[i - 1]}|`;
481
+ }
482
+ ret +=
483
+ table[i]
484
+ .slice(0, wordLen + 1)
485
+ .map((n) => pad(n.toString(), 3))
486
+ .join('|') + '\n';
487
+ }
488
+ return ret;
489
+ }
490
+
491
+ function isSeparatorAtPos(value: string, index: number): boolean {
492
+ if (index < 0 || index >= value.length) {
493
+ return false;
494
+ }
495
+ const code = value.charCodeAt(index);
496
+ switch (code) {
497
+ case CharCode.Underline:
498
+ case CharCode.Dash:
499
+ case CharCode.Period:
500
+ case CharCode.Space:
501
+ case CharCode.Slash:
502
+ case CharCode.Backslash:
503
+ case CharCode.SingleQuote:
504
+ case CharCode.DoubleQuote:
505
+ case CharCode.Colon:
506
+ case CharCode.DollarSign:
507
+ return true;
508
+ default:
509
+ return false;
510
+ }
511
+ }
512
+
513
+ function isWhitespaceAtPos(value: string, index: number): boolean {
514
+ if (index < 0 || index >= value.length) {
515
+ return false;
516
+ }
517
+ const code = value.charCodeAt(index);
518
+ switch (code) {
519
+ case CharCode.Space:
520
+ case CharCode.Tab:
521
+ return true;
522
+ default:
523
+ return false;
524
+ }
525
+ }
526
+
527
+ function isUpperCaseAtPos(pos: number, word: string, wordLow: string): boolean {
528
+ return word[pos] !== wordLow[pos];
529
+ }
530
+
531
+ export function isPatternInWord(
532
+ patternLow: string,
533
+ patternPos: number,
534
+ patternLen: number,
535
+ wordLow: string,
536
+ wordPos: number,
537
+ wordLen: number,
538
+ ): boolean {
539
+ while (patternPos < patternLen && wordPos < wordLen) {
540
+ if (patternLow[patternPos] === wordLow[wordPos]) {
541
+ patternPos += 1;
542
+ }
543
+ wordPos += 1;
544
+ }
545
+ return patternPos === patternLen; // pattern must be exhausted
546
+ }
547
+
548
+ const enum Arrow {
549
+ Top = 0b1,
550
+ Diag = 0b10,
551
+ Left = 0b100,
552
+ }
553
+
554
+ /**
555
+ * A tuple of three values.
556
+ * 0. the score
557
+ * 1. the matches encoded as bitmask (2^53)
558
+ * 2. the offset at which matching started
559
+ */
560
+ export type FuzzyScore = [number, number, number];
561
+
562
+ export namespace FuzzyScore {
563
+ /**
564
+ * No matches and value `-100`
565
+ */
566
+ export const Default = Object.freeze([-100, 0, 0]) as [-100, 0, 0];
567
+
568
+ export function isDefault(score?: FuzzyScore): score is [-100, 0, 0] {
569
+ return !score || (score[0] === -100 && score[1] === 0 && score[2] === 0);
570
+ }
571
+ }
572
+
573
+ export type FuzzyScorer = (
574
+ pattern: string,
575
+ lowPattern: string,
576
+ patternPos: number,
577
+ word: string,
578
+ lowWord: string,
579
+ wordPos: number,
580
+ firstMatchCanBeWeak: boolean,
581
+ ) => FuzzyScore | undefined;
582
+
583
+ export function fuzzyScore(
584
+ pattern: string,
585
+ patternLow: string,
586
+ patternPos: number,
587
+ word: string,
588
+ wordLow: string,
589
+ wordPos: number,
590
+ firstMatchCanBeWeak: boolean,
591
+ ): FuzzyScore | undefined {
592
+ const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
593
+ const wordLen = word.length > _maxLen ? _maxLen : word.length;
594
+
595
+ if (patternPos >= patternLen || wordPos >= wordLen || patternLen > wordLen) {
596
+ return undefined;
597
+ }
598
+
599
+ // Run a simple check if the characters of pattern occur
600
+ // (in order) at all in word. If that isn't the case we
601
+ // stop because no match will be possible
602
+ if (!isPatternInWord(patternLow, patternPos, patternLen, wordLow, wordPos, wordLen)) {
603
+ return undefined;
604
+ }
605
+
606
+ const patternStartPos = patternPos;
607
+ const wordStartPos = wordPos;
608
+
609
+ // There will be a mach, fill in tables
610
+ for (patternPos = patternStartPos + 1; patternPos <= patternLen; patternPos++) {
611
+ for (wordPos = 1; wordPos <= wordLen; wordPos++) {
612
+ let score = -1;
613
+ if (patternLow[patternPos - 1] === wordLow[wordPos - 1]) {
614
+ if (wordPos === patternPos - patternStartPos) {
615
+ // common prefix: `foobar <-> foobaz`
616
+ // ^^^^^
617
+ if (pattern[patternPos - 1] === word[wordPos - 1]) {
618
+ score = 7;
619
+ } else {
620
+ score = 5;
621
+ }
622
+ } else if (
623
+ isUpperCaseAtPos(wordPos - 1, word, wordLow) &&
624
+ (wordPos === 1 || !isUpperCaseAtPos(wordPos - 2, word, wordLow))
625
+ ) {
626
+ // hitting upper-case: `foo <-> forOthers`
627
+ // ^^ ^
628
+ if (pattern[patternPos - 1] === word[wordPos - 1]) {
629
+ score = 7;
630
+ } else {
631
+ score = 5;
632
+ }
633
+ } else if (isSeparatorAtPos(wordLow, wordPos - 2) || isWhitespaceAtPos(wordLow, wordPos - 2)) {
634
+ // post separator: `foo <-> bar_foo`
635
+ // ^^^
636
+ score = 5;
637
+ } else {
638
+ score = 1;
639
+ }
640
+ }
641
+
642
+ _scores[patternPos][wordPos] = score;
643
+
644
+ const diag = _table[patternPos - 1][wordPos - 1] + (score > 1 ? 1 : score);
645
+ const top = _table[patternPos - 1][wordPos] + -1;
646
+ const left = _table[patternPos][wordPos - 1] + -1;
647
+
648
+ if (left >= top) {
649
+ // left or diag
650
+ if (left > diag) {
651
+ _table[patternPos][wordPos] = left;
652
+ _arrows[patternPos][wordPos] = Arrow.Left;
653
+ } else if (left === diag) {
654
+ _table[patternPos][wordPos] = left;
655
+ _arrows[patternPos][wordPos] = Arrow.Left | Arrow.Diag;
656
+ } else {
657
+ _table[patternPos][wordPos] = diag;
658
+ _arrows[patternPos][wordPos] = Arrow.Diag;
659
+ }
660
+ } else {
661
+ // top or diag
662
+ if (top > diag) {
663
+ _table[patternPos][wordPos] = top;
664
+ _arrows[patternPos][wordPos] = Arrow.Top;
665
+ } else if (top === diag) {
666
+ _table[patternPos][wordPos] = top;
667
+ _arrows[patternPos][wordPos] = Arrow.Top | Arrow.Diag;
668
+ } else {
669
+ _table[patternPos][wordPos] = diag;
670
+ _arrows[patternPos][wordPos] = Arrow.Diag;
671
+ }
672
+ }
673
+ }
674
+ }
675
+
676
+ if (_debug) {
677
+ // eslint-disable-next-line no-console
678
+ console.log(printTable(_table, pattern, patternLen, word, wordLen));
679
+ // eslint-disable-next-line no-console
680
+ console.log(printTable(_arrows, pattern, patternLen, word, wordLen));
681
+ // eslint-disable-next-line no-console
682
+ console.log(printTable(_scores, pattern, patternLen, word, wordLen));
683
+ }
684
+
685
+ _matchesCount = 0;
686
+ _topScore = -100;
687
+ _patternStartPos = patternStartPos;
688
+ _firstMatchCanBeWeak = firstMatchCanBeWeak;
689
+ _findAllMatches2(patternLen, wordLen, patternLen === wordLen ? 1 : 0, 0, false);
690
+ if (_matchesCount === 0) {
691
+ return undefined;
692
+ }
693
+
694
+ return [_topScore, _topMatch2, wordStartPos];
695
+ }
696
+
697
+ let _matchesCount = 0;
698
+ let _topMatch2 = 0;
699
+ let _topScore = 0;
700
+ let _patternStartPos = 0;
701
+ let _firstMatchCanBeWeak = false;
702
+
703
+ function _findAllMatches2(
704
+ patternPos: number,
705
+ wordPos: number,
706
+ total: number,
707
+ matches: number,
708
+ lastMatched: boolean,
709
+ ): void {
710
+ if (_matchesCount >= 10 || total < -25) {
711
+ // stop when having already 10 results, or
712
+ // when a potential alignment as already 5 gaps
713
+ return;
714
+ }
715
+
716
+ let simpleMatchCount = 0;
717
+
718
+ while (patternPos > _patternStartPos && wordPos > 0) {
719
+ const score = _scores[patternPos][wordPos];
720
+ const arrow = _arrows[patternPos][wordPos];
721
+
722
+ if (arrow === Arrow.Left) {
723
+ // left -> no match, skip a word character
724
+ wordPos -= 1;
725
+ if (lastMatched) {
726
+ total -= 5; // new gap penalty
727
+ } else if (matches !== 0) {
728
+ total -= 1; // gap penalty after first match
729
+ }
730
+ lastMatched = false;
731
+ simpleMatchCount = 0;
732
+ } else if (arrow & Arrow.Diag) {
733
+ if (arrow & Arrow.Left) {
734
+ // left
735
+ _findAllMatches2(
736
+ patternPos,
737
+ wordPos - 1,
738
+ matches !== 0 ? total - 1 : total, // gap penalty after first match
739
+ matches,
740
+ lastMatched,
741
+ );
742
+ }
743
+
744
+ // diag
745
+ total += score;
746
+ patternPos -= 1;
747
+ wordPos -= 1;
748
+ lastMatched = true;
749
+
750
+ // match -> set a 1 at the word pos
751
+ matches += 2 ** wordPos;
752
+
753
+ // count simple matches and boost a row of
754
+ // simple matches when they yield in a
755
+ // strong match.
756
+ if (score === 1) {
757
+ simpleMatchCount += 1;
758
+
759
+ if (patternPos === _patternStartPos && !_firstMatchCanBeWeak) {
760
+ // when the first match is a weak
761
+ // match we discard it
762
+ return undefined;
763
+ }
764
+ } else {
765
+ // boost
766
+ total += 1 + simpleMatchCount * (score - 1);
767
+ simpleMatchCount = 0;
768
+ }
769
+ } else {
770
+ return undefined;
771
+ }
772
+ }
773
+
774
+ total -= wordPos >= 3 ? 9 : wordPos * 3; // late start penalty
775
+
776
+ // dynamically keep track of the current top score
777
+ // and insert the current best score at head, the rest at tail
778
+ _matchesCount += 1;
779
+ if (total > _topScore) {
780
+ _topScore = total;
781
+ _topMatch2 = matches;
782
+ }
783
+ }
784
+
785
+ // #endregion
786
+
787
+ // #region --- graceful ---
788
+
789
+ export function fuzzyScoreGracefulAggressive(
790
+ pattern: string,
791
+ lowPattern: string,
792
+ patternPos: number,
793
+ word: string,
794
+ lowWord: string,
795
+ wordPos: number,
796
+ firstMatchCanBeWeak: boolean,
797
+ ): FuzzyScore | undefined {
798
+ return fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, true, firstMatchCanBeWeak);
799
+ }
800
+
801
+ export function fuzzyScoreGraceful(
802
+ pattern: string,
803
+ lowPattern: string,
804
+ patternPos: number,
805
+ word: string,
806
+ lowWord: string,
807
+ wordPos: number,
808
+ firstMatchCanBeWeak: boolean,
809
+ ): FuzzyScore | undefined {
810
+ return fuzzyScoreWithPermutations(
811
+ pattern,
812
+ lowPattern,
813
+ patternPos,
814
+ word,
815
+ lowWord,
816
+ wordPos,
817
+ false,
818
+ firstMatchCanBeWeak,
819
+ );
820
+ }
821
+
822
+ function fuzzyScoreWithPermutations(
823
+ pattern: string,
824
+ lowPattern: string,
825
+ patternPos: number,
826
+ word: string,
827
+ lowWord: string,
828
+ wordPos: number,
829
+ aggressive: boolean,
830
+ firstMatchCanBeWeak: boolean,
831
+ ): FuzzyScore | undefined {
832
+ let top = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, firstMatchCanBeWeak);
833
+
834
+ if (top && !aggressive) {
835
+ // when using the original pattern yield a result we`
836
+ // return it unless we are aggressive and try to find
837
+ // a better alignment, e.g. `cno` -> `^co^ns^ole` or `^c^o^nsole`.
838
+ return top;
839
+ }
840
+
841
+ if (pattern.length >= 3) {
842
+ // When the pattern is long enough then try a few (max 7)
843
+ // permutations of the pattern to find a better match. The
844
+ // permutations only swap neighbouring characters, e.g
845
+ // `cnoso` becomes `conso`, `cnsoo`, `cnoos`.
846
+ const tries = Math.min(7, pattern.length - 1);
847
+ for (let movingPatternPos = patternPos + 1; movingPatternPos < tries; movingPatternPos++) {
848
+ const newPattern = nextTypoPermutation(pattern, movingPatternPos);
849
+ if (newPattern) {
850
+ const candidate = fuzzyScore(
851
+ newPattern,
852
+ newPattern.toLowerCase(),
853
+ patternPos,
854
+ word,
855
+ lowWord,
856
+ wordPos,
857
+ firstMatchCanBeWeak,
858
+ );
859
+ if (candidate) {
860
+ candidate[0] -= 3; // permutation penalty
861
+ if (!top || candidate[0] > top[0]) {
862
+ top = candidate;
863
+ }
864
+ }
865
+ }
866
+ }
867
+ }
868
+
869
+ return top;
870
+ }
871
+
872
+ function nextTypoPermutation(pattern: string, patternPos: number): string | undefined {
873
+ if (patternPos + 1 >= pattern.length) {
874
+ return undefined;
875
+ }
876
+
877
+ const swap1 = pattern[patternPos];
878
+ const swap2 = pattern[patternPos + 1];
879
+
880
+ if (swap1 === swap2) {
881
+ return undefined;
882
+ }
883
+
884
+ return pattern.slice(0, patternPos) + swap2 + swap1 + pattern.slice(patternPos + 2);
885
+ }
886
+
887
+ // #endregion