Resize both buffers on resize

This brings in proper support to resize both buffers (#510) and fixes an
exception that was caused by wrongfully not clearing the normal buffer when
resizing while the alt buffer is active.

There was an obscure bug in this that could have caused some great confusion
later on; When switching to the alt buffer, a hard terminal reset was performed
which tried to retain the buffers. However, because buffers was initialized in
the Terminal constructor to a new BufferSet, the Terminal.buffer convenience
pointer was pointing at a stale alt buffer which was the one actually being
used, not Terminal.buffers.alt.

Fixes #842
Fixes #510
This commit is contained in:
Daniel Imms
2017-08-05 18:59:44 -07:00
parent f5ba386c01
commit 3d20c2f266
4 changed files with 94 additions and 82 deletions
+76 -5
View File
@@ -13,21 +13,21 @@ import { CircularList } from './utils/CircularList';
* - scroll position
*/
export class Buffer {
public lines: CircularList<[number, string, number][]>;
public readonly lines: CircularList<[number, string, number][]>;
public savedY: number;
public savedX: number;
/**
* Create a new Buffer.
* @param {Terminal} terminal - The terminal the Buffer will belong to
* @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
*/
constructor(
private terminal: ITerminal,
private _terminal: ITerminal,
public ydisp: number = 0,
public ybase: number = 0,
public y: number = 0,
@@ -36,7 +36,78 @@ export class Buffer {
public scrollTop: number = 0,
public tabs: any = {},
) {
this.lines = new CircularList<[number, string, number][]>(this.terminal.scrollback);
this.scrollBottom = this.terminal.rows - 1;
this.lines = new CircularList<[number, string, number][]>(this._terminal.scrollback);
this.scrollBottom = this._terminal.rows - 1;
}
public resize(newCols: number, newRows: number): void {
// Don't resize the buffer if it's empty and hasn't been used yet.
if (this.lines.length === 0) {
return;
}
// Deal with columns increasing (we don't do anything when columns reduce)
if (this._terminal.cols < newCols) {
const ch: [number, string, number] = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr?
for (let i = 0; i < this.lines.length; i++) {
if (this.lines.get(i) === undefined) {
this.lines.set(i, this._terminal.blankLine());
}
while (this.lines.get(i).length < newCols) {
this.lines.get(i).push(ch);
}
}
}
// Resize rows in both directions as needed
let addToY = 0;
if (this._terminal.rows < newRows) {
for (let y = this._terminal.rows; y < newRows; y++) {
if (this.lines.length < newRows + this.ybase) {
if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
// There is room above the buffer and there are no empty elements below the line,
// scroll up
this.ybase--;
addToY++;
if (this.ydisp > 0) {
// Viewport is at the top of the buffer, must increase downwards
this.ydisp--;
}
} else {
// Add a blank line if there is no buffer left at the top to scroll to, or if there
// are blank lines after the cursor
this.lines.push(this._terminal.blankLine());
}
}
}
} else { // (this._terminal.rows >= newRows)
for (let y = this._terminal.rows; y > newRows; y--) {
if (this.lines.length > newRows + this.ybase) {
if (this.lines.length > this.ybase + this.y + 1) {
// The line is a blank line below the cursor, remove it
this.lines.pop();
} else {
// The line is the cursor, scroll down
this.ybase++;
this.ydisp++;
}
}
}
}
// Make sure that the cursor stays on screen
if (this.y >= newRows) {
this.y = newRows - 1;
}
if (addToY) {
this.y += addToY;
}
if (this.x >= newCols) {
this.x = newCols - 1;
}
this.scrollTop = 0;
this.scrollBottom = newRows - 1;
}
}
+5
View File
@@ -65,4 +65,9 @@ export class BufferSet extends EventEmitter implements IBufferSet {
this._activeBuffer = this._alt;
this.emit('activate', this._alt);
}
public resize(newCols: number, newRows: number): void {
this._normal.resize(newCols, newRows);
this._alt.resize(newCols, newRows);
}
}
+1
View File
@@ -48,6 +48,7 @@ export interface ITerminal {
emit(event: string, data: any);
reset(): void;
showCursor(): void;
blankLine(cur?: boolean, isWrapped?: boolean);
}
export interface IBuffer {
+12 -77
View File
@@ -221,7 +221,7 @@ function Terminal(options) {
this.surrogate_high = '';
// Create the terminal's buffers and set the current buffer
this.buffers = new BufferSet(this);
this.buffers = this.buffers || new BufferSet(this);
this.buffer = this.buffers.active; // Convenience shortcut;
this.buffers.on('activate', function (buffer) {
this._terminal.buffer = buffer;
@@ -1930,86 +1930,21 @@ Terminal.prototype.resize = function(x, y) {
if (x < 1) x = 1;
if (y < 1) y = 1;
// resize cols
j = this.cols;
if (j < x) {
ch = [this.defAttr, ' ', 1]; // does xterm use the default attr?
i = this.buffer.lines.length;
while (i--) {
if (this.buffer.lines.get(i) === undefined) {
this.buffer.lines.set(i, this.blankLine());
}
while (this.buffer.lines.get(i).length < x) {
this.buffer.lines.get(i).push(ch);
}
}
this.buffers.resize(x, y);
// Adjust rows in the DOM to accurately reflect the new dimensions
while (this.children.length < y) {
this.insertRow();
}
while (this.children.length > y) {
el = this.children.shift();
if (!el) continue;
el.parentNode.removeChild(el);
}
this.cols = x;
this.setupStops(this.cols);
// resize rows
j = this.rows;
addToY = 0;
if (j < y) {
el = this.element;
while (j++ < y) {
// y is rows, not this.buffer.y
if (this.buffer.lines.length < y + this.buffer.ybase) {
if (this.buffer.ybase > 0 && this.buffer.lines.length <= this.buffer.ybase + this.buffer.y + addToY + 1) {
// There is room above the buffer and there are no empty elements below the line,
// scroll up
this.buffer.ybase--;
addToY++;
if (this.buffer.ydisp > 0) {
// Viewport is at the top of the buffer, must increase downwards
this.buffer.ydisp--;
}
} else {
// Add a blank line if there is no buffer left at the top to scroll to, or if there
// are blank lines after the cursor
this.buffer.lines.push(this.blankLine());
}
}
if (this.children.length < y) {
this.insertRow();
}
}
} else { // (j > y)
while (j-- > y) {
if (this.buffer.lines.length > y + this.buffer.ybase) {
if (this.buffer.lines.length > this.buffer.ybase + this.buffer.y + 1) {
// The line is a blank line below the cursor, remove it
this.buffer.lines.pop();
} else {
// The line is the cursor, scroll down
this.buffer.ybase++;
this.buffer.ydisp++;
}
}
if (this.children.length > y) {
el = this.children.shift();
if (!el) continue;
el.parentNode.removeChild(el);
}
}
}
this.rows = y;
// Make sure that the cursor stays on screen
if (this.buffer.y >= y) {
this.buffer.y = y - 1;
}
if (addToY) {
this.buffer.y += addToY;
}
if (this.buffer.x >= x) {
this.buffer.x = x - 1;
}
this.buffer.scrollTop = 0;
this.buffer.scrollBottom = y - 1;
this.setupStops(this.cols);
this.charMeasure.measure();