@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
@@ -0,0 +1,377 @@
1
+ /** ******************************************************************************
2
+ * Copyright (C) 2017 TypeFox and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * This Source Code may also be made available under the following Secondary
9
+ * Licenses when the conditions for such availability set forth in the Eclipse
10
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ * with the GNU Classpath Exception which is available at
12
+ * https://www.gnu.org/software/classpath/license.html.
13
+ *
14
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+ import { MaybePromise } from './async';
17
+ import { Event, Emitter } from './event';
18
+
19
+ // DisposableStore 是从 vscode lifecycle 中复制而来
20
+ export class DisposableStore implements IDisposable {
21
+ private toDispose = new Set<IDisposable>();
22
+ private _isDisposed = false;
23
+
24
+ /**
25
+ * Dispose of all registered disposables and mark this object as disposed.
26
+ *
27
+ * Any future disposables added to this object will be disposed of on `add`.
28
+ */
29
+ public dispose(): void {
30
+ if (this._isDisposed) {
31
+ return;
32
+ }
33
+
34
+ markTracked(this);
35
+ this._isDisposed = true;
36
+ this.clear();
37
+ }
38
+
39
+ /**
40
+ * Dispose of all registered disposables but do not mark this object as disposed.
41
+ */
42
+ public clear(): void {
43
+ this.toDispose.forEach((item) => item.dispose());
44
+ this.toDispose.clear();
45
+ }
46
+
47
+ public add<T extends IDisposable>(t: T): T {
48
+ if (!t) {
49
+ return t;
50
+ }
51
+ if ((t as any as DisposableStore) === this) {
52
+ throw new Error('Cannot register a disposable on itself!');
53
+ }
54
+
55
+ markTracked(t);
56
+ if (this._isDisposed) {
57
+ // eslint-disable-next-line no-console
58
+ console.warn(new Error('Registering disposable on object that has already been disposed of').stack);
59
+ t.dispose();
60
+ } else {
61
+ this.toDispose.add(t);
62
+ }
63
+
64
+ return t;
65
+ }
66
+ }
67
+
68
+ export interface IDisposable {
69
+ /**
70
+ * Dispose this object.
71
+ */
72
+ dispose(): void;
73
+ }
74
+
75
+ export function isDisposable<E extends object>(thing: E): thing is E & IDisposable {
76
+ return typeof (thing as IDisposable).dispose === 'function' && (thing as IDisposable).dispose.length === 0;
77
+ }
78
+
79
+ export function dispose<T extends IDisposable>(disposable: T): T;
80
+ export function dispose<T extends IDisposable>(...disposables: Array<T | undefined>): T[];
81
+ export function dispose<T extends IDisposable>(disposables: T[]): T[];
82
+ export function dispose<T extends IDisposable>(first: T | T[], ...rest: T[]): T | T[] | undefined {
83
+ if (Array.isArray(first)) {
84
+ first.forEach((d) => {
85
+ if (d) {
86
+ markTracked(d);
87
+ d.dispose();
88
+ }
89
+ });
90
+ return [];
91
+ } else if (rest.length === 0) {
92
+ if (first) {
93
+ markTracked(first);
94
+ first.dispose();
95
+ return first;
96
+ }
97
+ return undefined;
98
+ } else {
99
+ dispose(first);
100
+ dispose(rest);
101
+ return [];
102
+ }
103
+ }
104
+
105
+ export function combinedDisposable(disposables: IDisposable[]): IDisposable {
106
+ disposables.forEach(markTracked);
107
+ return trackDisposable({ dispose: () => dispose(disposables) });
108
+ }
109
+
110
+ export function toDisposable(fn: () => void): IDisposable {
111
+ return {
112
+ dispose() {
113
+ fn();
114
+ },
115
+ };
116
+ }
117
+
118
+ export class Disposable implements IDisposable {
119
+ protected readonly disposables: IDisposable[] = [];
120
+ protected readonly onDisposeEmitter = new Emitter<void>();
121
+
122
+ constructor(...toDispose: IDisposable[]) {
123
+ toDispose.forEach((d) => this.addDispose(d));
124
+ }
125
+
126
+ static create(func: () => void): IDisposable {
127
+ return {
128
+ dispose: func,
129
+ };
130
+ }
131
+
132
+ static NULL = Disposable.create(() => {});
133
+
134
+ static None = Object.freeze<IDisposable>({ dispose() {} });
135
+
136
+ get onDispose(): Event<void> {
137
+ return this.onDisposeEmitter.event;
138
+ }
139
+
140
+ protected checkDisposed(): void {
141
+ if (this.disposed && !this.disposingElements) {
142
+ this.onDisposeEmitter.fire(undefined);
143
+ }
144
+ }
145
+
146
+ get disposed(): boolean {
147
+ return this.disposables.length === 0;
148
+ }
149
+
150
+ private disposingElements = false;
151
+ dispose(): void {
152
+ if (this.disposed || this.disposingElements) {
153
+ return;
154
+ }
155
+ this.disposingElements = true;
156
+ while (!this.disposed) {
157
+ try {
158
+ this.disposables.pop()!.dispose();
159
+ } catch (e) {
160
+ // eslint-disable-next-line no-console
161
+ console.error(e);
162
+ }
163
+ }
164
+ this.disposingElements = false;
165
+ this.checkDisposed();
166
+ }
167
+
168
+ addDispose(disposable: IDisposable): IDisposable;
169
+ addDispose(disposable: IDisposable[]): IDisposable[];
170
+ addDispose(disposable: IDisposable | IDisposable[]): IDisposable | IDisposable[] {
171
+ if (Array.isArray(disposable)) {
172
+ const disposables = disposable;
173
+ return disposables.map((disposable) => this.addDispose(disposable));
174
+ } else {
175
+ return this.add(disposable);
176
+ }
177
+ }
178
+
179
+ protected registerDispose<T extends IDisposable>(disposable: T): T {
180
+ if ((disposable as any as Disposable) === this) {
181
+ throw new Error('Cannot register a disposable on itself!');
182
+ }
183
+
184
+ this.add(disposable);
185
+ return disposable;
186
+ }
187
+
188
+ private add(disposable: IDisposable): IDisposable {
189
+ const disposables = this.disposables;
190
+ disposables.push(disposable);
191
+ const originalDispose = disposable.dispose.bind(disposable);
192
+ const toRemove = Disposable.create(() => {
193
+ const index = disposables.indexOf(disposable);
194
+ if (index !== -1) {
195
+ disposables.splice(index, 1);
196
+ }
197
+ this.checkDisposed();
198
+ });
199
+ disposable.dispose = () => {
200
+ toRemove.dispose();
201
+ originalDispose();
202
+ };
203
+ return toRemove;
204
+ }
205
+ }
206
+
207
+ export class DisposableCollection implements IDisposable {
208
+ protected readonly disposables: IDisposable[] = [];
209
+ protected readonly onDisposeEmitter = new Emitter<void>();
210
+
211
+ constructor(...toDispose: IDisposable[]) {
212
+ toDispose.forEach((d) => this.push(d));
213
+ }
214
+
215
+ get onDispose(): Event<void> {
216
+ return this.onDisposeEmitter.event;
217
+ }
218
+
219
+ protected checkDisposed(): void {
220
+ if (this.disposed && !this.disposingElements) {
221
+ this.onDisposeEmitter.fire(undefined);
222
+ }
223
+ }
224
+
225
+ get disposed(): boolean {
226
+ return this.disposables.length === 0;
227
+ }
228
+
229
+ private disposingElements = false;
230
+ dispose(): void {
231
+ if (this.disposed || this.disposingElements) {
232
+ return;
233
+ }
234
+ const toPromise = [] as any[];
235
+ this.disposingElements = true;
236
+ while (!this.disposed) {
237
+ try {
238
+ const maybePromise = this.disposables.pop()!.dispose() as MaybePromise<void>;
239
+ if (maybePromise) {
240
+ toPromise.push(maybePromise);
241
+ }
242
+ } catch (e) {
243
+ // eslint-disable-next-line no-console
244
+ console.error('DisposableCollection.dispose error', e);
245
+ }
246
+ }
247
+ this.disposingElements = false;
248
+ this.checkDisposed();
249
+ return Promise.all(toPromise) as unknown as MaybePromise<void> as void;
250
+ }
251
+
252
+ push(disposable: IDisposable): IDisposable {
253
+ const disposables = this.disposables;
254
+ disposables.push(disposable);
255
+ const originalDispose = disposable.dispose.bind(disposable);
256
+ const toRemove = Disposable.create(() => {
257
+ const index = disposables.indexOf(disposable);
258
+ if (index !== -1) {
259
+ disposables.splice(index, 1);
260
+ }
261
+ this.checkDisposed();
262
+ });
263
+ disposable.dispose = () => {
264
+ toRemove.dispose();
265
+ originalDispose();
266
+ };
267
+ return toRemove;
268
+ }
269
+
270
+ pushAll(disposables: IDisposable[]): IDisposable[] {
271
+ return disposables.map((disposable) => this.push(disposable));
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Enables logging of potentially leaked disposables.
277
+ *
278
+ * A disposable is considered leaked if it is not disposed or not registered as the child of
279
+ * another disposable. This tracking is very simple an only works for classes that either
280
+ * extend Disposable or use a DisposableStore. This means there are a lot of false positives.
281
+ */
282
+ const TRACK_DISPOSABLES = false;
283
+
284
+ const __is_disposable_tracked__ = '__is_disposable_tracked__';
285
+
286
+ function markTracked<T extends IDisposable>(x: T): void {
287
+ if (!TRACK_DISPOSABLES) {
288
+ return;
289
+ }
290
+
291
+ if (x && x !== Disposable.None) {
292
+ try {
293
+ (x as any)[__is_disposable_tracked__] = true;
294
+ } catch (_e) {
295
+ // noop
296
+ }
297
+ }
298
+ }
299
+
300
+ function trackDisposable<T extends IDisposable>(x: T): T {
301
+ if (!TRACK_DISPOSABLES) {
302
+ return x;
303
+ }
304
+
305
+ const stack = new Error('Potentially leaked disposable').stack!;
306
+ setTimeout(() => {
307
+ if (!(x as any)[__is_disposable_tracked__]) {
308
+ // eslint-disable-next-line no-console
309
+ console.log(stack);
310
+ }
311
+ }, 3000);
312
+ return x;
313
+ }
314
+
315
+ /**
316
+ * Manages the lifecycle of a disposable value that may be changed.
317
+ *
318
+ * This ensures that when the the disposable value is changed, the previously held disposable is disposed of. You can
319
+ * also register a `MutableDisposable` on a `Disposable` to ensure it is automatically cleaned up.
320
+ */
321
+ export class MutableDisposable<T extends IDisposable> implements IDisposable {
322
+ private _value?: T;
323
+ private _isDisposed = false;
324
+
325
+ constructor() {
326
+ trackDisposable(this);
327
+ }
328
+
329
+ get value(): T | undefined {
330
+ return this._isDisposed ? undefined : this._value;
331
+ }
332
+
333
+ set value(value: T | undefined) {
334
+ if (this._isDisposed || value === this._value) {
335
+ return;
336
+ }
337
+
338
+ if (this._value) {
339
+ this._value.dispose();
340
+ }
341
+ if (value) {
342
+ markTracked(value);
343
+ }
344
+ this._value = value;
345
+ }
346
+
347
+ clear() {
348
+ this.value = undefined;
349
+ }
350
+
351
+ dispose(): void {
352
+ this._isDisposed = true;
353
+ markTracked(this);
354
+ if (this._value) {
355
+ this._value.dispose();
356
+ }
357
+ this._value = undefined;
358
+ }
359
+ }
360
+
361
+ export class RefCountedDisposable {
362
+ private _counter = 1;
363
+
364
+ constructor(private readonly _disposable: IDisposable) {}
365
+
366
+ acquire() {
367
+ this._counter++;
368
+ return this;
369
+ }
370
+
371
+ release() {
372
+ if (--this._counter === 0) {
373
+ this._disposable.dispose();
374
+ }
375
+ return this;
376
+ }
377
+ }
@@ -0,0 +1,278 @@
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
+ import iconv from 'iconv-lite';
6
+
7
+ import type { BinaryBuffer } from './buffer';
8
+ import { SUPPORTED_ENCODINGS } from './const';
9
+
10
+ export const UTF8 = 'utf8';
11
+ export const UTF8_with_bom = 'utf8bom';
12
+ export const UTF16be = 'utf16be';
13
+ export const UTF16le = 'utf16le';
14
+
15
+ export const UTF16be_BOM = [0xfe, 0xff];
16
+ export const UTF16le_BOM = [0xff, 0xfe];
17
+ export const UTF8_BOM = [0xef, 0xbb, 0xbf];
18
+
19
+ const ZERO_BYTE_DETECTION_BUFFER_MAX_LEN = 512; // number of bytes to look at to decide about a file being binary or not
20
+ const AUTO_ENCODING_GUESS_MAX_BYTES = 512 * 128; // set an upper limit for the number of bytes we pass on to jschardet
21
+
22
+ export function isUTF8(encoding: string | null) {
23
+ if (encoding) {
24
+ return toIconvLiteEncoding(encoding) === UTF8;
25
+ }
26
+ return false;
27
+ }
28
+
29
+ const SUPPORT_ENCODINGS_TO_ICONV_ENCODINGS: { [name: string]: string } = {
30
+ ibm866: 'cp866',
31
+ big5: 'cp950',
32
+ utf8bom: 'utf8',
33
+ };
34
+
35
+ export function toIconvLiteEncoding(encodingName: string): string {
36
+ const normalizedEncodingName = encodingName.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
37
+ const mapped = SUPPORT_ENCODINGS_TO_ICONV_ENCODINGS[normalizedEncodingName];
38
+
39
+ return mapped || normalizedEncodingName;
40
+ }
41
+
42
+ /**
43
+ * The encodings that are allowed in a settings file don't match the canonical encoding labels specified by WHATWG.
44
+ * See https://encoding.spec.whatwg.org/#names-and-labels
45
+ * Iconv-lite strips all non-alphanumeric characters, but ripgrep doesn't. For backcompat, allow these labels.
46
+ */
47
+ export function toCanonicalName(enc: string): string {
48
+ switch (enc) {
49
+ case 'shiftjis':
50
+ return 'shift-jis';
51
+ case 'utf16le':
52
+ return 'utf-16le';
53
+ case 'utf16be':
54
+ return 'utf-16be';
55
+ case 'big5hkscs':
56
+ return 'big5-hkscs';
57
+ case 'eucjp':
58
+ return 'euc-jp';
59
+ case 'euckr':
60
+ return 'euc-kr';
61
+ case 'koi8r':
62
+ return 'koi8-r';
63
+ case 'koi8u':
64
+ return 'koi8-u';
65
+ case 'macroman':
66
+ return 'x-mac-roman';
67
+ case 'utf8bom':
68
+ return 'utf8';
69
+ default: {
70
+ const m = enc.match(/windows(\d+)/);
71
+ if (m) {
72
+ return 'windows-' + m[1];
73
+ }
74
+
75
+ return enc;
76
+ }
77
+ }
78
+ }
79
+
80
+ export async function encodingExists(encoding: string): Promise<boolean> {
81
+ return iconv.encodingExists(toNodeEncoding(encoding));
82
+ }
83
+
84
+ export function toNodeEncoding(enc: string | null): string {
85
+ if (enc === UTF8_with_bom || enc === null) {
86
+ return UTF8; // iconv does not distinguish UTF 8 with or without BOM, so we need to help it
87
+ }
88
+ return enc;
89
+ }
90
+
91
+ /**
92
+ * nodejs 内置的 Buffer 转换编码不支持 GBK,使用第三方的 iconv-lite
93
+ * @param buffer Uint8Array | Buffer
94
+ * @param encoding 传入的是 SUPPORTED_ENCODINGS 已有的键值(已通过 tests case 的)
95
+ */
96
+ export function iconvDecode(buffer: Uint8Array | Buffer, encoding: string) {
97
+ encoding = toIconvLiteEncoding(encoding);
98
+ return iconv.decode(buffer as Buffer, encoding);
99
+ }
100
+
101
+ export function iconvEncode(content: string, encoding: string): Uint8Array | Buffer {
102
+ encoding = toIconvLiteEncoding(encoding);
103
+ return iconv.encode(content, encoding);
104
+ }
105
+
106
+ function encodeLatin1(buffer: Uint8Array): string {
107
+ let result = '';
108
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
109
+ for (let i = 0; i < buffer.length; i++) {
110
+ result += String.fromCharCode(buffer[i]);
111
+ }
112
+ return result;
113
+ }
114
+
115
+ export function detectEncodingByBOMFromBuffer(
116
+ buffer: BinaryBuffer | null,
117
+ bytesRead: number,
118
+ ): typeof UTF8_with_bom | typeof UTF16le | typeof UTF16be | null {
119
+ if (!buffer || bytesRead < UTF16be_BOM.length) {
120
+ return null;
121
+ }
122
+
123
+ const b0 = buffer.readUInt8(0);
124
+ const b1 = buffer.readUInt8(1);
125
+
126
+ // UTF-16 BE
127
+ if (b0 === UTF16be_BOM[0] && b1 === UTF16be_BOM[1]) {
128
+ return UTF16be;
129
+ }
130
+
131
+ // UTF-16 LE
132
+ if (b0 === UTF16le_BOM[0] && b1 === UTF16le_BOM[1]) {
133
+ return UTF16le;
134
+ }
135
+
136
+ if (bytesRead < UTF8_BOM.length) {
137
+ return null;
138
+ }
139
+
140
+ const b2 = buffer.readUInt8(2);
141
+
142
+ // UTF-8
143
+ if (b0 === UTF8_BOM[0] && b1 === UTF8_BOM[1] && b2 === UTF8_BOM[2]) {
144
+ return UTF8_with_bom;
145
+ }
146
+
147
+ return null;
148
+ }
149
+
150
+ // we explicitly ignore a specific set of encodings from auto guessing
151
+ // - ASCII: we never want this encoding (most UTF-8 files would happily detect as
152
+ // ASCII files and then you could not type non-ASCII characters anymore)
153
+ // - UTF-16: we have our own detection logic for UTF-16
154
+ // - UTF-32: we do not support this encoding in VSCode
155
+ const IGNORE_ENCODINGS = ['ascii', 'utf-16', 'utf-32'];
156
+
157
+ async function guessEncodingByBuffer(buffer: BinaryBuffer): Promise<string | null> {
158
+ // lazy load
159
+ const jschardet = await import('jschardet');
160
+
161
+ // ensure to limit buffer for guessing due to https://github.com/aadsm/jschardet/issues/53
162
+ const limitedBuffer = buffer.slice(0, AUTO_ENCODING_GUESS_MAX_BYTES);
163
+
164
+ // before guessing jschardet calls toString('binary') on input if it is a Buffer,
165
+ // since we are using it inside browser environment as well we do conversion ourselves
166
+ // https://github.com/aadsm/jschardet/blob/v2.1.1/src/index.js#L36-L40
167
+ const binaryString = encodeLatin1(limitedBuffer.buffer);
168
+
169
+ const guessed = jschardet.detect(binaryString);
170
+ if (!guessed || !guessed.encoding) {
171
+ return null;
172
+ }
173
+
174
+ const enc = guessed.encoding.toLowerCase();
175
+ if (0 <= IGNORE_ENCODINGS.indexOf(enc)) {
176
+ return null; // see comment above why we ignore some encodings
177
+ }
178
+
179
+ return toIconvLiteEncoding(guessed.encoding);
180
+ }
181
+
182
+ export interface IDetectedEncodingResult {
183
+ encoding: string | null;
184
+ seemsBinary: boolean;
185
+ }
186
+
187
+ export function detectEncodingFromBuffer(buffer: BinaryBuffer, autoGuessEncoding?: false): IDetectedEncodingResult;
188
+ export function detectEncodingFromBuffer(
189
+ buffer: BinaryBuffer,
190
+ autoGuessEncoding?: boolean,
191
+ ): Promise<IDetectedEncodingResult>;
192
+ export function detectEncodingFromBuffer(
193
+ buffer: BinaryBuffer,
194
+ autoGuessEncoding?: boolean,
195
+ ): Promise<IDetectedEncodingResult> | IDetectedEncodingResult {
196
+ const bytesRead = buffer.byteLength;
197
+ // Always first check for BOM to find out about encoding
198
+ let encoding = detectEncodingByBOMFromBuffer(buffer, bytesRead);
199
+
200
+ // Detect 0 bytes to see if file is binary or UTF-16 LE/BE
201
+ // unless we already know that this file has a UTF-16 encoding
202
+ let seemsBinary = false;
203
+ if (encoding !== UTF16be && encoding !== UTF16le && buffer) {
204
+ let couldBeUTF16LE = true; // e.g. 0xAA 0x00
205
+ let couldBeUTF16BE = true; // e.g. 0x00 0xAA
206
+ let containsZeroByte = false;
207
+
208
+ // This is a simplified guess to detect UTF-16 BE or LE by just checking if
209
+ // the first 512 bytes have the 0-byte at a specific location. For UTF-16 LE
210
+ // this would be the odd byte index and for UTF-16 BE the even one.
211
+ // Note: this can produce false positives (a binary file that uses a 2-byte
212
+ // encoding of the same format as UTF-16) and false negatives (a UTF-16 file
213
+ // that is using 4 bytes to encode a character).
214
+ for (let i = 0; i < bytesRead && i < ZERO_BYTE_DETECTION_BUFFER_MAX_LEN; i++) {
215
+ const isEndian = i % 2 === 1; // assume 2-byte sequences typical for UTF-16
216
+ const isZeroByte = buffer.readUInt8(i) === 0;
217
+
218
+ if (isZeroByte) {
219
+ containsZeroByte = true;
220
+ }
221
+
222
+ // UTF-16 LE: expect e.g. 0xAA 0x00
223
+ if (couldBeUTF16LE && ((isEndian && !isZeroByte) || (!isEndian && isZeroByte))) {
224
+ couldBeUTF16LE = false;
225
+ }
226
+
227
+ // UTF-16 BE: expect e.g. 0x00 0xAA
228
+ if (couldBeUTF16BE && ((isEndian && isZeroByte) || (!isEndian && !isZeroByte))) {
229
+ couldBeUTF16BE = false;
230
+ }
231
+
232
+ // Return if this is neither UTF16-LE nor UTF16-BE and thus treat as binary
233
+ if (isZeroByte && !couldBeUTF16LE && !couldBeUTF16BE) {
234
+ break;
235
+ }
236
+ }
237
+
238
+ // Handle case of 0-byte included
239
+ if (containsZeroByte) {
240
+ if (couldBeUTF16LE) {
241
+ encoding = UTF16le;
242
+ } else if (couldBeUTF16BE) {
243
+ encoding = UTF16be;
244
+ } else {
245
+ seemsBinary = true;
246
+ }
247
+ }
248
+ }
249
+
250
+ // Auto guess encoding if configured
251
+ if (autoGuessEncoding && !seemsBinary && !encoding && buffer) {
252
+ return guessEncodingByBuffer(buffer.slice(0, bytesRead)).then((guessedEncoding) => ({
253
+ seemsBinary: false,
254
+ encoding: guessedEncoding,
255
+ }));
256
+ }
257
+
258
+ return { seemsBinary, encoding };
259
+ }
260
+
261
+ export interface IEncodingInfo {
262
+ id: string; // encoding identifier
263
+ labelLong: string; // long label name
264
+ labelShort: string; // short label name
265
+ }
266
+
267
+ export function getEncodingInfo(encoding: string | null): null | IEncodingInfo {
268
+ if (!encoding) {
269
+ return null;
270
+ }
271
+ const result = SUPPORTED_ENCODINGS[encoding] || {};
272
+
273
+ return {
274
+ id: encoding,
275
+ labelLong: result.labelLong || encoding,
276
+ labelShort: result.labelShort || encoding,
277
+ };
278
+ }