Skip to content

Commit

Permalink
Merge branch '842_resize_buffers_bug' into 335_xterm_to_ts
Browse files Browse the repository at this point in the history
setOption('scrollback', 0) no longer returns false, it just warns.
  • Loading branch information
Tyriar committed Aug 6, 2017
2 parents f9fce53 + d460038 commit 56ec6ed
Show file tree
Hide file tree
Showing 12 changed files with 2,739 additions and 125 deletions.
128 changes: 121 additions & 7 deletions src/Buffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { Buffer } from './Buffer';
import { CircularList } from './utils/CircularList';
import { MockTerminal } from './utils/TestUtils';

const INIT_COLS = 80;
const INIT_ROWS = 24;

describe('Buffer', () => {
let terminal: ITerminal;
let buffer: Buffer;

beforeEach(() => {
terminal = <any>{
cols: 80,
rows: 24,
options: {
scrollback: 1000
}
};
terminal = new MockTerminal();
terminal.cols = INIT_COLS;
terminal.rows = INIT_ROWS;
terminal.options.scrollback = 1000;
buffer = new Buffer(terminal);
});

Expand All @@ -30,4 +31,117 @@ describe('Buffer', () => {
assert.equal(buffer.scrollBottom, terminal.rows - 1);
});
});

describe('fillViewportRows', () => {
it('should fill the buffer with blank lines based on the size of the viewport', () => {
const blankLineChar = terminal.blankLine()[0];
buffer.fillViewportRows();
assert.equal(buffer.lines.length, INIT_ROWS);
for (let y = 0; y < INIT_ROWS; y++) {
assert.equal(buffer.lines.get(y).length, INIT_COLS);
for (let x = 0; x < INIT_COLS; x++) {
assert.deepEqual(buffer.lines.get(y)[x], blankLineChar);
}
}
});
});

describe('resize', () => {
describe('column size is reduced', () => {
it('should not trim the data in the buffer', () => {
buffer.fillViewportRows();
buffer.resize(INIT_COLS / 2, INIT_ROWS);
assert.equal(buffer.lines.length, INIT_ROWS);
for (let i = 0; i < INIT_ROWS; i++) {
assert.equal(buffer.lines.get(i).length, INIT_COLS);
}
});
});

describe('column size is increased', () => {
it('should add pad columns', () => {
buffer.fillViewportRows();
buffer.resize(INIT_COLS + 10, INIT_ROWS);
assert.equal(buffer.lines.length, INIT_ROWS);
for (let i = 0; i < INIT_ROWS; i++) {
assert.equal(buffer.lines.get(i).length, INIT_COLS + 10);
}
});
});

describe('row size reduced', () => {
it('should trim blank lines from the end', () => {
buffer.fillViewportRows();
buffer.resize(INIT_COLS, INIT_ROWS - 10);
assert.equal(buffer.lines.length, INIT_ROWS - 10);
});

it('should move the viewport down when it\'s at the end', () => {
buffer.fillViewportRows();
// Set cursor y to have 5 blank lines below it
buffer.y = INIT_ROWS - 5 - 1;
buffer.resize(INIT_COLS, INIT_ROWS - 10);
// Trim 5 rows
assert.equal(buffer.lines.length, INIT_ROWS - 5);
// Shift the viewport down 5 rows
assert.equal(buffer.ydisp, 5);
assert.equal(buffer.ybase, 5);
});
});

describe('row size increased', () => {
describe('empty buffer', () => {
it('should add blank lines to end', () => {
buffer.fillViewportRows();
assert.equal(buffer.ydisp, 0);
buffer.resize(INIT_COLS, INIT_ROWS + 10);
assert.equal(buffer.ydisp, 0);
assert.equal(buffer.lines.length, INIT_ROWS + 10);
});
});

describe('filled buffer', () => {
it('should show more of the buffer above', () => {
buffer.fillViewportRows();
// Create 10 extra blank lines
for (let i = 0; i < 10; i++) {
buffer.lines.push(terminal.blankLine());
}
// Set cursor to the bottom of the buffer
buffer.y = INIT_ROWS - 1;
// Scroll down 10 lines
buffer.ybase = 10;
buffer.ydisp = 10;
assert.equal(buffer.lines.length, INIT_ROWS + 10);
buffer.resize(INIT_COLS, INIT_ROWS + 5);
// Should be should 5 more lines
assert.equal(buffer.ydisp, 5);
assert.equal(buffer.ybase, 5);
// Should not trim the buffer
assert.equal(buffer.lines.length, INIT_ROWS + 10);
});

it('should show more of the buffer below when the viewport is at the top of the buffer', () => {
buffer.fillViewportRows();
// Create 10 extra blank lines
for (let i = 0; i < 10; i++) {
buffer.lines.push(terminal.blankLine());
}
// Set cursor to the bottom of the buffer
buffer.y = INIT_ROWS - 1;
// Scroll down 10 lines
buffer.ybase = 10;
buffer.ydisp = 0;
assert.equal(buffer.lines.length, INIT_ROWS + 10);
buffer.resize(INIT_COLS, INIT_ROWS + 5);
// The viewport should remain at the top
assert.equal(buffer.ydisp, 0);
// The buffer ybase should move up 5 lines
assert.equal(buffer.ybase, 5);
// Should not trim the buffer
assert.equal(buffer.lines.length, INIT_ROWS + 10);
});
});
});
});
});
120 changes: 107 additions & 13 deletions src/Buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,125 @@ import { CircularList } from './utils/CircularList';
* - scroll position
*/
export class Buffer implements IBuffer {
public lines: CircularList<[number, string, number][]>;
private _lines: CircularList<[number, string, number][]>;

public ydisp: number;
public ybase: number;
public y: number;
public x: number;
public scrollBottom: number;
public scrollTop: number;
public tabs: any;
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,
public ydisp: number = 0,
public ybase: number = 0,
public y: number = 0,
public x: number = 0,
public scrollBottom: number = 0,
public scrollTop: number = 0,
public tabs: any = {},
private _terminal: ITerminal
) {
// TODO: Don't cast terminal to any
this.lines = new CircularList<[number, string, number][]>(this.terminal.options.scrollback);
this.scrollBottom = this.terminal.rows - 1;
this.clear();
}

public get lines(): CircularList<[number, string, number][]> {
return this._lines;
}

public fillViewportRows(): void {
if (this._lines.length === 0) {
let i = this._terminal.rows;
while (i--) {
this.lines.push(this._terminal.blankLine());
}
}
}

public clear(): void {
this.ydisp = 0;
this.ybase = 0;
this.y = 0;
this.x = 0;
this.scrollBottom = 0;
this.scrollTop = 0;
this.tabs = {};
this._lines = new CircularList<[number, string, number][]>(this._terminal.options.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;
}
}
12 changes: 5 additions & 7 deletions src/BufferSet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,17 @@ import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { BufferSet } from './BufferSet';
import { Buffer } from './Buffer';
import { MockTerminal } from './utils/TestUtils';

describe('BufferSet', () => {
let terminal: ITerminal;
let bufferSet: BufferSet;

beforeEach(() => {
terminal = <any>{
cols: 80,
rows: 24,
options: {
scrollback: 1000
}
};
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 24;
terminal.options.scrollback = 1000;
bufferSet = new BufferSet(terminal);
});

Expand Down
15 changes: 15 additions & 0 deletions src/BufferSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
constructor(private _terminal: ITerminal) {
super();
this._normal = new Buffer(this._terminal);
this._normal.fillViewportRows();
this._alt = new Buffer(this._terminal);
this._activeBuffer = this._normal;
}
Expand Down Expand Up @@ -54,6 +55,11 @@ export class BufferSet extends EventEmitter implements IBufferSet {
* Sets the normal Buffer of the BufferSet as its currently active Buffer
*/
public activateNormalBuffer(): void {
// The alt buffer should always be cleared when we switch to the normal
// buffer. This frees up memory since the alt buffer should always be new
// when activated.
this._alt.clear();

this._activeBuffer = this._normal;
this.emit('activate', this._normal);
}
Expand All @@ -62,7 +68,16 @@ export class BufferSet extends EventEmitter implements IBufferSet {
* Sets the alt Buffer of the BufferSet as its currently active Buffer
*/
public activateAltBuffer(): void {
// 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);
}

public resize(newCols: number, newRows: number): void {
this._normal.resize(newCols, newRows);
this._alt.resize(newCols, newRows);
}
}
1 change: 0 additions & 1 deletion src/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,6 @@ export class InputHandler implements IInputHandler {
case 47: // alt screen buffer
case 1047: // alt screen buffer
this._terminal.buffers.activateAltBuffer();
this._terminal.reset();
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
break;
Expand Down
1 change: 1 addition & 0 deletions src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface ITerminal extends IEventEmitter {
log(text: string): void;
reset(): void;
showCursor(): void;
blankLine(cur?: boolean, isWrapped?: boolean);
}

/**
Expand Down
Loading

0 comments on commit 56ec6ed

Please sign in to comment.