Fix scroll region scrolling with 0 scrollback

This commit is contained in:
Daniel Imms
2017-08-25 11:26:46 -07:00
parent 6e9d60d76d
commit 9a8b7a4d8d
3 changed files with 228 additions and 33 deletions
+1 -2
View File
@@ -78,10 +78,9 @@ export class Buffer implements IBuffer {
this.ybase = 0;
this.y = 0;
this.x = 0;
this.scrollBottom = 0;
this.scrollTop = 0;
this.tabs = {};
this._lines = new CircularList<LineData>(this._getCorrectBufferLength(this._terminal.rows));
this.scrollTop = 0;
this.scrollBottom = this._terminal.rows - 1;
}
+139 -1
View File
@@ -3,6 +3,9 @@ import { Terminal } from './Terminal';
import { MockViewport, MockCompositionHelper } from './utils/TestUtils.test';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';
const INIT_COLS = 80;
const INIT_ROWS = 24;
class TestTerminal extends Terminal {
public evaluateKeyEscapeSequence(ev: any): {cancel: boolean, key: string, scrollDisp: number} { return this._evaluateKeyEscapeSequence(<KeyboardEvent>ev); }
public keyDown(ev: any): boolean { return this._keyDown(ev); }
@@ -13,7 +16,10 @@ describe('term.js addons', () => {
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal();
term = new TestTerminal({
cols: INIT_COLS,
rows: INIT_ROWS
});
term.refresh = () => {};
term.viewport = new MockViewport();
(<any>term).compositionHelper = new MockCompositionHelper();
@@ -283,6 +289,138 @@ describe('term.js addons', () => {
assert.equal(term.buffer.ydisp, startYDisp - 1);
});
});
describe('scroll() function', () => {
describe('when scrollback > 0', () => {
it('should create a new line and scroll', () => {
term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a';
term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX] = 'b';
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX], 'b');
assert.equal(term.buffer.lines.get(INIT_ROWS)[0][CHAR_DATA_CHAR_INDEX], ' ');
});
it('should properly scroll inside a scroll region (scrollTop set)', () => {
term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a';
term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b';
term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c';
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a');
assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c');
});
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a';
term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b';
term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c';
term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd';
term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e';
term.buffer.y = 3;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a', '\'a\' should be pushed to the scrollback');
assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'b');
assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'c');
assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], 'd');
assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(5)[0][CHAR_DATA_CHAR_INDEX], 'e');
});
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a';
term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b';
term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c';
term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd';
term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e';
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a');
assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer');
assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'd');
assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], 'e');
});
});
describe('when scrollback === 0', () => {
beforeEach(() => {
term.setOption('scrollback', 0);
assert.equal(term.buffer.lines.maxLength, INIT_ROWS);
});
it('should create a new line and shift everything up', () => {
term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a';
term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b';
term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX] = 'c';
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
assert.equal(term.buffer.lines.length, INIT_ROWS);
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
// 'a' gets pushed out of buffer
assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'b');
assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], ' ');
assert.equal(term.buffer.lines.get(INIT_ROWS - 2)[0][CHAR_DATA_CHAR_INDEX], 'c');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX], ' ');
});
it('should properly scroll inside a scroll region (scrollTop set)', () => {
term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a';
term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b';
term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c';
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a');
assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c');
});
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a';
term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b';
term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c';
term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd';
term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e';
term.buffer.y = 3;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'b');
assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c');
assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'd');
assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], 'e');
});
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a';
term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b';
term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c';
term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd';
term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e';
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a');
assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer');
assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'd');
assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], 'e');
});
});
});
});
describe('evaluateKeyEscapeSequence', () => {
+88 -30
View File
@@ -1167,46 +1167,104 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* line.
*/
public scroll(isWrapped?: boolean): void {
let row;
// The problem is caused when scrollback = 0. This section was written
// around the assumption that there would be buffer to trim when necessary.
// Make room for the new row in lines
const bufferNeedsTrimming = this.buffer.lines.length === this.buffer.lines.maxLength;
if (bufferNeedsTrimming) {
this.buffer.lines.trimStart(1);
this.buffer.ybase--;
this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0);
// shiftElements should be used here
// If scrollTop is set (non-zero), we should shiftElements
// scrollBottom defines the position at which the new element is inserted
// Note that both scrollBottom and scrollTop need to be handled individually
// because they can be set to terminal.rows or 0 respectively
// Don't mess with ybase temporarily, asking for trouble
// This should work with minimal effort utilizing the CiruclarList
// TODO: Need to hold onto top given the if?
const bottomRow = this.buffer.ybase + this.buffer.scrollBottom;
console.log('bottomRow', bottomRow);
console.log('this.buffer.scrollTop: ' + this.buffer.scrollTop);
if (this.buffer.scrollTop === 0) {
const willBufferBeTrimmed = this.buffer.lines.length === this.buffer.lines.maxLength;
if (bottomRow === this.buffer.lines.length - 1) {
// Pushing when possible is faster than splicing
this.buffer.lines.push(this.blankLine(undefined, isWrapped));
console.log('push');
} else {
// Insert a row *below* the bottomRow, pushing the top row into the scrollback
this.buffer.lines.splice(bottomRow + 1, 0, this.blankLine(undefined, isWrapped));
console.log('splice');
}
// Adjust ydisp and ybase to accommodate the changes after the buffer was
// trimmed.
if (!willBufferBeTrimmed) {
console.log('increment ydisp/ybase');
this.buffer.ybase++;
this.buffer.ydisp++;
}
console.log('this.buffer.ybase: ' + this.buffer.ybase);
console.log('this.buffer.ybase: ' + this.buffer.ydisp);
} else {
const topRow = this.buffer.ybase + this.buffer.scrollTop;
const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/;
console.log('shiftElements');
console.log('topRow: ' + topRow + '(' + this.buffer.lines.get(topRow)[0][1] + ')');
console.log('scrollRegionHeight: ' + scrollRegionHeight);
this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
this.buffer.lines.set(bottomRow, this.blankLine(undefined, isWrapped));
}
this.buffer.ybase++;
// Scroll the viewport down to the bottom if the user is not scrolling
if (!this.userScrolling) {
this.buffer.ydisp = this.buffer.ybase;
}
// last line
row = this.buffer.ybase + this.rows - 1;
// subtract the bottom scroll region
row -= this.rows - 1 - this.buffer.scrollBottom;
// Make room for the new row in lines
// const bufferNeedsTrimming = this.buffer.lines.length === this.buffer.lines.maxLength;
// if (bufferNeedsTrimming) {
// this.buffer.lines.trimStart(1);
// this.buffer.ybase--;
// this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0);
// }
if (row === this.buffer.lines.length) {
// Optimization: pushing is faster than splicing when they amount to the same behavior
this.buffer.lines.push(this.blankLine(undefined, isWrapped));
} else {
// add our new line
this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped));
}
// this.buffer.ybase++;
if (this.buffer.scrollTop !== 0) {
if (this.buffer.ybase !== 0) {
this.buffer.ybase--;
if (!this.userScrolling) {
this.buffer.ydisp = this.buffer.ybase;
}
}
this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1);
}
// // Scroll the viewport down to the bottom if the user is not scrolling
// if (!this.userScrolling) {
// this.buffer.ydisp = this.buffer.ybase;
// }
// // last line
// let row = this.buffer.ybase + this.rows - 1;
// // subtract the bottom scroll region
// row -= this.rows - 1 - this.buffer.scrollBottom;
// // Same as this?
// // row = this.buffer.ybase + this.buffer.scrollBottom;
// if (row === this.buffer.lines.length) {
// // Optimization: pushing is faster than splicing when they amount to the same behavior
// this.buffer.lines.push(this.blankLine(undefined, isWrapped));
// } else {
// // add our new line
// this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped));
// }
// if (this.buffer.scrollTop !== 0) {
// if (this.buffer.ybase !== 0) {
// this.buffer.ybase--;
// if (!this.userScrolling) {
// this.buffer.ydisp = this.buffer.ybase;
// }
// }
// this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1);
// }
// this.maxRange();
this.updateRange(this.buffer.scrollTop);