@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.
- package/lib/arrays.d.ts +3 -3
- package/lib/arrays.d.ts.map +1 -1
- package/lib/arrays.js.map +1 -1
- package/lib/async.d.ts +3 -3
- package/lib/async.d.ts.map +1 -1
- package/lib/buffer.js +4 -4
- package/lib/buffer.js.map +1 -1
- package/lib/character-classifier.js +3 -3
- package/lib/character-classifier.js.map +1 -1
- package/lib/decorators.d.ts +1 -1
- package/lib/decorators.d.ts.map +1 -1
- package/lib/encoding.d.ts +7 -1
- package/lib/encoding.d.ts.map +1 -1
- package/lib/encoding.js +15 -2
- package/lib/encoding.js.map +1 -1
- package/lib/errors.d.ts +2 -2
- package/lib/errors.d.ts.map +1 -1
- package/lib/event.d.ts +2 -2
- package/lib/event.d.ts.map +1 -1
- package/lib/filters.d.ts +3 -3
- package/lib/filters.d.ts.map +1 -1
- package/lib/filters.js +25 -25
- package/lib/filters.js.map +1 -1
- package/lib/glob.d.ts +2 -2
- package/lib/glob.d.ts.map +1 -1
- package/lib/glob.js +1 -1
- package/lib/glob.js.map +1 -1
- package/lib/hash.js +10 -10
- package/lib/hash.js.map +1 -1
- package/lib/iterator.d.ts +1 -1
- package/lib/iterator.d.ts.map +1 -1
- package/lib/linked-text.d.ts +1 -1
- package/lib/linked-text.d.ts.map +1 -1
- package/lib/linked-text.js.map +1 -1
- package/lib/map.js +17 -17
- package/lib/map.js.map +1 -1
- package/lib/objects.d.ts +1 -0
- package/lib/objects.d.ts.map +1 -1
- package/lib/objects.js +38 -1
- package/lib/objects.js.map +1 -1
- package/lib/os.d.ts +1 -8
- package/lib/os.d.ts.map +1 -1
- package/lib/os.js +4 -8
- package/lib/os.js.map +1 -1
- package/lib/path.d.ts +1 -1
- package/lib/path.d.ts.map +1 -1
- package/lib/path.js +29 -29
- package/lib/path.js.map +1 -1
- package/lib/platform.js +8 -8
- package/lib/platform.js.map +1 -1
- package/lib/process.d.ts.map +1 -1
- package/lib/process.js +28 -13
- package/lib/process.js.map +1 -1
- package/lib/progress.js +3 -3
- package/lib/progress.js.map +1 -1
- package/lib/strings.js +9 -9
- package/lib/strings.js.map +1 -1
- package/lib/types.d.ts +5 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +8 -1
- package/lib/types.js.map +1 -1
- package/lib/uint.js +4 -4
- package/lib/uint.js.map +1 -1
- package/lib/uri.js +8 -8
- package/lib/uri.js.map +1 -1
- package/lib/uuid.d.ts +1 -1
- package/lib/uuid.d.ts.map +1 -1
- package/lib/uuid.js +3 -2
- package/lib/uuid.js.map +1 -1
- package/package.json +8 -6
- package/src/ansi.ts +10 -0
- package/src/argv.ts +57 -0
- package/src/arrays.ts +328 -0
- package/src/async.ts +580 -0
- package/src/buffer.ts +290 -0
- package/src/cache.ts +37 -0
- package/src/cancellation.ts +136 -0
- package/src/charCode.ts +425 -0
- package/src/character-classifier.ts +82 -0
- package/src/const/encoding.ts +242 -0
- package/src/const/index.ts +1 -0
- package/src/date.ts +36 -0
- package/src/decorators.ts +166 -0
- package/src/disposable.ts +377 -0
- package/src/encoding.ts +278 -0
- package/src/errors.ts +192 -0
- package/src/event.ts +1024 -0
- package/src/file-uri.ts +41 -0
- package/src/filters.ts +887 -0
- package/src/functional.ts +43 -0
- package/src/glob.ts +752 -0
- package/src/hash.ts +336 -0
- package/src/iconLabels.ts +149 -0
- package/src/index.ts +40 -0
- package/src/iterator.ts +36 -0
- package/src/lifecycle.ts +48 -0
- package/src/linked-list.ts +144 -0
- package/src/linked-text.ts +55 -0
- package/src/lru-map.ts +133 -0
- package/src/map.ts +934 -0
- package/src/marshalling.ts +62 -0
- package/src/objects.ts +145 -0
- package/src/os.ts +76 -0
- package/src/path.ts +1885 -0
- package/src/platform.ts +194 -0
- package/src/process.ts +43 -0
- package/src/progress.ts +83 -0
- package/src/promises.ts +23 -0
- package/src/sequence.ts +28 -0
- package/src/strings.ts +1002 -0
- package/src/types.ts +214 -0
- package/src/uint.ts +59 -0
- package/src/uri.ts +298 -0
- 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
|
+
}
|
package/src/encoding.ts
ADDED
|
@@ -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
|
+
}
|