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

@@ -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 };

View File

@@ -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;
}
}
}

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.
*

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);
});

View File

@@ -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);
});
});

View File

@@ -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);
});

View File

@@ -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');