mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 05fc61ffb1aee9fd19173c32113daed079f9b7bd (#5074)
* Merge from vscode 05fc61ffb1aee9fd19173c32113daed079f9b7bd * fix tests
This commit is contained in:
@@ -397,7 +397,7 @@ export class InputBox extends Widget {
|
||||
return errorMsg ? errorMsg.type !== MessageType.ERROR : true;
|
||||
}
|
||||
|
||||
private stylesForType(type: MessageType | undefined): { border: Color | undefined; background: Color | undefined; foreground: Color | undefined } {
|
||||
public stylesForType(type: MessageType | undefined): { border: Color | undefined; background: Color | undefined; foreground: Color | undefined } {
|
||||
switch (type) {
|
||||
case MessageType.INFO: return { border: this.inputValidationInfoBorder, background: this.inputValidationInfoBackground, foreground: this.inputValidationInfoForeground };
|
||||
case MessageType.WARNING: return { border: this.inputValidationWarningBorder, background: this.inputValidationWarningBackground, foreground: this.inputValidationWarningForeground };
|
||||
|
||||
@@ -11,7 +11,7 @@ let textDecoder: TextDecoder | null;
|
||||
|
||||
export class VSBuffer {
|
||||
|
||||
public static alloc(byteLength: number): VSBuffer {
|
||||
static alloc(byteLength: number): VSBuffer {
|
||||
if (hasBuffer) {
|
||||
return new VSBuffer(Buffer.allocUnsafe(byteLength));
|
||||
} else {
|
||||
@@ -19,7 +19,7 @@ export class VSBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
public static wrap(actual: Uint8Array): VSBuffer {
|
||||
static wrap(actual: Uint8Array): VSBuffer {
|
||||
if (hasBuffer && !(Buffer.isBuffer(actual))) {
|
||||
// https://nodejs.org/dist/latest-v10.x/docs/api/buffer.html#buffer_class_method_buffer_from_arraybuffer_byteoffset_length
|
||||
// Create a zero-copy Buffer wrapper around the ArrayBuffer pointed to by the Uint8Array
|
||||
@@ -28,7 +28,7 @@ export class VSBuffer {
|
||||
return new VSBuffer(actual);
|
||||
}
|
||||
|
||||
public static fromString(source: string): VSBuffer {
|
||||
static fromString(source: string): VSBuffer {
|
||||
if (hasBuffer) {
|
||||
return new VSBuffer(Buffer.from(source));
|
||||
} else {
|
||||
@@ -39,7 +39,7 @@ export class VSBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
public static concat(buffers: VSBuffer[], totalLength?: number): VSBuffer {
|
||||
static concat(buffers: VSBuffer[], totalLength?: number): VSBuffer {
|
||||
if (typeof totalLength === 'undefined') {
|
||||
totalLength = 0;
|
||||
for (let i = 0, len = buffers.length; i < len; i++) {
|
||||
@@ -58,15 +58,15 @@ export class VSBuffer {
|
||||
return ret;
|
||||
}
|
||||
|
||||
public readonly buffer: Uint8Array;
|
||||
public readonly byteLength: number;
|
||||
readonly buffer: Uint8Array;
|
||||
readonly byteLength: number;
|
||||
|
||||
private constructor(buffer: Uint8Array) {
|
||||
this.buffer = buffer;
|
||||
this.byteLength = this.buffer.byteLength;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
toString(): string {
|
||||
if (hasBuffer) {
|
||||
return this.buffer.toString();
|
||||
} else {
|
||||
@@ -77,33 +77,32 @@ export class VSBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
public slice(start?: number, end?: number): VSBuffer {
|
||||
slice(start?: number, end?: number): VSBuffer {
|
||||
return new VSBuffer(this.buffer.slice(start, end));
|
||||
}
|
||||
|
||||
public set(array: VSBuffer, offset?: number): void {
|
||||
set(array: VSBuffer, offset?: number): void {
|
||||
this.buffer.set(array.buffer, offset);
|
||||
}
|
||||
|
||||
public readUint32BE(offset: number): number {
|
||||
return readUint32BE(this.buffer, offset);
|
||||
readUInt32BE(offset: number): number {
|
||||
return readUInt32BE(this.buffer, offset);
|
||||
}
|
||||
|
||||
public writeUint32BE(value: number, offset: number): void {
|
||||
writeUint32BE(this.buffer, value, offset);
|
||||
writeUInt32BE(value: number, offset: number): void {
|
||||
writeUInt32BE(this.buffer, value, offset);
|
||||
}
|
||||
|
||||
public readUint8(offset: number): number {
|
||||
return readUint8(this.buffer, offset);
|
||||
readUInt8(offset: number): number {
|
||||
return readUInt8(this.buffer, offset);
|
||||
}
|
||||
|
||||
public writeUint8(value: number, offset: number): void {
|
||||
writeUint8(this.buffer, value, offset);
|
||||
writeUInt8(value: number, offset: number): void {
|
||||
writeUInt8(this.buffer, value, offset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function readUint32BE(source: Uint8Array, offset: number): number {
|
||||
function readUInt32BE(source: Uint8Array, offset: number): number {
|
||||
return (
|
||||
source[offset] * 2 ** 24
|
||||
+ source[offset + 1] * 2 ** 16
|
||||
@@ -112,7 +111,7 @@ function readUint32BE(source: Uint8Array, offset: number): number {
|
||||
);
|
||||
}
|
||||
|
||||
function writeUint32BE(destination: Uint8Array, value: number, offset: number): void {
|
||||
function writeUInt32BE(destination: Uint8Array, value: number, offset: number): void {
|
||||
destination[offset + 3] = value;
|
||||
value = value >>> 8;
|
||||
destination[offset + 2] = value;
|
||||
@@ -122,11 +121,11 @@ function writeUint32BE(destination: Uint8Array, value: number, offset: number):
|
||||
destination[offset] = value;
|
||||
}
|
||||
|
||||
function readUint8(source: Uint8Array, offset: number): number {
|
||||
function readUInt8(source: Uint8Array, offset: number): number {
|
||||
return source[offset];
|
||||
}
|
||||
|
||||
function writeUint8(destination: Uint8Array, value: number, offset: number): void {
|
||||
function writeUInt8(destination: Uint8Array, value: number, offset: number): void {
|
||||
destination[offset] = value;
|
||||
}
|
||||
|
||||
@@ -139,6 +138,47 @@ export interface VSBufferReadable {
|
||||
read(): VSBuffer | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A buffer readable stream emits data to listeners. The stream
|
||||
* will only start emitting when the first data listener has
|
||||
* been added or the resume() method has been called.
|
||||
*/
|
||||
export interface VSBufferReadableStream {
|
||||
|
||||
/**
|
||||
* The 'data' event is emitted whenever the stream is
|
||||
* relinquishing ownership of a chunk of data to a consumer.
|
||||
*/
|
||||
on(event: 'data', callback: (chunk: VSBuffer) => void): void;
|
||||
|
||||
/**
|
||||
* Emitted when any error occurs.
|
||||
*/
|
||||
on(event: 'error', callback: (err: any) => void): void;
|
||||
|
||||
/**
|
||||
* The 'end' event is emitted when there is no more data
|
||||
* to be consumed from the stream. The 'end' event will
|
||||
* not be emitted unless the data is completely consumed.
|
||||
*/
|
||||
on(event: 'end', callback: () => void): void;
|
||||
|
||||
/**
|
||||
* Stops emitting any events until resume() is called.
|
||||
*/
|
||||
pause(): void;
|
||||
|
||||
/**
|
||||
* Starts emitting events again after pause() was called.
|
||||
*/
|
||||
resume(): void;
|
||||
|
||||
/**
|
||||
* Destroys the stream and stops emitting any event.
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fully read a VSBuffer readable into a single buffer.
|
||||
*/
|
||||
@@ -158,6 +198,7 @@ export function readableToBuffer(readable: VSBufferReadable): VSBuffer {
|
||||
*/
|
||||
export function bufferToReadable(buffer: VSBuffer): VSBufferReadable {
|
||||
let done = false;
|
||||
|
||||
return {
|
||||
read: () => {
|
||||
if (done) {
|
||||
@@ -169,4 +210,231 @@ export function bufferToReadable(buffer: VSBuffer): VSBufferReadable {
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fully read a VSBuffer stream into a single buffer.
|
||||
*/
|
||||
export function streamToBuffer(stream: VSBufferReadableStream): Promise<VSBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: VSBuffer[] = [];
|
||||
|
||||
stream.on('data', chunk => chunks.push(chunk));
|
||||
stream.on('error', error => reject(error));
|
||||
stream.on('end', () => resolve(VSBuffer.concat(chunks)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a VSBufferStream from an existing VSBuffer.
|
||||
*/
|
||||
export function bufferToStream(buffer: VSBuffer): VSBufferReadableStream {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
stream.end(buffer);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a VSBufferStream that can be pushed
|
||||
* buffers to. Will only start to emit data when a listener
|
||||
* is added.
|
||||
*/
|
||||
export function writeableBufferStream(): VSBufferWriteableStream {
|
||||
return new VSBufferWriteableStreamImpl();
|
||||
}
|
||||
|
||||
export interface VSBufferWriteableStream extends VSBufferReadableStream {
|
||||
write(chunk: VSBuffer): void;
|
||||
error(error: Error): void;
|
||||
end(result?: VSBuffer | Error): void;
|
||||
}
|
||||
|
||||
class VSBufferWriteableStreamImpl implements VSBufferWriteableStream {
|
||||
|
||||
private readonly state = {
|
||||
flowing: false,
|
||||
ended: false,
|
||||
destroyed: false
|
||||
};
|
||||
|
||||
private readonly buffer = {
|
||||
data: [] as VSBuffer[],
|
||||
error: [] as Error[]
|
||||
};
|
||||
|
||||
private readonly listeners = {
|
||||
data: [] as { (chunk: VSBuffer): void }[],
|
||||
error: [] as { (error: Error): void }[],
|
||||
end: [] as { (): void }[]
|
||||
};
|
||||
|
||||
pause(): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.flowing = false;
|
||||
}
|
||||
|
||||
resume(): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.state.flowing) {
|
||||
this.state.flowing = true;
|
||||
|
||||
// emit buffered events
|
||||
this.flowData();
|
||||
this.flowErrors();
|
||||
this.flowEnd();
|
||||
}
|
||||
}
|
||||
|
||||
write(chunk: VSBuffer): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flowing: directly send the data to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.data.forEach(listener => listener(chunk));
|
||||
}
|
||||
|
||||
// not yet flowing: buffer data until flowing
|
||||
else {
|
||||
this.buffer.data.push(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
error(error: Error): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flowing: directly send the error to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
}
|
||||
|
||||
// not yet flowing: buffer errors until flowing
|
||||
else {
|
||||
this.buffer.error.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
end(result?: VSBuffer | Error): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// end with data or error if provided
|
||||
if (result instanceof Error) {
|
||||
this.error(result);
|
||||
} else if (result) {
|
||||
this.write(result);
|
||||
}
|
||||
|
||||
// flowing: send end event to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
// not yet flowing: remember state
|
||||
else {
|
||||
this.state.ended = true;
|
||||
}
|
||||
}
|
||||
|
||||
on(event: 'data', callback: (chunk: VSBuffer) => void): void;
|
||||
on(event: 'error', callback: (err: any) => void): void;
|
||||
on(event: 'end', callback: () => void): void;
|
||||
on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case 'data':
|
||||
this.listeners.data.push(callback);
|
||||
|
||||
// switch into flowing mode as soon as the first 'data'
|
||||
// listener is added and we are not yet in flowing mode
|
||||
this.resume();
|
||||
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
this.listeners.end.push(callback);
|
||||
|
||||
// emit 'end' event directly if we are flowing
|
||||
// and the end has already been reached
|
||||
//
|
||||
// finish() when it went through
|
||||
if (this.state.flowing && this.flowEnd()) {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
this.listeners.error.push(callback);
|
||||
|
||||
// emit buffered 'error' events unless done already
|
||||
// now that we know that we have at least one listener
|
||||
if (this.state.flowing) {
|
||||
this.flowErrors();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private flowData(): void {
|
||||
if (this.buffer.data.length > 0) {
|
||||
const fullDataBuffer = VSBuffer.concat(this.buffer.data);
|
||||
|
||||
this.listeners.data.forEach(listener => listener(fullDataBuffer));
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private flowErrors(): void {
|
||||
if (this.listeners.error.length > 0) {
|
||||
for (const error of this.buffer.error) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
}
|
||||
|
||||
this.buffer.error.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private flowEnd(): boolean {
|
||||
if (this.state.ended) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
|
||||
return this.listeners.end.length > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (!this.state.destroyed) {
|
||||
this.state.destroyed = true;
|
||||
this.state.ended = true;
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
this.buffer.error.length = 0;
|
||||
|
||||
this.listeners.data.length = 0;
|
||||
this.listeners.error.length = 0;
|
||||
this.listeners.end.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -216,10 +216,10 @@ class ProtocolReader extends Disposable {
|
||||
|
||||
// save new state => next time will read the body
|
||||
this._state.readHead = false;
|
||||
this._state.readLen = buff.readUint32BE(9);
|
||||
this._state.messageType = <ProtocolMessageType>buff.readUint8(0);
|
||||
this._state.id = buff.readUint32BE(1);
|
||||
this._state.ack = buff.readUint32BE(5);
|
||||
this._state.readLen = buff.readUInt32BE(9);
|
||||
this._state.messageType = <ProtocolMessageType>buff.readUInt8(0);
|
||||
this._state.id = buff.readUInt32BE(1);
|
||||
this._state.ack = buff.readUInt32BE(5);
|
||||
} else {
|
||||
// buff is the body
|
||||
const messageType = this._state.messageType;
|
||||
@@ -288,10 +288,10 @@ class ProtocolWriter {
|
||||
msg.writtenTime = Date.now();
|
||||
this.lastWriteTime = Date.now();
|
||||
const header = VSBuffer.alloc(ProtocolConstants.HeaderLength);
|
||||
header.writeUint8(msg.type, 0);
|
||||
header.writeUint32BE(msg.id, 1);
|
||||
header.writeUint32BE(msg.ack, 5);
|
||||
header.writeUint32BE(msg.data.byteLength, 9);
|
||||
header.writeUInt8(msg.type, 0);
|
||||
header.writeUInt32BE(msg.id, 1);
|
||||
header.writeUInt32BE(msg.ack, 5);
|
||||
header.writeUInt32BE(msg.data.byteLength, 9);
|
||||
this._writeSoon(header, msg.data);
|
||||
}
|
||||
|
||||
|
||||
@@ -166,17 +166,17 @@ enum DataType {
|
||||
|
||||
function createSizeBuffer(size: number): VSBuffer {
|
||||
const result = VSBuffer.alloc(4);
|
||||
result.writeUint32BE(size, 0);
|
||||
result.writeUInt32BE(size, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
function readSizeBuffer(reader: IReader): number {
|
||||
return reader.read(4).readUint32BE(0);
|
||||
return reader.read(4).readUInt32BE(0);
|
||||
}
|
||||
|
||||
function createOneByteBuffer(value: number): VSBuffer {
|
||||
const result = VSBuffer.alloc(1);
|
||||
result.writeUint8(value, 0);
|
||||
result.writeUInt8(value, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ function serialize(writer: IWriter, data: any): void {
|
||||
}
|
||||
|
||||
function deserialize(reader: IReader): any {
|
||||
const type = reader.read(1).readUint8(0);
|
||||
const type = reader.read(1).readUInt8(0);
|
||||
|
||||
switch (type) {
|
||||
case DataType.Undefined: return undefined;
|
||||
|
||||
@@ -136,10 +136,10 @@ suite('IPC, Socket Protocol', () => {
|
||||
assert.equal(msg1.toString(), 'foobarfarboo');
|
||||
|
||||
const buffer = VSBuffer.alloc(1);
|
||||
buffer.writeUint8(123, 0);
|
||||
buffer.writeUInt8(123, 0);
|
||||
a.send(buffer);
|
||||
const msg2 = await bMessages.waitForOne();
|
||||
assert.equal(msg2.readUint8(0), 123);
|
||||
assert.equal(msg2.readUInt8(0), 123);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -3,18 +3,365 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { hasBuffer, VSBuffer } from 'vs/base/common/buffer';
|
||||
import { VSBuffer, bufferToReadable, readableToBuffer, bufferToStream, streamToBuffer, writeableBufferStream } from 'vs/base/common/buffer';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
suite('Buffer', () => {
|
||||
|
||||
if (hasBuffer) {
|
||||
test('issue #71993 - VSBuffer#toString returns numbers', () => {
|
||||
const data = new Uint8Array([1, 2, 3, 'h'.charCodeAt(0), 'i'.charCodeAt(0), 4, 5]).buffer;
|
||||
const buffer = VSBuffer.wrap(new Uint8Array(data, 3, 2));
|
||||
assert.deepEqual(buffer.toString(), 'hi');
|
||||
});
|
||||
}
|
||||
test('issue #71993 - VSBuffer#toString returns numbers', () => {
|
||||
const data = new Uint8Array([1, 2, 3, 'h'.charCodeAt(0), 'i'.charCodeAt(0), 4, 5]).buffer;
|
||||
const buffer = VSBuffer.wrap(new Uint8Array(data, 3, 2));
|
||||
assert.deepEqual(buffer.toString(), 'hi');
|
||||
});
|
||||
|
||||
test('bufferToReadable / readableToBuffer', () => {
|
||||
const content = 'Hello World';
|
||||
const readable = bufferToReadable(VSBuffer.fromString(content));
|
||||
|
||||
assert.equal(readableToBuffer(readable).toString(), content);
|
||||
});
|
||||
|
||||
test('bufferToStream / streamToBuffer', async () => {
|
||||
const content = 'Hello World';
|
||||
const stream = bufferToStream(VSBuffer.fromString(content));
|
||||
|
||||
assert.equal((await streamToBuffer(stream)).toString(), content);
|
||||
});
|
||||
|
||||
test('bufferWriteableStream - basics (no error)', async () => {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
let chunks: VSBuffer[] = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
|
||||
let ended = false;
|
||||
stream.on('end', () => {
|
||||
ended = true;
|
||||
});
|
||||
|
||||
let errors: Error[] = [];
|
||||
stream.on('error', error => {
|
||||
errors.push(error);
|
||||
});
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.end(VSBuffer.fromString('World'));
|
||||
|
||||
assert.equal(chunks.length, 2);
|
||||
assert.equal(chunks[0].toString(), 'Hello');
|
||||
assert.equal(chunks[1].toString(), 'World');
|
||||
assert.equal(ended, true);
|
||||
assert.equal(errors.length, 0);
|
||||
});
|
||||
|
||||
test('bufferWriteableStream - basics (error)', async () => {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
let chunks: VSBuffer[] = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
|
||||
let ended = false;
|
||||
stream.on('end', () => {
|
||||
ended = true;
|
||||
});
|
||||
|
||||
let errors: Error[] = [];
|
||||
stream.on('error', error => {
|
||||
errors.push(error);
|
||||
});
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.end(new Error());
|
||||
|
||||
assert.equal(chunks.length, 1);
|
||||
assert.equal(chunks[0].toString(), 'Hello');
|
||||
assert.equal(ended, true);
|
||||
assert.equal(errors.length, 1);
|
||||
});
|
||||
|
||||
test('bufferWriteableStream - buffers data when no listener', async () => {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.end(VSBuffer.fromString('World'));
|
||||
|
||||
let chunks: VSBuffer[] = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
|
||||
let ended = false;
|
||||
stream.on('end', () => {
|
||||
ended = true;
|
||||
});
|
||||
|
||||
let errors: Error[] = [];
|
||||
stream.on('error', error => {
|
||||
errors.push(error);
|
||||
});
|
||||
|
||||
assert.equal(chunks.length, 1);
|
||||
assert.equal(chunks[0].toString(), 'HelloWorld');
|
||||
assert.equal(ended, true);
|
||||
assert.equal(errors.length, 0);
|
||||
});
|
||||
|
||||
test('bufferWriteableStream - buffers errors when no listener', async () => {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.error(new Error());
|
||||
|
||||
let chunks: VSBuffer[] = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
|
||||
let errors: Error[] = [];
|
||||
stream.on('error', error => {
|
||||
errors.push(error);
|
||||
});
|
||||
|
||||
let ended = false;
|
||||
stream.on('end', () => {
|
||||
ended = true;
|
||||
});
|
||||
|
||||
stream.end();
|
||||
|
||||
assert.equal(chunks.length, 1);
|
||||
assert.equal(chunks[0].toString(), 'Hello');
|
||||
assert.equal(ended, true);
|
||||
assert.equal(errors.length, 1);
|
||||
});
|
||||
|
||||
test('bufferWriteableStream - buffers end when no listener', async () => {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.end(VSBuffer.fromString('World'));
|
||||
|
||||
let ended = false;
|
||||
stream.on('end', () => {
|
||||
ended = true;
|
||||
});
|
||||
|
||||
let chunks: VSBuffer[] = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
|
||||
let errors: Error[] = [];
|
||||
stream.on('error', error => {
|
||||
errors.push(error);
|
||||
});
|
||||
|
||||
assert.equal(chunks.length, 1);
|
||||
assert.equal(chunks[0].toString(), 'HelloWorld');
|
||||
assert.equal(ended, true);
|
||||
assert.equal(errors.length, 0);
|
||||
});
|
||||
|
||||
test('bufferWriteableStream - nothing happens after end()', async () => {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
let chunks: VSBuffer[] = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.end(VSBuffer.fromString('World'));
|
||||
|
||||
let dataCalledAfterEnd = false;
|
||||
stream.on('data', data => {
|
||||
dataCalledAfterEnd = true;
|
||||
});
|
||||
|
||||
let errorCalledAfterEnd = false;
|
||||
stream.on('error', error => {
|
||||
errorCalledAfterEnd = true;
|
||||
});
|
||||
|
||||
let endCalledAfterEnd = false;
|
||||
stream.on('end', () => {
|
||||
endCalledAfterEnd = true;
|
||||
});
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.error(new Error());
|
||||
await timeout(0);
|
||||
stream.end(VSBuffer.fromString('World'));
|
||||
|
||||
assert.equal(dataCalledAfterEnd, false);
|
||||
assert.equal(errorCalledAfterEnd, false);
|
||||
assert.equal(endCalledAfterEnd, false);
|
||||
|
||||
assert.equal(chunks.length, 2);
|
||||
assert.equal(chunks[0].toString(), 'Hello');
|
||||
assert.equal(chunks[1].toString(), 'World');
|
||||
});
|
||||
|
||||
test('bufferWriteableStream - pause/resume (simple)', async () => {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
let chunks: VSBuffer[] = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
|
||||
let ended = false;
|
||||
stream.on('end', () => {
|
||||
ended = true;
|
||||
});
|
||||
|
||||
let errors: Error[] = [];
|
||||
stream.on('error', error => {
|
||||
errors.push(error);
|
||||
});
|
||||
|
||||
stream.pause();
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.end(VSBuffer.fromString('World'));
|
||||
|
||||
assert.equal(chunks.length, 0);
|
||||
assert.equal(errors.length, 0);
|
||||
assert.equal(ended, false);
|
||||
|
||||
stream.resume();
|
||||
|
||||
assert.equal(chunks.length, 1);
|
||||
assert.equal(chunks[0].toString(), 'HelloWorld');
|
||||
assert.equal(ended, true);
|
||||
assert.equal(errors.length, 0);
|
||||
});
|
||||
|
||||
test('bufferWriteableStream - pause/resume (pause after first write)', async () => {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
let chunks: VSBuffer[] = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
|
||||
let ended = false;
|
||||
stream.on('end', () => {
|
||||
ended = true;
|
||||
});
|
||||
|
||||
let errors: Error[] = [];
|
||||
stream.on('error', error => {
|
||||
errors.push(error);
|
||||
});
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
|
||||
stream.pause();
|
||||
|
||||
await timeout(0);
|
||||
stream.end(VSBuffer.fromString('World'));
|
||||
|
||||
assert.equal(chunks.length, 1);
|
||||
assert.equal(chunks[0].toString(), 'Hello');
|
||||
assert.equal(errors.length, 0);
|
||||
assert.equal(ended, false);
|
||||
|
||||
stream.resume();
|
||||
|
||||
assert.equal(chunks.length, 2);
|
||||
assert.equal(chunks[0].toString(), 'Hello');
|
||||
assert.equal(chunks[1].toString(), 'World');
|
||||
assert.equal(ended, true);
|
||||
assert.equal(errors.length, 0);
|
||||
});
|
||||
|
||||
test('bufferWriteableStream - pause/resume (error)', async () => {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
let chunks: VSBuffer[] = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
|
||||
let ended = false;
|
||||
stream.on('end', () => {
|
||||
ended = true;
|
||||
});
|
||||
|
||||
let errors: Error[] = [];
|
||||
stream.on('error', error => {
|
||||
errors.push(error);
|
||||
});
|
||||
|
||||
stream.pause();
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.end(new Error());
|
||||
|
||||
assert.equal(chunks.length, 0);
|
||||
assert.equal(ended, false);
|
||||
assert.equal(errors.length, 0);
|
||||
|
||||
stream.resume();
|
||||
|
||||
assert.equal(chunks.length, 1);
|
||||
assert.equal(chunks[0].toString(), 'Hello');
|
||||
assert.equal(ended, true);
|
||||
assert.equal(errors.length, 1);
|
||||
});
|
||||
|
||||
test('bufferWriteableStream - destroy', async () => {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
let chunks: VSBuffer[] = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
|
||||
let ended = false;
|
||||
stream.on('end', () => {
|
||||
ended = true;
|
||||
});
|
||||
|
||||
let errors: Error[] = [];
|
||||
stream.on('error', error => {
|
||||
errors.push(error);
|
||||
});
|
||||
|
||||
stream.destroy();
|
||||
|
||||
await timeout(0);
|
||||
stream.write(VSBuffer.fromString('Hello'));
|
||||
await timeout(0);
|
||||
stream.end(VSBuffer.fromString('World'));
|
||||
|
||||
assert.equal(chunks.length, 0);
|
||||
assert.equal(ended, false);
|
||||
assert.equal(errors.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,51 +6,114 @@
|
||||
import * as assert from 'assert';
|
||||
import * as fs from 'fs';
|
||||
import * as encoding from 'vs/base/node/encoding';
|
||||
import { readExactlyByFile } from 'vs/base/node/stream';
|
||||
import { Readable } from 'stream';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
|
||||
export async function detectEncodingByBOM(file: string): Promise<string | null> {
|
||||
try {
|
||||
const { buffer, bytesRead } = await readExactlyByFile(file, 3);
|
||||
|
||||
return encoding.detectEncodingByBOMFromBuffer(buffer, bytesRead);
|
||||
} catch (error) {
|
||||
return null; // ignore errors (like file not found)
|
||||
}
|
||||
}
|
||||
|
||||
interface ReadResult {
|
||||
buffer: Buffer | null;
|
||||
bytesRead: number;
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
suite('Encoding', () => {
|
||||
|
||||
test('detectBOM does not return error for non existing file', async () => {
|
||||
const file = getPathFromAmdModule(require, './fixtures/not-exist.css');
|
||||
|
||||
const detectedEncoding = await encoding.detectEncodingByBOM(file);
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, null);
|
||||
});
|
||||
|
||||
test('detectBOM UTF-8', async () => {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some_utf8.css');
|
||||
|
||||
const detectedEncoding = await encoding.detectEncodingByBOM(file);
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, 'utf8');
|
||||
});
|
||||
|
||||
test('detectBOM UTF-16 LE', async () => {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some_utf16le.css');
|
||||
|
||||
const detectedEncoding = await encoding.detectEncodingByBOM(file);
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, 'utf16le');
|
||||
});
|
||||
|
||||
test('detectBOM UTF-16 BE', async () => {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
|
||||
|
||||
const detectedEncoding = await encoding.detectEncodingByBOM(file);
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, 'utf16be');
|
||||
});
|
||||
|
||||
test('detectBOM ANSI', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some_ansi.css');
|
||||
|
||||
const detectedEncoding = await encoding.detectEncodingByBOM(file);
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, null);
|
||||
});
|
||||
|
||||
test('detectBOM ANSI', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/empty.txt');
|
||||
|
||||
const detectedEncoding = await encoding.detectEncodingByBOM(file);
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, null);
|
||||
});
|
||||
|
||||
|
||||
@@ -9,23 +9,6 @@ import * as stream from 'vs/base/node/stream';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
|
||||
suite('Stream', () => {
|
||||
test('readExactlyByFile - ANSI', function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/file.css');
|
||||
|
||||
return stream.readExactlyByFile(file, 10).then(({ buffer, bytesRead }) => {
|
||||
assert.equal(bytesRead, 10);
|
||||
assert.equal(buffer!.toString(), '/*--------');
|
||||
});
|
||||
});
|
||||
|
||||
test('readExactlyByFile - empty', function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/empty.txt');
|
||||
|
||||
return stream.readExactlyByFile(file, 10).then(({ bytesRead }) => {
|
||||
assert.equal(bytesRead, 0);
|
||||
});
|
||||
});
|
||||
|
||||
test('readToMatchingString - ANSI', function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/file.css');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user