diff --git a/src/Terminal.ts b/src/Terminal.ts index 2cb1dcf08..391cd991f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -183,6 +183,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // user input states public writeBuffer: string[]; + public writeBufferUtf8: Uint8Array[]; private _writeInProgress: boolean; /** @@ -340,6 +341,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // user input states this.writeBuffer = []; + this.writeBufferUtf8 = []; this._writeInProgress = false; this._xoffSentToCatchUp = false; @@ -1366,20 +1368,85 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } /** - * Writes utf8 data to the terminal. - * TODO: This currently does no flow control. + * Writes raw utf8 bytes to the terminal. + * @param data The text to write to the terminal. */ public writeUtf8(data: Uint8Array): void { + // Ensure the terminal isn't disposed if (this._isDisposed) { return; } - this._refreshStart = this.buffer.y; - this._refreshEnd = this.buffer.y; - this._inputHandler.parseUtf8(data); + // Ignore falsy data values (including the empty string) + if (!data) { + return; + } - this.updateRange(this.buffer.y); - this.refresh(this._refreshStart, this._refreshEnd); + this.writeBufferUtf8.push(data); + + // Send XOFF to pause the pty process if the write buffer becomes too large so + // xterm.js can catch up before more data is sent. This is necessary in order + // to keep signals such as ^C responsive. + if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBufferUtf8.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { + // XOFF - stop pty pipe + // XON will be triggered by emulator before processing data chunk + this.handler(C0.DC3); + this._xoffSentToCatchUp = true; + } + + if (!this._writeInProgress && this.writeBufferUtf8.length > 0) { + // Kick off a write which will write all data in sequence recursively + this._writeInProgress = true; + // Kick off an async innerWrite so more writes can come in while processing data + setTimeout(() => { + this._innerWriteUtf8(); + }); + } + } + + protected _innerWriteUtf8(bufferOffset: number = 0): void { + // Ensure the terminal isn't disposed + if (this._isDisposed) { + this.writeBufferUtf8 = []; + } + + const startTime = Date.now(); + while (this.writeBufferUtf8.length > bufferOffset) { + const data = this.writeBufferUtf8[bufferOffset]; + bufferOffset++; + + // If XOFF was sent in order to catch up with the pty process, resume it if + // we reached the end of the writeBuffer to allow more data to come in. + if (this._xoffSentToCatchUp && this.writeBufferUtf8.length === bufferOffset) { + this.handler(C0.DC1); + this._xoffSentToCatchUp = false; + } + + this._refreshStart = this.buffer.y; + this._refreshEnd = this.buffer.y; + + // HACK: Set the parser state based on it's state at the time of return. + // This works around the bug #662 which saw the parser state reset in the + // middle of parsing escape sequence in two chunks. For some reason the + // state of the parser resets to 0 after exiting parser.parse. This change + // just sets the state back based on the correct return statement. + + this._inputHandler.parseUtf8(data); + + this.updateRange(this.buffer.y); + this.refresh(this._refreshStart, this._refreshEnd); + + if (Date.now() - startTime >= WRITE_TIMEOUT_MS) { + break; + } + } + if (this.writeBufferUtf8.length > bufferOffset) { + // Allow renderer to catch up before processing the next batch + setTimeout(() => this._innerWriteUtf8(bufferOffset), 0); + } else { + this._writeInProgress = false; + this.writeBufferUtf8 = []; + } } /**