UNPKG

@theia/core

Version:

Theia is a cloud & desktop IDE framework implemented in TypeScript.

214 lines (186 loc) • 6.96 kB
// ***************************************************************************** // Copyright (C) 2021 Red Hat, Inc. and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { Disposable } from '../disposable'; import { Emitter, Event } from '../event'; import { ReadBuffer, WriteBuffer } from './message-buffer'; /** * The default {@link WriteBuffer} implementation. Uses a {@link Uint8Array} for buffering. * The {@link Uint8ArrayWriteBuffer.onCommit} hook can be used to rect to on-commit events. * After the {@link Uint8ArrayWriteBuffer.commit} method has been called the buffer is disposed * and can no longer be used for writing data. If the writer buffer is no longer needed but the message * has not been committed yet it has to be disposed manually. */ export class Uint8ArrayWriteBuffer implements WriteBuffer, Disposable { private encoder = new TextEncoder(); private msg: DataView; private isDisposed = false; private offset: number; constructor(private buffer: Uint8Array = new Uint8Array(1024), writePosition: number = 0) { this.offset = buffer.byteOffset + writePosition; this.msg = new DataView(buffer.buffer); } ensureCapacity(value: number): this { let newLength = this.buffer.byteLength; while (newLength < this.offset + value) { newLength *= 2; } if (newLength !== this.buffer.byteLength) { const newBuffer = new Uint8Array(newLength); newBuffer.set(this.buffer); this.buffer = newBuffer; this.msg = new DataView(this.buffer.buffer); } return this; } writeLength(length: number): this { if (length < 0 || (length % 1) !== 0) { throw new Error(`Could not write the given length value. '${length}' is not an integer >= 0`); } if (length < 127) { this.writeUint8(length); } else { this.writeUint8(128 + (length & 127)); this.writeLength(length >> 7); } return this; } writeNumber(value: number): this { this.ensureCapacity(8); this.msg.setFloat64(this.offset, value); this.offset += 8; return this; } writeUint8(value: number): this { this.ensureCapacity(1); this.buffer[this.offset++] = value; return this; } writeRaw(bytes: Uint8Array): this { this.ensureCapacity(bytes.byteLength); this.buffer.set(bytes, this.offset); this.offset += bytes.byteLength; return this; } writeUint16(value: number): this { this.ensureCapacity(2); this.msg.setUint16(this.offset, value); this.offset += 2; return this; } writeUint32(value: number): this { this.ensureCapacity(4); this.msg.setUint32(this.offset, value); this.offset += 4; return this; } writeString(value: string): this { this.ensureCapacity(4 * value.length); const result = this.encoder.encodeInto(value, this.buffer.subarray(this.offset + 4)); this.msg.setUint32(this.offset, result.written!); this.offset += 4 + result.written!; return this; } writeBytes(value: Uint8Array): this { this.writeLength(value.byteLength); this.ensureCapacity(value.length); this.buffer.set(value, this.offset); this.offset += value.length; return this; } private onCommitEmitter = new Emitter<Uint8Array>(); get onCommit(): Event<Uint8Array> { return this.onCommitEmitter.event; } commit(): void { if (this.isDisposed) { throw new Error("Could not invoke 'commit'. The WriteBuffer is already disposed."); } this.onCommitEmitter.fire(this.getCurrentContents()); this.dispose(); } getCurrentContents(): Uint8Array { return this.buffer.slice(this.buffer.byteOffset, this.offset); } dispose(): void { if (!this.isDisposed) { this.onCommitEmitter.dispose(); this.isDisposed = true; } } } /** * The default {@link ReadBuffer} implementation. Uses a {@link Uint8Array} for buffering. * Is for single message read. A message can only be read once. */ export class Uint8ArrayReadBuffer implements ReadBuffer { private offset: number = 0; private msg: DataView; private decoder = new TextDecoder(); constructor(private readonly buffer: Uint8Array, readPosition = 0) { this.offset = buffer.byteOffset + readPosition; this.msg = new DataView(buffer.buffer); } readUint8(): number { return this.msg.getUint8(this.offset++); } readUint16(): number { const result = this.msg.getUint16(this.offset); this.offset += 2; return result; } readUint32(): number { const result = this.msg.getUint32(this.offset); this.offset += 4; return result; } readLength(): number { let shift = 0; let byte = this.readUint8(); let value = (byte & 127) << shift; while (byte > 127) { shift += 7; byte = this.readUint8(); value = value + ((byte & 127) << shift); } return value; } readNumber(): number { const result = this.msg.getFloat64(this.offset); this.offset += 8; return result; } readString(): string { const len = this.readUint32(); const sliceOffset = this.offset - this.buffer.byteOffset; const result = this.decodeString(this.buffer.slice(sliceOffset, sliceOffset + len)); this.offset += len; return result; } private decodeString(buf: Uint8Array): string { return this.decoder.decode(buf); } readBytes(): Uint8Array { const length = this.readLength(); const sliceOffset = this.offset - this.buffer.byteOffset; const result = this.buffer.slice(sliceOffset, sliceOffset + length); this.offset += length; return result; } sliceAtReadPosition(): ReadBuffer { const sliceOffset = this.offset - this.buffer.byteOffset; return new Uint8ArrayReadBuffer(this.buffer, sliceOffset); } }