mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Fix scroll region scrolling with 0 scrollback
This commit is contained in:
+1
-2
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user