Merge from vscode 05fc61ffb1aee9fd19173c32113daed079f9b7bd (#5074)

* Merge from vscode 05fc61ffb1aee9fd19173c32113daed079f9b7bd

* fix tests
This commit is contained in:
Anthony Dresser
2019-04-16 22:11:30 -07:00
committed by GitHub
parent 2f8519cb6b
commit 8956b591f7
217 changed files with 5120 additions and 3926 deletions

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as stream from 'vs/base/node/stream';
import * as iconv from 'iconv-lite';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { exec } from 'child_process';
import { Readable, Writable } from 'stream';
import { VSBuffer } from 'vs/base/common/buffer';
export const UTF8 = 'utf8';
export const UTF8_with_bom = 'utf8bom';
@@ -24,7 +24,12 @@ export interface IDecodeStreamOptions {
overwriteEncoding?(detectedEncoding: string | null): string;
}
export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }> {
export interface IDecodeStreamResult {
detected: IDetectedEncodingResult;
stream: NodeJS.ReadableStream;
}
export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
if (!options.minBytesRequiredForDetection) {
options.minBytesRequiredForDetection = options.guessEncoding ? AUTO_GUESS_BUFFER_MAX_LEN : NO_GUESS_BUFFER_MAX_LEN;
}
@@ -33,90 +38,88 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
options.overwriteEncoding = detected => detected || UTF8;
}
return new Promise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }>((resolve, reject) => {
readable.on('error', reject);
readable.pipe(new class extends Writable {
private _decodeStream: NodeJS.ReadWriteStream;
private _decodeStreamConstruction: Promise<void>;
private _buffer: Buffer[] = [];
private _bytesBuffered = 0;
return new Promise<IDecodeStreamResult>((resolve, reject) => {
const writer = new class extends Writable {
private decodeStream: NodeJS.ReadWriteStream;
private decodeStreamConstruction: Promise<void>;
private buffer: Buffer[] = [];
private bytesBuffered = 0;
_write(chunk: any, encoding: string, callback: Function): void {
if (!Buffer.isBuffer(chunk)) {
callback(new Error('data must be a buffer'));
}
if (this._decodeStream) {
// just a forwarder now
this._decodeStream.write(chunk, callback);
if (this.decodeStream) {
this.decodeStream.write(chunk, callback); // just a forwarder now
return;
}
this._buffer.push(chunk);
this._bytesBuffered += chunk.length;
this.buffer.push(chunk);
this.bytesBuffered += chunk.length;
if (this._decodeStreamConstruction) {
// waiting for the decoder to be ready
this._decodeStreamConstruction.then(_ => callback(), err => callback(err));
// waiting for the decoder to be ready
if (this.decodeStreamConstruction) {
this.decodeStreamConstruction.then(() => callback(), err => callback(err));
}
} else if (typeof options.minBytesRequiredForDetection === 'number' && this._bytesBuffered >= options.minBytesRequiredForDetection) {
// buffered enough data, create stream and forward data
// buffered enough data, create stream and forward data
else if (typeof options.minBytesRequiredForDetection === 'number' && this.bytesBuffered >= options.minBytesRequiredForDetection) {
this._startDecodeStream(callback);
}
} else {
// only buffering
// only buffering
else {
callback();
}
}
_startDecodeStream(callback: Function): void {
this._decodeStreamConstruction = Promise.resolve(detectEncodingFromBuffer({
buffer: Buffer.concat(this._buffer), bytesRead: this._bytesBuffered
this.decodeStreamConstruction = Promise.resolve(detectEncodingFromBuffer({
buffer: Buffer.concat(this.buffer),
bytesRead: this.bytesBuffered
}, options.guessEncoding)).then(detected => {
if (options.overwriteEncoding) {
detected.encoding = options.overwriteEncoding(detected.encoding);
}
this._decodeStream = decodeStream(detected.encoding);
for (const buffer of this._buffer) {
this._decodeStream.write(buffer);
}
callback();
resolve({ detected, stream: this._decodeStream });
this.decodeStream = decodeStream(detected.encoding);
for (const buffer of this.buffer) {
this.decodeStream.write(buffer);
}
callback();
resolve({ detected, stream: this.decodeStream });
}, err => {
this.emit('error', err);
callback(err);
});
}
_final(callback: (err?: any) => any) {
if (this._decodeStream) {
// normal finish
this._decodeStream.end(callback);
} else {
// we were still waiting for data...
this._startDecodeStream(() => this._decodeStream.end(callback));
// normal finish
if (this.decodeStream) {
this.decodeStream.end(callback);
}
// we were still waiting for data...
else {
this._startDecodeStream(() => this.decodeStream.end(callback));
}
}
});
};
// errors
readable.on('error', reject);
// pipe through
readable.pipe(writer);
});
}
export function bomLength(encoding: string): number {
switch (encoding) {
case UTF8:
return 3;
case UTF16be:
case UTF16le:
return 2;
}
return 0;
}
export function decode(buffer: Buffer, encoding: string): string {
return iconv.decode(buffer, toNodeEncoding(encoding));
}
@@ -129,7 +132,7 @@ export function encodingExists(encoding: string): boolean {
return iconv.encodingExists(toNodeEncoding(encoding));
}
export function decodeStream(encoding: string | null): NodeJS.ReadWriteStream {
function decodeStream(encoding: string | null): NodeJS.ReadWriteStream {
return iconv.decodeStream(toNodeEncoding(encoding));
}
@@ -145,7 +148,7 @@ function toNodeEncoding(enc: string | null): string {
return enc;
}
export function detectEncodingByBOMFromBuffer(buffer: Buffer | null, bytesRead: number): string | null {
export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): string | null {
if (!buffer || bytesRead < 2) {
return null;
}
@@ -177,39 +180,31 @@ export function detectEncodingByBOMFromBuffer(buffer: Buffer | null, bytesRead:
return null;
}
/**
* Detects the Byte Order Mark in a given file.
* If no BOM is detected, null will be passed to callback.
*/
export function detectEncodingByBOM(file: string): Promise<string | null> {
return stream.readExactlyByFile(file, 3).then(({ buffer, bytesRead }) => detectEncodingByBOMFromBuffer(buffer, bytesRead), error => null);
}
const MINIMUM_THRESHOLD = 0.2;
const IGNORE_ENCODINGS = ['ascii', 'utf-8', 'utf-16', 'utf-32'];
/**
* Guesses the encoding from buffer.
*/
export function guessEncodingByBuffer(buffer: Buffer): Promise<string | null> {
return import('jschardet').then(jschardet => {
jschardet.Constants.MINIMUM_THRESHOLD = MINIMUM_THRESHOLD;
async function guessEncodingByBuffer(buffer: Buffer): Promise<string | null> {
const jschardet = await import('jschardet');
const guessed = jschardet.detect(buffer);
if (!guessed || !guessed.encoding) {
return null;
}
jschardet.Constants.MINIMUM_THRESHOLD = MINIMUM_THRESHOLD;
const enc = guessed.encoding.toLowerCase();
const guessed = jschardet.detect(buffer);
if (!guessed || !guessed.encoding) {
return null;
}
// Ignore encodings that cannot guess correctly
// (http://chardet.readthedocs.io/en/latest/supported-encodings.html)
if (0 <= IGNORE_ENCODINGS.indexOf(enc)) {
return null;
}
const enc = guessed.encoding.toLowerCase();
return toIconvLiteEncoding(guessed.encoding);
});
// Ignore encodings that cannot guess correctly
// (http://chardet.readthedocs.io/en/latest/supported-encodings.html)
if (0 <= IGNORE_ENCODINGS.indexOf(enc)) {
return null;
}
return toIconvLiteEncoding(guessed.encoding);
}
const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = {
@@ -270,9 +265,14 @@ export interface IDetectedEncodingResult {
seemsBinary: boolean;
}
export function detectEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: false): IDetectedEncodingResult;
export function detectEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: boolean): Promise<IDetectedEncodingResult>;
export function detectEncodingFromBuffer({ buffer, bytesRead }: stream.ReadResult, autoGuessEncoding?: boolean): Promise<IDetectedEncodingResult> | IDetectedEncodingResult {
export interface IReadResult {
buffer: Buffer | null;
bytesRead: number;
}
export function detectEncodingFromBuffer(readResult: IReadResult, autoGuessEncoding?: false): IDetectedEncodingResult;
export function detectEncodingFromBuffer(readResult: IReadResult, autoGuessEncoding?: boolean): Promise<IDetectedEncodingResult>;
export function detectEncodingFromBuffer({ buffer, bytesRead }: IReadResult, autoGuessEncoding?: boolean): Promise<IDetectedEncodingResult> | IDetectedEncodingResult {
// Always first check for BOM to find out about encoding
let encoding = detectEncodingByBOMFromBuffer(buffer, bytesRead);
@@ -357,7 +357,7 @@ const windowsTerminalEncodings = {
'1252': 'cp1252' // West European Latin
};
export function resolveTerminalEncoding(verbose?: boolean): Promise<string> {
export async function resolveTerminalEncoding(verbose?: boolean): Promise<string> {
let rawEncodingPromise: Promise<string>;
// Support a global environment variable to win over other mechanics
@@ -403,24 +403,23 @@ export function resolveTerminalEncoding(verbose?: boolean): Promise<string> {
});
}
return rawEncodingPromise.then(rawEncoding => {
if (verbose) {
console.log(`Detected raw terminal encoding: ${rawEncoding}`);
}
if (!rawEncoding || rawEncoding.toLowerCase() === 'utf-8' || rawEncoding.toLowerCase() === UTF8) {
return UTF8;
}
const iconvEncoding = toIconvLiteEncoding(rawEncoding);
if (iconv.encodingExists(iconvEncoding)) {
return iconvEncoding;
}
if (verbose) {
console.log('Unsupported terminal encoding, falling back to UTF-8.');
}
const rawEncoding = await rawEncodingPromise;
if (verbose) {
console.log(`Detected raw terminal encoding: ${rawEncoding}`);
}
if (!rawEncoding || rawEncoding.toLowerCase() === 'utf-8' || rawEncoding.toLowerCase() === UTF8) {
return UTF8;
});
}
const iconvEncoding = toIconvLiteEncoding(rawEncoding);
if (iconv.encodingExists(iconvEncoding)) {
return iconvEncoding;
}
if (verbose) {
console.log('Unsupported terminal encoding, falling back to UTF-8.');
}
return UTF8;
}

View File

@@ -5,63 +5,6 @@
import * as fs from 'fs';
export interface ReadResult {
buffer: Buffer | null;
bytesRead: number;
}
/**
* Reads totalBytes from the provided file.
*/
export function readExactlyByFile(file: string, totalBytes: number): Promise<ReadResult> {
return new Promise<ReadResult>((resolve, reject) => {
fs.open(file, 'r', null, (err, fd) => {
if (err) {
return reject(err);
}
function end(err: Error | null, resultBuffer: Buffer | null, bytesRead: number): void {
fs.close(fd, closeError => {
if (closeError) {
return reject(closeError);
}
if (err && (<any>err).code === 'EISDIR') {
return reject(err); // we want to bubble this error up (file is actually a folder)
}
return resolve({ buffer: resultBuffer, bytesRead });
});
}
const buffer = Buffer.allocUnsafe(totalBytes);
let offset = 0;
function readChunk(): void {
fs.read(fd, buffer, offset, totalBytes - offset, null, (err, bytesRead) => {
if (err) {
return end(err, null, 0);
}
if (bytesRead === 0) {
return end(null, buffer, offset);
}
offset += bytesRead;
if (offset === totalBytes) {
return end(null, buffer, offset);
}
return readChunk();
});
}
readChunk();
});
});
}
/**
* Reads a file until a matching string is found.
*