@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/strings.ts ADDED
@@ -0,0 +1,1002 @@
1
+ import { CharCode } from './charCode';
2
+ import { isMacintosh, isWindows } from './platform';
3
+ import { Constants } from './uint';
4
+ /**
5
+ * The empty string.
6
+ */
7
+ export const empty = '';
8
+
9
+ const hasTextEncoder = typeof TextEncoder !== 'undefined';
10
+ const hasTextDecoder = typeof TextDecoder !== 'undefined';
11
+
12
+ /**
13
+ * 浏览器全局可以直接使用 TextEncoder/TextDecoder
14
+ * Node.js 11+ 才可以全局使用,以下需要 require('util')
15
+ */
16
+ export namespace stringUtils {
17
+ export const StringTextEncoder = hasTextEncoder ? TextEncoder : require('util').TextEncoder;
18
+
19
+ export const StringTextDecoder = hasTextDecoder ? TextDecoder : require('util').TextDecoder;
20
+ }
21
+
22
+ export function isFalsyOrWhitespace(str: string | undefined): boolean {
23
+ if (!str || typeof str !== 'string') {
24
+ return true;
25
+ }
26
+ return str.trim().length === 0;
27
+ }
28
+
29
+ /**
30
+ * @returns the provided number with the given number of preceding zeros.
31
+ */
32
+ export function pad(n: number, l: number, char = '0'): string {
33
+ const str = '' + n;
34
+ const r = [str];
35
+
36
+ for (let i = str.length; i < l; i++) {
37
+ r.push(char);
38
+ }
39
+
40
+ return r.reverse().join('');
41
+ }
42
+
43
+ const _formatRegexp = /{(\d+)}/g;
44
+
45
+ /**
46
+ * Helper to produce a string with a variable number of arguments. Insert variable segments
47
+ * into the string using the {n} notation where N is the index of the argument following the string.
48
+ * @param value string to which formatting is applied
49
+ * @param args replacements for {n}-entries
50
+ */
51
+ export function format(value: string, ...args: any[]): string {
52
+ if (args.length === 0) {
53
+ return value;
54
+ }
55
+ return value.replace(_formatRegexp, function (match, group) {
56
+ const idx = parseInt(group, 10);
57
+ return isNaN(idx) || idx < 0 || idx >= args.length ? match : args[idx];
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Handles mnemonics for buttons. Depending on OS:
63
+ * - Windows: Supported via & character (replace && with & and & with && for escaping)
64
+ * - Linux: Supported via _ character (replace && with _)
65
+ * - macOS: Unsupported (replace && with empty string)
66
+ */
67
+ export function mnemonicButtonLabel(label: string, forceDisableMnemonics?: boolean): string {
68
+ if (isMacintosh || forceDisableMnemonics) {
69
+ return label.replace(/\(&&\w\)|&&/g, '');
70
+ }
71
+
72
+ if (isWindows) {
73
+ return label.replace(/&&|&/g, (m) => (m === '&' ? '&&' : '&'));
74
+ }
75
+
76
+ return label.replace(/&&/g, '_');
77
+ }
78
+
79
+ /**
80
+ * Converts HTML characters inside the string to use entities instead. Makes the string safe from
81
+ * being used e.g. in HTMLElement.innerHTML.
82
+ */
83
+ export function escape(html: string): string {
84
+ return html.replace(/[<>&]/g, function (match) {
85
+ switch (match) {
86
+ case '<':
87
+ return '&lt;';
88
+ case '>':
89
+ return '&gt;';
90
+ case '&':
91
+ return '&amp;';
92
+ default:
93
+ return match;
94
+ }
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Escapes regular expression characters in a given string
100
+ */
101
+ export function escapeRegExpCharacters(value: string): string {
102
+ return value.replace(/[\\\{\}\*\+\?\|\^\$\.\[\]\(\)]/g, '\\$&');
103
+ }
104
+
105
+ /**
106
+ * Counts how often `character` occurs inside `value`.
107
+ */
108
+ export function count(value: string, character: string): number {
109
+ let result = 0;
110
+ const ch = character.charCodeAt(0);
111
+ for (let i = value.length - 1; i >= 0; i--) {
112
+ if (value.charCodeAt(i) === ch) {
113
+ result++;
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+
119
+ /**
120
+ * Removes all occurrences of needle from the beginning and end of haystack.
121
+ * @param haystack string to trim
122
+ * @param needle the thing to trim (default is a blank)
123
+ */
124
+ export function trim(haystack: string, needle = ' '): string {
125
+ const trimmed = ltrim(haystack, needle);
126
+ return rtrim(trimmed, needle);
127
+ }
128
+
129
+ /**
130
+ * Removes all occurrences of needle from the beginning of haystack.
131
+ * @param haystack string to trim
132
+ * @param needle the thing to trim
133
+ */
134
+ export function ltrim(haystack: string, needle: string): string {
135
+ if (!haystack || !needle) {
136
+ return haystack;
137
+ }
138
+
139
+ const needleLen = needle.length;
140
+ if (needleLen === 0 || haystack.length === 0) {
141
+ return haystack;
142
+ }
143
+
144
+ let offset = 0;
145
+
146
+ while (haystack.indexOf(needle, offset) === offset) {
147
+ offset = offset + needleLen;
148
+ }
149
+ return haystack.substring(offset);
150
+ }
151
+
152
+ /**
153
+ * Removes all occurrences of needles from the end of haystack.
154
+ * @param source
155
+ * @param needles
156
+ * @example
157
+ * ```ts
158
+ * let source = '/path/to/file.ts,;';
159
+ * const res = multiRightTrim(source, [`,`, ';']);
160
+ * // res === '/path/to/file.ts';
161
+ * ```
162
+ */
163
+ export function multiRightTrim(source: string, needles: string[]): string {
164
+ let result = decodeURIComponent(source);
165
+ for (const needle of needles) {
166
+ result = rtrim(result, needle);
167
+ }
168
+ return result;
169
+ }
170
+
171
+ /**
172
+ * Removes all occurrences of needle from the end of haystack.
173
+ * @param haystack string to trim
174
+ * @param needle the thing to trim
175
+ */
176
+ export function rtrim(haystack: string, needle: string): string {
177
+ if (!haystack || !needle) {
178
+ return haystack;
179
+ }
180
+
181
+ const needleLen = needle.length;
182
+ const haystackLen = haystack.length;
183
+
184
+ if (needleLen === 0 || haystackLen === 0) {
185
+ return haystack;
186
+ }
187
+
188
+ let offset = haystackLen;
189
+ let idx = -1;
190
+
191
+ while (true) {
192
+ idx = haystack.lastIndexOf(needle, offset - 1);
193
+ if (idx === -1 || idx + needleLen !== offset) {
194
+ break;
195
+ }
196
+ if (idx === 0) {
197
+ return '';
198
+ }
199
+ offset = idx;
200
+ }
201
+
202
+ return haystack.substring(0, offset);
203
+ }
204
+
205
+ export function convertSimple2RegExpPattern(pattern: string): string {
206
+ return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*');
207
+ }
208
+
209
+ export function stripWildcards(pattern: string): string {
210
+ return pattern.replace(/\*/g, '');
211
+ }
212
+
213
+ /**
214
+ * Determines if haystack starts with needle.
215
+ */
216
+ export function startsWith(haystack: string, needle: string): boolean {
217
+ if (haystack.length < needle.length) {
218
+ return false;
219
+ }
220
+
221
+ if (haystack === needle) {
222
+ return true;
223
+ }
224
+
225
+ for (let i = 0; i < needle.length; i++) {
226
+ if (haystack[i] !== needle[i]) {
227
+ return false;
228
+ }
229
+ }
230
+
231
+ return true;
232
+ }
233
+
234
+ /**
235
+ * Determines if haystack ends with needle.
236
+ */
237
+ export function endsWith(haystack: string, needle: string): boolean {
238
+ const diff = haystack.length - needle.length;
239
+ if (diff > 0) {
240
+ return haystack.indexOf(needle, diff) === diff;
241
+ } else if (diff === 0) {
242
+ return haystack === needle;
243
+ } else {
244
+ return false;
245
+ }
246
+ }
247
+
248
+ export interface RegExpOptions {
249
+ matchCase?: boolean;
250
+ wholeWord?: boolean;
251
+ multiline?: boolean;
252
+ global?: boolean;
253
+ unicode?: boolean;
254
+ }
255
+
256
+ export function createRegExp(searchString: string, isRegex: boolean, options: RegExpOptions = {}): RegExp {
257
+ if (!searchString) {
258
+ throw new Error('Cannot create regex from empty string');
259
+ }
260
+ if (!isRegex) {
261
+ searchString = escapeRegExpCharacters(searchString);
262
+ }
263
+ if (options.wholeWord) {
264
+ if (!/\B/.test(searchString.charAt(0))) {
265
+ searchString = '\\b' + searchString;
266
+ }
267
+ if (!/\B/.test(searchString.charAt(searchString.length - 1))) {
268
+ searchString = searchString + '\\b';
269
+ }
270
+ }
271
+ let modifiers = '';
272
+ if (options.global) {
273
+ modifiers += 'g';
274
+ }
275
+ if (!options.matchCase) {
276
+ modifiers += 'i';
277
+ }
278
+ if (options.multiline) {
279
+ modifiers += 'm';
280
+ }
281
+ if (options.unicode) {
282
+ modifiers += 'u';
283
+ }
284
+
285
+ return new RegExp(searchString, modifiers);
286
+ }
287
+
288
+ export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean {
289
+ // Exit early if it's one of these special cases which are meant to match
290
+ // against an empty string
291
+ if (regexp.source === '^' || regexp.source === '^$' || regexp.source === '$' || regexp.source === '^\\s*$') {
292
+ return false;
293
+ }
294
+
295
+ // We check against an empty string. If the regular expression doesn't advance
296
+ // (e.g. ends in an endless loop) it will match an empty string.
297
+ const match = regexp.exec('');
298
+ return !!(match && regexp.lastIndex === 0);
299
+ }
300
+
301
+ export function regExpContainsBackreference(regexpValue: string): boolean {
302
+ return !!regexpValue.match(/([^\\]|^)(\\\\)*\\\d+/);
303
+ }
304
+
305
+ export function regExpFlags(regexp: RegExp): string {
306
+ return (
307
+ (regexp.global ? 'g' : '') +
308
+ (regexp.ignoreCase ? 'i' : '') +
309
+ (regexp.multiline ? 'm' : '') +
310
+ ((regexp as any).unicode ? 'u' : '')
311
+ );
312
+ }
313
+
314
+ /**
315
+ * Returns first index of the string that is not whitespace.
316
+ * If string is empty or contains only whitespaces, returns -1
317
+ */
318
+ export function firstNonWhitespaceIndex(str: string): number {
319
+ for (let i = 0, len = str.length; i < len; i++) {
320
+ const chCode = str.charCodeAt(i);
321
+ if (chCode !== CharCode.Space && chCode !== CharCode.Tab) {
322
+ return i;
323
+ }
324
+ }
325
+ return -1;
326
+ }
327
+
328
+ /**
329
+ * Returns the leading whitespace of the string.
330
+ * If the string contains only whitespaces, returns entire string
331
+ */
332
+ export function getLeadingWhitespace(str: string, start = 0, end: number = str.length): string {
333
+ for (let i = start; i < end; i++) {
334
+ const chCode = str.charCodeAt(i);
335
+ if (chCode !== CharCode.Space && chCode !== CharCode.Tab) {
336
+ return str.substring(start, i);
337
+ }
338
+ }
339
+ return str.substring(start, end);
340
+ }
341
+
342
+ /**
343
+ * Returns last index of the string that is not whitespace.
344
+ * If string is empty or contains only whitespaces, returns -1
345
+ */
346
+ export function lastNonWhitespaceIndex(str: string, startIndex: number = str.length - 1): number {
347
+ for (let i = startIndex; i >= 0; i--) {
348
+ const chCode = str.charCodeAt(i);
349
+ if (chCode !== CharCode.Space && chCode !== CharCode.Tab) {
350
+ return i;
351
+ }
352
+ }
353
+ return -1;
354
+ }
355
+
356
+ export function compare(a: string, b: string): number {
357
+ if (a < b) {
358
+ return -1;
359
+ } else if (a > b) {
360
+ return 1;
361
+ } else {
362
+ return 0;
363
+ }
364
+ }
365
+
366
+ export function compareIgnoreCase(a: string, b: string): number {
367
+ const len = Math.min(a.length, b.length);
368
+ for (let i = 0; i < len; i++) {
369
+ let codeA = a.charCodeAt(i);
370
+ let codeB = b.charCodeAt(i);
371
+
372
+ if (codeA === codeB) {
373
+ // equal
374
+ continue;
375
+ }
376
+
377
+ if (isUpperAsciiLetter(codeA)) {
378
+ codeA += 32;
379
+ }
380
+
381
+ if (isUpperAsciiLetter(codeB)) {
382
+ codeB += 32;
383
+ }
384
+
385
+ const diff = codeA - codeB;
386
+
387
+ if (diff === 0) {
388
+ // equal -> ignoreCase
389
+ continue;
390
+ } else if (isLowerAsciiLetter(codeA) && isLowerAsciiLetter(codeB)) {
391
+ //
392
+ return diff;
393
+ } else {
394
+ return compare(a.toLowerCase(), b.toLowerCase());
395
+ }
396
+ }
397
+
398
+ if (a.length < b.length) {
399
+ return -1;
400
+ } else if (a.length > b.length) {
401
+ return 1;
402
+ } else {
403
+ return 0;
404
+ }
405
+ }
406
+
407
+ export function isLowerAsciiLetter(code: number): boolean {
408
+ return code >= CharCode.a && code <= CharCode.z;
409
+ }
410
+
411
+ export function isUpperAsciiLetter(code: number): boolean {
412
+ return code >= CharCode.A && code <= CharCode.Z;
413
+ }
414
+
415
+ function isAsciiLetter(code: number): boolean {
416
+ return isLowerAsciiLetter(code) || isUpperAsciiLetter(code);
417
+ }
418
+
419
+ export function equalsIgnoreCase(a: string, b: string): boolean {
420
+ const len1 = a ? a.length : 0;
421
+ const len2 = b ? b.length : 0;
422
+
423
+ if (len1 !== len2) {
424
+ return false;
425
+ }
426
+
427
+ return doEqualsIgnoreCase(a, b);
428
+ }
429
+
430
+ function doEqualsIgnoreCase(a: string, b: string, stopAt = a.length): boolean {
431
+ if (typeof a !== 'string' || typeof b !== 'string') {
432
+ return false;
433
+ }
434
+
435
+ for (let i = 0; i < stopAt; i++) {
436
+ const codeA = a.charCodeAt(i);
437
+ const codeB = b.charCodeAt(i);
438
+
439
+ if (codeA === codeB) {
440
+ continue;
441
+ }
442
+
443
+ // a-z A-Z
444
+ if (isAsciiLetter(codeA) && isAsciiLetter(codeB)) {
445
+ const diff = Math.abs(codeA - codeB);
446
+ if (diff !== 0 && diff !== 32) {
447
+ return false;
448
+ }
449
+ } else {
450
+ // Any other charcode
451
+ if (String.fromCharCode(codeA).toLowerCase() !== String.fromCharCode(codeB).toLowerCase()) {
452
+ return false;
453
+ }
454
+ }
455
+ }
456
+
457
+ return true;
458
+ }
459
+
460
+ export function startsWithIgnoreCase(str: string, candidate: string): boolean {
461
+ const candidateLength = candidate.length;
462
+ if (candidate.length > str.length) {
463
+ return false;
464
+ }
465
+
466
+ return doEqualsIgnoreCase(str, candidate, candidateLength);
467
+ }
468
+
469
+ /**
470
+ * @returns the length of the common prefix of the two strings.
471
+ */
472
+ export function commonPrefixLength(a: string, b: string): number {
473
+ let i: number;
474
+ const len = Math.min(a.length, b.length);
475
+
476
+ for (i = 0; i < len; i++) {
477
+ if (a.charCodeAt(i) !== b.charCodeAt(i)) {
478
+ return i;
479
+ }
480
+ }
481
+
482
+ return len;
483
+ }
484
+
485
+ /**
486
+ * @returns the length of the common suffix of the two strings.
487
+ */
488
+ export function commonSuffixLength(a: string, b: string): number {
489
+ let i: number;
490
+ const len = Math.min(a.length, b.length);
491
+
492
+ const aLastIndex = a.length - 1;
493
+ const bLastIndex = b.length - 1;
494
+
495
+ for (i = 0; i < len; i++) {
496
+ if (a.charCodeAt(aLastIndex - i) !== b.charCodeAt(bLastIndex - i)) {
497
+ return i;
498
+ }
499
+ }
500
+
501
+ return len;
502
+ }
503
+
504
+ function substrEquals(a: string, aStart: number, aEnd: number, b: string, bStart: number, bEnd: number): boolean {
505
+ while (aStart < aEnd && bStart < bEnd) {
506
+ if (a[aStart] !== b[bStart]) {
507
+ return false;
508
+ }
509
+ aStart += 1;
510
+ bStart += 1;
511
+ }
512
+ return true;
513
+ }
514
+
515
+ /**
516
+ * Return the overlap between the suffix of `a` and the prefix of `b`.
517
+ * For instance `overlap("foobar", "arr, I'm a pirate") === 2`.
518
+ */
519
+ export function overlap(a: string, b: string): number {
520
+ const aEnd = a.length;
521
+ let bEnd = b.length;
522
+ let aStart = aEnd - bEnd;
523
+
524
+ if (aStart === 0) {
525
+ return a === b ? aEnd : 0;
526
+ } else if (aStart < 0) {
527
+ bEnd += aStart;
528
+ aStart = 0;
529
+ }
530
+
531
+ while (aStart < aEnd && bEnd > 0) {
532
+ if (substrEquals(a, aStart, aEnd, b, 0, bEnd)) {
533
+ return bEnd;
534
+ }
535
+ bEnd -= 1;
536
+ aStart += 1;
537
+ }
538
+ return 0;
539
+ }
540
+
541
+ // --- unicode
542
+ // http://en.wikipedia.org/wiki/Surrogate_pair
543
+ // Returns the code point starting at a specified index in a string
544
+ // Code points U+0000 to U+D7FF and U+E000 to U+FFFF are represented on a single character
545
+ // Code points U+10000 to U+10FFFF are represented on two consecutive characters
546
+ // export function getUnicodePoint(str:string, index:number, len:number):number {
547
+ // const chrCode = str.charCodeAt(index);
548
+ // if (0xD800 <= chrCode && chrCode <= 0xDBFF && index + 1 < len) {
549
+ // const nextChrCode = str.charCodeAt(index + 1);
550
+ // if (0xDC00 <= nextChrCode && nextChrCode <= 0xDFFF) {
551
+ // return (chrCode - 0xD800) << 10 + (nextChrCode - 0xDC00) + 0x10000;
552
+ // }
553
+ // }
554
+ // return chrCode;
555
+ // }
556
+ export function isHighSurrogate(charCode: number): boolean {
557
+ return 0xd800 <= charCode && charCode <= 0xdbff;
558
+ }
559
+
560
+ export function isLowSurrogate(charCode: number): boolean {
561
+ return 0xdc00 <= charCode && charCode <= 0xdfff;
562
+ }
563
+
564
+ /**
565
+ * Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-rtl-test.js
566
+ */
567
+ const CONTAINS_RTL =
568
+ /(?:[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u0710\u0712-\u072F\u074D-\u07A5\u07B1-\u07EA\u07F4\u07F5\u07FA-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u08BD\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE33\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDCFF]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD50-\uDFFF]|\uD83B[\uDC00-\uDEBB])/;
569
+
570
+ /**
571
+ * Returns true if `str` contains any Unicode character that is classified as "R" or "AL".
572
+ */
573
+ export function containsRTL(str: string): boolean {
574
+ return CONTAINS_RTL.test(str);
575
+ }
576
+
577
+ /**
578
+ * Generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-emoji-test.js
579
+ */
580
+ const CONTAINS_EMOJI =
581
+ /(?:[\u231A\u231B\u23F0\u23F3\u2600-\u27BF\u2B50\u2B55]|\uD83C[\uDDE6-\uDDFF\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F\uDE80-\uDEF8]|\uD83E[\uDD00-\uDDE6])/;
582
+
583
+ export function containsEmoji(str: string): boolean {
584
+ return CONTAINS_EMOJI.test(str);
585
+ }
586
+
587
+ const IS_BASIC_ASCII = /^[\t\n\r\x20-\x7E]*$/;
588
+ /**
589
+ * Returns true if `str` contains only basic ASCII characters in the range 32 - 126 (including 32 and 126) or \n, \r, \t
590
+ */
591
+ export function isBasicASCII(str: string): boolean {
592
+ return IS_BASIC_ASCII.test(str);
593
+ }
594
+
595
+ export function containsFullWidthCharacter(str: string): boolean {
596
+ for (let i = 0, len = str.length; i < len; i++) {
597
+ if (isFullWidthCharacter(str.charCodeAt(i))) {
598
+ return true;
599
+ }
600
+ }
601
+ return false;
602
+ }
603
+
604
+ export function isFullWidthCharacter(charCode: number): boolean {
605
+ // Do a cheap trick to better support wrapping of wide characters, treat them as 2 columns
606
+ // http://jrgraphix.net/research/unicode_blocks.php
607
+ // 2E80 — 2EFF CJK Radicals Supplement
608
+ // 2F00 — 2FDF Kangxi Radicals
609
+ // 2FF0 — 2FFF Ideographic Description Characters
610
+ // 3000 — 303F CJK Symbols and Punctuation
611
+ // 3040 — 309F Hiragana
612
+ // 30A0 — 30FF Katakana
613
+ // 3100 — 312F Bopomofo
614
+ // 3130 — 318F Hangul Compatibility Jamo
615
+ // 3190 — 319F Kanbun
616
+ // 31A0 — 31BF Bopomofo Extended
617
+ // 31F0 — 31FF Katakana Phonetic Extensions
618
+ // 3200 — 32FF Enclosed CJK Letters and Months
619
+ // 3300 — 33FF CJK Compatibility
620
+ // 3400 — 4DBF CJK Unified Ideographs Extension A
621
+ // 4DC0 — 4DFF Yijing Hexagram Symbols
622
+ // 4E00 — 9FFF CJK Unified Ideographs
623
+ // A000 — A48F Yi Syllables
624
+ // A490 — A4CF Yi Radicals
625
+ // AC00 — D7AF Hangul Syllables
626
+ // [IGNORE] D800 — DB7F High Surrogates
627
+ // [IGNORE] DB80 — DBFF High Private Use Surrogates
628
+ // [IGNORE] DC00 — DFFF Low Surrogates
629
+ // [IGNORE] E000 — F8FF Private Use Area
630
+ // F900 — FAFF CJK Compatibility Ideographs
631
+ // [IGNORE] FB00 — FB4F Alphabetic Presentation Forms
632
+ // [IGNORE] FB50 — FDFF Arabic Presentation Forms-A
633
+ // [IGNORE] FE00 — FE0F Variation Selectors
634
+ // [IGNORE] FE20 — FE2F Combining Half Marks
635
+ // [IGNORE] FE30 — FE4F CJK Compatibility Forms
636
+ // [IGNORE] FE50 — FE6F Small Form Variants
637
+ // [IGNORE] FE70 — FEFF Arabic Presentation Forms-B
638
+ // FF00 — FFEF Halfwidth and Fullwidth Forms
639
+ // [https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms]
640
+ // of which FF01 - FF5E fullwidth ASCII of 21 to 7E
641
+ // [IGNORE] and FF65 - FFDC halfwidth of Katakana and Hangul
642
+ // [IGNORE] FFF0 — FFFF Specials
643
+ charCode = +charCode; // @perf
644
+ return (
645
+ (charCode >= 0x2e80 && charCode <= 0xd7af) ||
646
+ (charCode >= 0xf900 && charCode <= 0xfaff) ||
647
+ (charCode >= 0xff01 && charCode <= 0xff5e)
648
+ );
649
+ }
650
+
651
+ /**
652
+ * Given a string and a max length returns a shorted version. Shorting
653
+ * happens at favorable positions - such as whitespace or punctuation characters.
654
+ */
655
+ export function lcut(text: string, n: number) {
656
+ if (text.length < n) {
657
+ return text;
658
+ }
659
+
660
+ const re = /\b/g;
661
+ let i = 0;
662
+ while (re.test(text)) {
663
+ if (text.length - re.lastIndex < n) {
664
+ break;
665
+ }
666
+
667
+ i = re.lastIndex;
668
+ re.lastIndex += 1;
669
+ }
670
+
671
+ return text.substring(i).replace(/^\s/, empty);
672
+ }
673
+
674
+ // Escape codes
675
+ // http://en.wikipedia.org/wiki/ANSI_escape_code
676
+ const EL = /\x1B\x5B[12]?K/g; // Erase in line
677
+ const COLOR_START = /\x1b\[\d+m/g; // Color
678
+ const COLOR_END = /\x1b\[0?m/g; // Color
679
+
680
+ export function removeAnsiEscapeCodes(str: string): string {
681
+ if (str) {
682
+ str = str.replace(EL, '');
683
+ str = str.replace(COLOR_START, '');
684
+ str = str.replace(COLOR_END, '');
685
+ }
686
+
687
+ return str;
688
+ }
689
+
690
+ export const removeAccents: (str: string) => string = (function () {
691
+ if (typeof (String.prototype as any).normalize !== 'function') {
692
+ // ☹️ no ES6 features...
693
+ return function (str: string) {
694
+ return str;
695
+ };
696
+ } else {
697
+ // transform into NFD form and remove accents
698
+ // see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463
699
+ const regex = /[\u0300-\u036f]/g;
700
+ return function (str: string) {
701
+ return (str as any).normalize('NFD').replace(regex, empty);
702
+ };
703
+ }
704
+ })();
705
+
706
+ // -- UTF-8 BOM
707
+
708
+ export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM);
709
+
710
+ export function startsWithUTF8BOM(str: string): boolean {
711
+ return !!(str && str.length > 0 && str.charCodeAt(0) === CharCode.UTF8_BOM);
712
+ }
713
+
714
+ export function stripUTF8BOM(str: string): string {
715
+ return startsWithUTF8BOM(str) ? str.substr(1) : str;
716
+ }
717
+
718
+ export function safeBtoa(str: string): string {
719
+ return btoa(encodeURIComponent(str)); // we use encodeURIComponent because btoa fails for non Latin 1 values
720
+ }
721
+
722
+ export function repeat(s: string, count: number): string {
723
+ let result = '';
724
+ for (let i = 0; i < count; i++) {
725
+ result += s;
726
+ }
727
+ return result;
728
+ }
729
+
730
+ /**
731
+ * Checks if the characters of the provided query string are included in the
732
+ * target string. The characters do not have to be contiguous within the string.
733
+ */
734
+ export function fuzzyContains(target: string, query: string): boolean {
735
+ if (!target || !query) {
736
+ return false; // return early if target or query are undefined
737
+ }
738
+
739
+ if (target.length < query.length) {
740
+ return false; // impossible for query to be contained in target
741
+ }
742
+
743
+ const queryLen = query.length;
744
+ const targetLower = target.toLowerCase();
745
+
746
+ let index = 0;
747
+ let lastIndexOf = -1;
748
+ while (index < queryLen) {
749
+ const indexOf = targetLower.indexOf(query[index], lastIndexOf + 1);
750
+ if (indexOf < 0) {
751
+ return false;
752
+ }
753
+
754
+ lastIndexOf = indexOf;
755
+
756
+ index++;
757
+ }
758
+
759
+ return true;
760
+ }
761
+
762
+ export function containsUppercaseCharacter(target: string, ignoreEscapedChars = false): boolean {
763
+ if (!target) {
764
+ return false;
765
+ }
766
+
767
+ if (ignoreEscapedChars) {
768
+ target = target.replace(/\\./g, '');
769
+ }
770
+
771
+ return target.toLowerCase() !== target;
772
+ }
773
+
774
+ export function uppercaseFirstLetter(str: string): string {
775
+ return str.charAt(0).toUpperCase() + str.slice(1);
776
+ }
777
+
778
+ export function getNLines(str: string, n = 1): string {
779
+ if (n === 0) {
780
+ return '';
781
+ }
782
+
783
+ let idx = -1;
784
+ do {
785
+ idx = str.indexOf('\n', idx + 1);
786
+ n--;
787
+ } while (n > 0 && idx >= 0);
788
+
789
+ return idx >= 0 ? str.substr(0, idx) : str;
790
+ }
791
+
792
+ /**
793
+ * See http://en.wikipedia.org/wiki/Surrogate_pair
794
+ */
795
+ export function computeCodePoint(highSurrogate: number, lowSurrogate: number): number {
796
+ return ((highSurrogate - 0xd800) << 10) + (lowSurrogate - 0xdc00) + 0x10000;
797
+ }
798
+
799
+ /**
800
+ * get the code point that begins at offset `offset`
801
+ */
802
+ export function getNextCodePoint(str: string, len: number, offset: number): number {
803
+ const charCode = str.charCodeAt(offset);
804
+ if (isHighSurrogate(charCode) && offset + 1 < len) {
805
+ const nextCharCode = str.charCodeAt(offset + 1);
806
+ if (isLowSurrogate(nextCharCode)) {
807
+ return computeCodePoint(charCode, nextCharCode);
808
+ }
809
+ }
810
+ return charCode;
811
+ }
812
+
813
+ /**
814
+ * A manual encoding of `str` to UTF8.
815
+ * Use only in environments which do not offer native conversion methods!
816
+ */
817
+ export function encodeUTF8(str: string): Uint8Array {
818
+ const strLen = str.length;
819
+
820
+ // See https://en.wikipedia.org/wiki/UTF-8
821
+
822
+ // first loop to establish needed buffer size
823
+ let neededSize = 0;
824
+ let strOffset = 0;
825
+ while (strOffset < strLen) {
826
+ const codePoint = getNextCodePoint(str, strLen, strOffset);
827
+ strOffset += codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1;
828
+
829
+ if (codePoint < 0x0080) {
830
+ neededSize += 1;
831
+ } else if (codePoint < 0x0800) {
832
+ neededSize += 2;
833
+ } else if (codePoint < 0x10000) {
834
+ neededSize += 3;
835
+ } else {
836
+ neededSize += 4;
837
+ }
838
+ }
839
+
840
+ // second loop to actually encode
841
+ const arr = new Uint8Array(neededSize);
842
+ strOffset = 0;
843
+ let arrOffset = 0;
844
+ while (strOffset < strLen) {
845
+ const codePoint = getNextCodePoint(str, strLen, strOffset);
846
+ strOffset += codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1;
847
+
848
+ if (codePoint < 0x0080) {
849
+ arr[arrOffset++] = codePoint;
850
+ } else if (codePoint < 0x0800) {
851
+ arr[arrOffset++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);
852
+ arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
853
+ } else if (codePoint < 0x10000) {
854
+ arr[arrOffset++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);
855
+ arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
856
+ arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
857
+ } else {
858
+ arr[arrOffset++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);
859
+ arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);
860
+ arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
861
+ arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
862
+ }
863
+ }
864
+
865
+ return arr;
866
+ }
867
+
868
+ /**
869
+ * A manual decoding of a UTF8 string.
870
+ * Use only in environments which do not offer native conversion methods!
871
+ */
872
+ export function decodeUTF8(buffer: Uint8Array): string {
873
+ // https://en.wikipedia.org/wiki/UTF-8
874
+
875
+ const len = buffer.byteLength;
876
+ const result: string[] = [];
877
+ let offset = 0;
878
+ while (offset < len) {
879
+ const v0 = buffer[offset];
880
+ let codePoint: number;
881
+ if (v0 >= 0b11110000 && offset + 3 < len) {
882
+ // 4 bytes
883
+ codePoint =
884
+ (((buffer[offset++] & 0b00000111) << 18) >>> 0) |
885
+ (((buffer[offset++] & 0b00111111) << 12) >>> 0) |
886
+ (((buffer[offset++] & 0b00111111) << 6) >>> 0) |
887
+ (((buffer[offset++] & 0b00111111) << 0) >>> 0);
888
+ } else if (v0 >= 0b11100000 && offset + 2 < len) {
889
+ // 3 bytes
890
+ codePoint =
891
+ (((buffer[offset++] & 0b00001111) << 12) >>> 0) |
892
+ (((buffer[offset++] & 0b00111111) << 6) >>> 0) |
893
+ (((buffer[offset++] & 0b00111111) << 0) >>> 0);
894
+ } else if (v0 >= 0b11000000 && offset + 1 < len) {
895
+ // 2 bytes
896
+ codePoint = (((buffer[offset++] & 0b00011111) << 6) >>> 0) | (((buffer[offset++] & 0b00111111) << 0) >>> 0);
897
+ } else {
898
+ // 1 byte
899
+ codePoint = buffer[offset++];
900
+ }
901
+
902
+ if ((codePoint >= 0 && codePoint <= 0xd7ff) || (codePoint >= 0xe000 && codePoint <= 0xffff)) {
903
+ // Basic Multilingual Plane
904
+ result.push(String.fromCharCode(codePoint));
905
+ } else if (codePoint >= 0x010000 && codePoint <= 0x10ffff) {
906
+ // Supplementary Planes
907
+ const uPrime = codePoint - 0x10000;
908
+ const w1 = 0xd800 + ((uPrime & 0b11111111110000000000) >>> 10);
909
+ const w2 = 0xdc00 + ((uPrime & 0b00000000001111111111) >>> 0);
910
+ result.push(String.fromCharCode(w1));
911
+ result.push(String.fromCharCode(w2));
912
+ } else {
913
+ // illegal code point
914
+ result.push(String.fromCharCode(0xfffd));
915
+ }
916
+ }
917
+
918
+ return result.join('');
919
+ }
920
+
921
+ interface ITemplateOptions {
922
+ /**
923
+ * 分割符,函数会确保多个分隔符不会连在一起
924
+ */
925
+ separator: string;
926
+ /**
927
+ * 如果该占位符不存在则使用 defaultValue 进行替换,默认为 undefined,即为空字符串
928
+ */
929
+ defaultValue?: string;
930
+ }
931
+
932
+ /**
933
+ * 插值表达式的标记使用的是 ${}
934
+ * 该函数会对 options 中的 separator 会有特殊处理,
935
+ */
936
+ export function template(tpl: string, variables: Record<string, any>, options: ITemplateOptions) {
937
+ const result = [] as string[];
938
+ let placeHolderStack = [] as string[];
939
+
940
+ for (let idx = 0; idx < tpl.length; idx++) {
941
+ const char = tpl[idx];
942
+ const nextChar = tpl[idx + 1];
943
+
944
+ // 往后多看一位
945
+ if (char === '$' && nextChar === '{') {
946
+ // 往后的可能是占位符了,注入进栈标志位(即 $)
947
+ // 如果 placeHolder 栈已经有值了,现在不支持嵌套 ${},直接吐出所有值放到 result 中即可
948
+ if (placeHolderStack.length > 0) {
949
+ result.push(...placeHolderStack);
950
+ placeHolderStack = [];
951
+ }
952
+ placeHolderStack.push(char);
953
+ placeHolderStack.push(nextChar);
954
+ idx++;
955
+ continue;
956
+ }
957
+
958
+ // 如果当前 placeHolder 栈有字符,一直将字符入栈,直到匹配到 }
959
+ if (placeHolderStack.length > 0) {
960
+ if (char === '}') {
961
+ // 占位符匹配结束
962
+ // 拿出占位符进行值替换
963
+ const placeholder = placeHolderStack.slice(2).join('');
964
+ let v: string | undefined;
965
+
966
+ if (placeholder === 'separator') {
967
+ if (result[result.length - 1] === options.separator) {
968
+ // 不需要重复 separator
969
+ placeHolderStack = [];
970
+ continue;
971
+ }
972
+ // 分隔符有单独的优化
973
+ v = options.separator;
974
+ } else {
975
+ v = variables[placeholder];
976
+ }
977
+ const toPush = v ?? options.defaultValue;
978
+ if (toPush) {
979
+ result.push(toPush);
980
+ }
981
+ placeHolderStack = [];
982
+ } else {
983
+ placeHolderStack.push(char);
984
+ }
985
+ continue;
986
+ }
987
+
988
+ result.push(tpl[idx]);
989
+ }
990
+
991
+ // 去除前面和后面的 sep
992
+ // 这些 sep 也是不需要的
993
+ while (result[result.length - 1] === options.separator) {
994
+ result.pop();
995
+ }
996
+
997
+ while (result[0] === options.separator) {
998
+ result.shift();
999
+ }
1000
+
1001
+ return result.join('');
1002
+ }