diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 1e220915..2e2ae50c 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -20,7 +20,7 @@ describe('Buffer', () => { terminal.cols = INIT_COLS; terminal.rows = INIT_ROWS; terminal.options.scrollback = 1000; - buffer = new Buffer(terminal); + buffer = new Buffer(terminal, false); }); describe('constructor', () => { @@ -145,4 +145,20 @@ describe('Buffer', () => { }); }); }); + + describe('alt buffer', () => { + it('should always have a scrollback of 0', () => { + assert.equal(terminal.options.scrollback, 1000); + // Test size on initialization + buffer = new Buffer(terminal, true); + buffer.fillViewportRows(); + assert.equal(buffer.lines.maxLength, INIT_ROWS); + // Test size on buffer increase + buffer.resize(INIT_COLS, INIT_ROWS * 2); + assert.equal(buffer.lines.maxLength, INIT_ROWS * 2); + // Test size on buffer decrease + buffer.resize(INIT_COLS, INIT_ROWS / 2); + assert.equal(buffer.lines.maxLength, INIT_ROWS / 2); + }); + }); }); diff --git a/src/Buffer.ts b/src/Buffer.ts index 9d2222ff..5fe97779 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -28,14 +28,12 @@ export class Buffer implements IBuffer { /** * Create a new Buffer. - * @param {Terminal} _terminal - The terminal the Buffer will belong to - * @param {number} ydisp - The scroll position of the Buffer in the viewport - * @param {number} ybase - The scroll position of the y cursor (ybase + y = the y position within the Buffer) - * @param {number} y - The cursor's y position after ybase - * @param {number} x - The cursor's x position after ybase + * @param _terminal The terminal the Buffer will belong to. + * @param _isAltBuffer Whether the buffer is the alt buffer. */ constructor( - private _terminal: ITerminal + private _terminal: ITerminal, + private _isAltBuffer: boolean ) { this.clear(); } @@ -44,6 +42,20 @@ export class Buffer implements IBuffer { return this._lines; } + /** + * Gets the correct buffer length based on the rows provided, the terminal's + * scrollback and whether this is an alt buffer. + * @param rows The terminal rows to use in the calculation. + */ + private _getCorrectBufferLength(rows: number): number { + // The alt buffer should never have scrollback. + // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer + if (this._isAltBuffer) { + return rows; + } + return rows + this._terminal.options.scrollback; + } + /** * Fills the buffer's viewport with blank lines. */ @@ -67,7 +79,7 @@ export class Buffer implements IBuffer { this.scrollBottom = 0; this.scrollTop = 0; this.tabs = {}; - this._lines = new CircularList(this._terminal.rows + this._terminal.options.scrollback); + this._lines = new CircularList(this._getCorrectBufferLength(this._terminal.rows)); this.scrollBottom = this._terminal.rows - 1; } @@ -82,6 +94,13 @@ export class Buffer implements IBuffer { return; } + // Increase max length if needed before adjustments to allow space to fill + // as required. + const newMaxLength = this._getCorrectBufferLength(newRows); + if (newMaxLength > this._lines.maxLength) { + this._lines.maxLength = newMaxLength; + } + // Deal with columns increasing (we don't do anything when columns reduce) if (this._terminal.cols < newCols) { const ch: CharData = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr? @@ -131,6 +150,19 @@ export class Buffer implements IBuffer { } } + // Reduce max length if needed after adjustments, this is done after as it + // would otherwise cut data from the bottom of the buffer. + if (newMaxLength < this._lines.maxLength) { + // Trim from the top of the buffer and adjust ybase and ydisp. + // const amountToTrim = this._lines.length - newMaxLength; + // if (amountToTrim > 0) { + // this._lines.trimStart(amountToTrim); + // this.ybase = Math.max(this.ybase - amountToTrim, 0); + // this.ydisp = Math.max(this.ydisp - amountToTrim, 0); + // } + this._lines.maxLength = newMaxLength; + } + // Make sure that the cursor stays on screen if (this.y >= newRows) { this.y = newRows - 1; diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 9136c5fb..851fc47d 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -21,9 +21,9 @@ export class BufferSet extends EventEmitter implements IBufferSet { */ constructor(private _terminal: ITerminal) { super(); - this._normal = new Buffer(this._terminal); + this._normal = new Buffer(this._terminal, false); this._normal.fillViewportRows(); - this._alt = new Buffer(this._terminal); + this._alt = new Buffer(this._terminal, true); this._activeBuffer = this._normal; } @@ -71,22 +71,10 @@ export class BufferSet extends EventEmitter implements IBufferSet { // Since the alt buffer is always cleared when the normal buffer is // activated, we want to fill it when switching to it. this._alt.fillViewportRows(); - this._activeBuffer = this._alt; this.emit('activate', this._alt); } - /** - * Refreshes the max length of the buffer set, this should be called whenever - * terminal rows or scrollback changes. - * @param rows The number of terminal rows. - */ - public refreshMaxLength(rows: number): void { - this._normal.lines.maxLength = rows + this._terminal.options.scrollback; - // TODO: The alt buffer should never have scrollback, see https://github.com/sourcelair/xterm.js/issues/802 - this._alt.lines.maxLength = rows + this._terminal.options.scrollback; - } - /** * Resizes both normal and alt buffers, adjusting their data accordingly. * @param newCols The new number of columns. @@ -95,6 +83,5 @@ export class BufferSet extends EventEmitter implements IBufferSet { public resize(newCols: number, newRows: number): void { this._normal.resize(newCols, newRows); this._alt.resize(newCols, newRows); - this.refreshMaxLength(newRows); } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 5362cae9..4036ac15 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -442,23 +442,13 @@ export class InputHandler implements IInputHandler { } let row: number = this._terminal.buffer.y + this._terminal.buffer.ybase; - let j: number; - j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; - j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1; - + let scrollBottomRowsOffset = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; + let scrollBottomAbsolute = this._terminal.rows - 1 + this._terminal.buffer.ybase - scrollBottomRowsOffset + 1; while (param--) { - if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) { - // Trim the start of lines to make room for the new line - this._terminal.buffer.lines.trimStart(1); - this._terminal.buffer.ybase--; - this._terminal.buffer.ydisp--; - row--; - j--; - } // test: echo -e '\e[44m\e[1L\e[0m' // blankLine(true) - xterm/linux behavior + this._terminal.buffer.lines.splice(scrollBottomAbsolute - 1, 1); this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true)); - this._terminal.buffer.lines.splice(j, 1); } // this.maxRange(); @@ -480,18 +470,11 @@ export class InputHandler implements IInputHandler { let j: number; j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j; - while (param--) { - if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) { - // Trim the start of lines to make room for the new line - this._terminal.buffer.lines.trimStart(1); - this._terminal.buffer.ybase -= 1; - this._terminal.buffer.ydisp -= 1; - } // test: echo -e '\e[44m\e[1M\e[0m' // blankLine(true) - xterm/linux behavior - this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true)); - this._terminal.buffer.lines.splice(row, 1); + this._terminal.buffer.lines.splice(row - 1, 1); + this._terminal.buffer.lines.splice(j, 0, this._terminal.blankLine(true)); } // this.maxRange(); diff --git a/src/Terminal.ts b/src/Terminal.ts index 2afb4502..743537b1 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -464,7 +464,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar'); break; case 'scrollback': - this.buffers.refreshMaxLength(this.rows); + this.buffers.resize(this.cols, this.rows); this.viewport.syncScrollArea(); break; case 'tabStopWidth': this.setupStops(); break; @@ -1177,17 +1177,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT let row; // Make room for the new row in lines - if (this.buffer.lines.length === this.buffer.lines.maxLength) { + const bufferNeedsTrimming = this.buffer.lines.length === this.buffer.lines.maxLength; + if (bufferNeedsTrimming) { this.buffer.lines.trimStart(1); this.buffer.ybase--; - if (this.buffer.ydisp !== 0) { - this.buffer.ydisp--; - } + this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); } this.buffer.ybase++; - // TODO: Why is this done twice? + // Scroll the viewport down to the bottom if the user is not scrolling if (!this.userScrolling) { this.buffer.ydisp = this.buffer.ybase; }