UNPKG

@sussudio/base

Version:

Internal APIs for VS Code's utilities and user interface building blocks.

317 lines (316 loc) 10.9 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as streams from './stream.mjs'; const hasBuffer = typeof Buffer !== 'undefined'; let textEncoder; let textDecoder; export class VSBuffer { /** * When running in a nodejs context, the backing store for the returned `VSBuffer` instance * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable. */ static alloc(byteLength) { if (hasBuffer) { return new VSBuffer(Buffer.allocUnsafe(byteLength)); } else { return new VSBuffer(new Uint8Array(byteLength)); } } /** * When running in a nodejs context, if `actual` is not a nodejs Buffer, the backing store for * the returned `VSBuffer` instance might use a nodejs Buffer allocated from node's Buffer pool, * which is not transferrable. */ static wrap(actual) { 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 actual = Buffer.from(actual.buffer, actual.byteOffset, actual.byteLength); } return new VSBuffer(actual); } /** * When running in a nodejs context, the backing store for the returned `VSBuffer` instance * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable. */ static fromString(source, options) { const dontUseNodeBuffer = options?.dontUseNodeBuffer || false; if (!dontUseNodeBuffer && hasBuffer) { return new VSBuffer(Buffer.from(source)); } else { if (!textEncoder) { textEncoder = new TextEncoder(); } return new VSBuffer(textEncoder.encode(source)); } } /** * When running in a nodejs context, the backing store for the returned `VSBuffer` instance * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable. */ static fromByteArray(source) { const result = VSBuffer.alloc(source.length); for (let i = 0, len = source.length; i < len; i++) { result.buffer[i] = source[i]; } return result; } /** * When running in a nodejs context, the backing store for the returned `VSBuffer` instance * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable. */ static concat(buffers, totalLength) { if (typeof totalLength === 'undefined') { totalLength = 0; for (let i = 0, len = buffers.length; i < len; i++) { totalLength += buffers[i].byteLength; } } const ret = VSBuffer.alloc(totalLength); let offset = 0; for (let i = 0, len = buffers.length; i < len; i++) { const element = buffers[i]; ret.set(element, offset); offset += element.byteLength; } return ret; } buffer; byteLength; constructor(buffer) { this.buffer = buffer; this.byteLength = this.buffer.byteLength; } /** * When running in a nodejs context, the backing store for the returned `VSBuffer` instance * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable. */ clone() { const result = VSBuffer.alloc(this.byteLength); result.set(this); return result; } toString() { if (hasBuffer) { return this.buffer.toString(); } else { if (!textDecoder) { textDecoder = new TextDecoder(); } return textDecoder.decode(this.buffer); } } slice(start, end) { // IMPORTANT: use subarray instead of slice because TypedArray#slice // creates shallow copy and NodeBuffer#slice doesn't. The use of subarray // ensures the same, performance, behaviour. return new VSBuffer(this.buffer.subarray(start, end)); } set(array, offset) { if (array instanceof VSBuffer) { this.buffer.set(array.buffer, offset); } else if (array instanceof Uint8Array) { this.buffer.set(array, offset); } else if (array instanceof ArrayBuffer) { this.buffer.set(new Uint8Array(array), offset); } else if (ArrayBuffer.isView(array)) { this.buffer.set(new Uint8Array(array.buffer, array.byteOffset, array.byteLength), offset); } else { throw new Error(`Unknown argument 'array'`); } } readUInt32BE(offset) { return readUInt32BE(this.buffer, offset); } writeUInt32BE(value, offset) { writeUInt32BE(this.buffer, value, offset); } readUInt32LE(offset) { return readUInt32LE(this.buffer, offset); } writeUInt32LE(value, offset) { writeUInt32LE(this.buffer, value, offset); } readUInt8(offset) { return readUInt8(this.buffer, offset); } writeUInt8(value, offset) { writeUInt8(this.buffer, value, offset); } } export function readUInt16LE(source, offset) { return ((source[offset + 0] << 0) >>> 0) | ((source[offset + 1] << 8) >>> 0); } export function writeUInt16LE(destination, value, offset) { destination[offset + 0] = value & 0b11111111; value = value >>> 8; destination[offset + 1] = value & 0b11111111; } export function readUInt32BE(source, offset) { return source[offset] * 2 ** 24 + source[offset + 1] * 2 ** 16 + source[offset + 2] * 2 ** 8 + source[offset + 3]; } export function writeUInt32BE(destination, value, offset) { destination[offset + 3] = value; value = value >>> 8; destination[offset + 2] = value; value = value >>> 8; destination[offset + 1] = value; value = value >>> 8; destination[offset] = value; } export function readUInt32LE(source, offset) { return ( ((source[offset + 0] << 0) >>> 0) | ((source[offset + 1] << 8) >>> 0) | ((source[offset + 2] << 16) >>> 0) | ((source[offset + 3] << 24) >>> 0) ); } export function writeUInt32LE(destination, value, offset) { destination[offset + 0] = value & 0b11111111; value = value >>> 8; destination[offset + 1] = value & 0b11111111; value = value >>> 8; destination[offset + 2] = value & 0b11111111; value = value >>> 8; destination[offset + 3] = value & 0b11111111; } export function readUInt8(source, offset) { return source[offset]; } export function writeUInt8(destination, value, offset) { destination[offset] = value; } export function readableToBuffer(readable) { return streams.consumeReadable(readable, (chunks) => VSBuffer.concat(chunks)); } export function bufferToReadable(buffer) { return streams.toReadable(buffer); } export function streamToBuffer(stream) { return streams.consumeStream(stream, (chunks) => VSBuffer.concat(chunks)); } export async function bufferedStreamToBuffer(bufferedStream) { if (bufferedStream.ended) { return VSBuffer.concat(bufferedStream.buffer); } return VSBuffer.concat([ // Include already read chunks... ...bufferedStream.buffer, // ...and all additional chunks await streamToBuffer(bufferedStream.stream), ]); } export function bufferToStream(buffer) { return streams.toStream(buffer, (chunks) => VSBuffer.concat(chunks)); } export function streamToBufferReadableStream(stream) { return streams.transform( stream, { data: (data) => (typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data)) }, (chunks) => VSBuffer.concat(chunks), ); } export function newWriteableBufferStream(options) { return streams.newWriteableStream((chunks) => VSBuffer.concat(chunks), options); } export function prefixedBufferReadable(prefix, readable) { return streams.prefixedReadable(prefix, readable, (chunks) => VSBuffer.concat(chunks)); } export function prefixedBufferStream(prefix, stream) { return streams.prefixedStream(prefix, stream, (chunks) => VSBuffer.concat(chunks)); } /** Decodes base64 to a uint8 array. URL-encoded and unpadded base64 is allowed. */ export function decodeBase64(encoded) { let building = 0; let remainder = 0; let bufi = 0; // The simpler way to do this is `Uint8Array.from(atob(str), c => c.charCodeAt(0))`, // but that's about 10-20x slower than this function in current Chromium versions. const buffer = new Uint8Array(Math.floor((encoded.length / 4) * 3)); const append = (value) => { switch (remainder) { case 3: buffer[bufi++] = building | value; remainder = 0; break; case 2: buffer[bufi++] = building | (value >>> 2); building = value << 6; remainder = 3; break; case 1: buffer[bufi++] = building | (value >>> 4); building = value << 4; remainder = 2; break; default: building = value << 2; remainder = 1; } }; for (let i = 0; i < encoded.length; i++) { const code = encoded.charCodeAt(i); // See https://datatracker.ietf.org/doc/html/rfc4648#section-4 // This branchy code is about 3x faster than an indexOf on a base64 char string. if (code >= 65 && code <= 90) { append(code - 65); // A-Z starts ranges from char code 65 to 90 } else if (code >= 97 && code <= 122) { append(code - 97 + 26); // a-z starts ranges from char code 97 to 122, starting at byte 26 } else if (code >= 48 && code <= 57) { append(code - 48 + 52); // 0-9 starts ranges from char code 48 to 58, starting at byte 52 } else if (code === 43 || code === 45) { append(62); // "+" or "-" for URLS } else if (code === 47 || code === 95) { append(63); // "/" or "_" for URLS } else if (code === 61) { break; // "=" } else { throw new SyntaxError(`Unexpected base64 character ${encoded[i]}`); } } const unpadded = bufi; while (remainder > 0) { append(0); } // slice is needed to account for overestimation due to padding return VSBuffer.wrap(buffer).slice(0, unpadded); } const base64Alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; const base64UrlSafeAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; /** Encodes a buffer to a base64 string. */ export function encodeBase64({ buffer }, padded = true, urlSafe = false) { const dictionary = urlSafe ? base64UrlSafeAlphabet : base64Alphabet; let output = ''; const remainder = buffer.byteLength % 3; let i = 0; for (; i < buffer.byteLength - remainder; i += 3) { const a = buffer[i + 0]; const b = buffer[i + 1]; const c = buffer[i + 2]; output += dictionary[a >>> 2]; output += dictionary[((a << 4) | (b >>> 4)) & 0b111111]; output += dictionary[((b << 2) | (c >>> 6)) & 0b111111]; output += dictionary[c & 0b111111]; } if (remainder === 1) { const a = buffer[i + 0]; output += dictionary[a >>> 2]; output += dictionary[(a << 4) & 0b111111]; if (padded) { output += '=='; } } else if (remainder === 2) { const a = buffer[i + 0]; const b = buffer[i + 1]; output += dictionary[a >>> 2]; output += dictionary[((a << 4) | (b >>> 4)) & 0b111111]; output += dictionary[(b << 2) & 0b111111]; if (padded) { output += '='; } } return output; }