Skip to content

Commit 76e7057

Browse files
authoredNov 23, 2018
Merge pull request #1764 from whydoubt/alt_buffer
Alternate buffer improvement
2 parents c6c6758 + 62b1aa5 commit 76e7057

8 files changed

+132
-23
lines changed
 

‎src/Buffer.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export class Buffer implements IBuffer {
3838
public tabs: any;
3939
public savedY: number;
4040
public savedX: number;
41+
public savedCurAttr: number;
4142
public markers: Marker[] = [];
4243
private _bufferLineConstructor: IBufferLineConstructor;
4344

@@ -113,11 +114,14 @@ export class Buffer implements IBuffer {
113114
/**
114115
* Fills the buffer's viewport with blank lines.
115116
*/
116-
public fillViewportRows(): void {
117+
public fillViewportRows(fillAttr?: number): void {
117118
if (this.lines.length === 0) {
119+
if (fillAttr === undefined) {
120+
fillAttr = DEFAULT_ATTR;
121+
}
118122
let i = this._terminal.rows;
119123
while (i--) {
120-
this.lines.push(this.getBlankLine(DEFAULT_ATTR));
124+
this.lines.push(this.getBlankLine(fillAttr));
121125
}
122126
}
123127
}

‎src/BufferSet.test.ts

+26
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,30 @@ describe('BufferSet', () => {
4848
assert.equal(bufferSet.active, bufferSet.alt);
4949
});
5050
});
51+
52+
describe('cursor handling when swapping buffers', () => {
53+
beforeEach(() => {
54+
bufferSet.normal.x = 0;
55+
bufferSet.normal.y = 0;
56+
bufferSet.alt.x = 0;
57+
bufferSet.alt.y = 0;
58+
});
59+
60+
it('should keep the cursor stationary when activating alt buffer', () => {
61+
bufferSet.activateNormalBuffer();
62+
bufferSet.active.x = 30;
63+
bufferSet.active.y = 10;
64+
bufferSet.activateAltBuffer();
65+
assert.equal(bufferSet.active.x, 30);
66+
assert.equal(bufferSet.active.y, 10);
67+
});
68+
it('should keep the cursor stationary when activating normal buffer', () => {
69+
bufferSet.activateAltBuffer();
70+
bufferSet.active.x = 30;
71+
bufferSet.active.y = 10;
72+
bufferSet.activateNormalBuffer();
73+
assert.equal(bufferSet.active.x, 30);
74+
assert.equal(bufferSet.active.y, 10);
75+
});
76+
});
5177
});

‎src/BufferSet.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ export class BufferSet extends EventEmitter implements IBufferSet {
6161
if (this._activeBuffer === this._normal) {
6262
return;
6363
}
64+
this._normal.x = this._alt.x;
65+
this._normal.y = this._alt.y;
6466
// The alt buffer should always be cleared when we switch to the normal
6567
// buffer. This frees up memory since the alt buffer should always be new
6668
// when activated.
@@ -75,13 +77,15 @@ export class BufferSet extends EventEmitter implements IBufferSet {
7577
/**
7678
* Sets the alt Buffer of the BufferSet as its currently active Buffer
7779
*/
78-
public activateAltBuffer(): void {
80+
public activateAltBuffer(fillAttr?: number): void {
7981
if (this._activeBuffer === this._alt) {
8082
return;
8183
}
8284
// Since the alt buffer is always cleared when the normal buffer is
8385
// activated, we want to fill it when switching to it.
84-
this._alt.fillViewportRows();
86+
this._alt.fillViewportRows(fillAttr);
87+
this._alt.x = this._normal.x;
88+
this._alt.y = this._normal.y;
8589
this._activeBuffer = this._alt;
8690
this.emit('activate', {
8791
activeBuffer: this._alt,

‎src/InputHandler.test.ts

+62-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { assert, expect } from 'chai';
77
import { InputHandler } from './InputHandler';
88
import { MockInputHandlingTerminal } from './utils/TestUtils.test';
9-
import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, CHAR_DATA_CHAR_INDEX } from './Buffer';
9+
import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, DEFAULT_ATTR } from './Buffer';
1010
import { Terminal } from './Terminal';
1111
import { IBufferLine } from './Types';
1212

@@ -506,4 +506,65 @@ describe('InputHandler', () => {
506506
inputHandler.print(String.fromCharCode(0x200B), 0, 1);
507507
});
508508
});
509+
510+
describe('alt screen', () => {
511+
let term: Terminal;
512+
let handler: InputHandler;
513+
514+
function lineContent(line: IBufferLine): string {
515+
let content = '';
516+
for (let i = 0; i < line.length; ++i) content += line.get(i)[CHAR_DATA_CHAR_INDEX];
517+
return content;
518+
}
519+
520+
beforeEach(() => {
521+
term = new Terminal();
522+
handler = new InputHandler(term);
523+
});
524+
it('should handle DECSET/DECRST 47 (alt screen buffer)', () => {
525+
handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
526+
expect(lineContent(term.buffer.lines.get(0))).to.equal(Array(term.cols + 1).join(' '));
527+
expect(lineContent(term.buffer.lines.get(1))).to.equal(' TEST' + Array(term.cols - 7).join(' '));
528+
// Text color of 'TEST' should be red
529+
expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
530+
});
531+
it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => {
532+
handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST');
533+
expect(lineContent(term.buffer.lines.get(0))).to.equal(Array(term.cols + 1).join(' '));
534+
expect(lineContent(term.buffer.lines.get(1))).to.equal(' TEST' + Array(term.cols - 7).join(' '));
535+
// Text color of 'TEST' should be red
536+
expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
537+
});
538+
it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => {
539+
handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST');
540+
expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' '));
541+
expect(lineContent(term.buffer.lines.get(1))).to.equal('JUNK' + Array(term.cols - 3).join(' '));
542+
// Text color of 'TEST' should be default
543+
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
544+
// Text color of 'JUNK' should be red
545+
expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
546+
});
547+
it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => {
548+
handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST');
549+
expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' '));
550+
expect(lineContent(term.buffer.lines.get(1))).to.equal(Array(term.cols + 1).join(' '));
551+
// Text color of 'TEST' should be default
552+
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
553+
});
554+
it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => {
555+
handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST');
556+
expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' '));
557+
// Text color of 'TEST' should be default
558+
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
559+
handler.parse('\x1b[?1049h\x1b[uTEST');
560+
expect(lineContent(term.buffer.lines.get(1))).to.equal('TEST' + Array(term.cols - 3).join(' '));
561+
// Text color of 'TEST' should be red
562+
expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
563+
});
564+
it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => {
565+
handler.parse('\x1b[42m\x1b[?1049h');
566+
// Buffer should be filled with green background
567+
expect(term.buffer.lines.get(20).get(10)[CHAR_DATA_ATTR_INDEX] & 0x1ff).to.equal(2);
568+
});
569+
});
509570
});

‎src/InputHandler.ts

+29-14
Original file line numberDiff line numberDiff line change
@@ -1284,7 +1284,9 @@ export class InputHandler extends Disposable implements IInputHandler {
12841284
case 66:
12851285
this._terminal.log('Serial port requested application keypad.');
12861286
this._terminal.applicationKeypad = true;
1287-
this._terminal.viewport.syncScrollArea();
1287+
if (this._terminal.viewport) {
1288+
this._terminal.viewport.syncScrollArea();
1289+
}
12881290
break;
12891291
case 9: // X10 Mouse
12901292
// no release, no motion, no wheel, no modifiers.
@@ -1333,14 +1335,19 @@ export class InputHandler extends Disposable implements IInputHandler {
13331335
case 25: // show cursor
13341336
this._terminal.cursorHidden = false;
13351337
break;
1338+
case 1048: // alt screen cursor
1339+
this.saveCursor(params);
1340+
break;
13361341
case 1049: // alt screen buffer cursor
1337-
// TODO: Not sure if we need to save/restore after switching the buffer
1338-
// this.saveCursor(params);
1342+
this.saveCursor(params);
13391343
// FALL-THROUGH
13401344
case 47: // alt screen buffer
13411345
case 1047: // alt screen buffer
1342-
this._terminal.buffers.activateAltBuffer();
1343-
this._terminal.viewport.syncScrollArea();
1346+
this._terminal.buffers.activateAltBuffer(this._terminal.eraseAttr());
1347+
this._terminal.refresh(0, this._terminal.rows - 1);
1348+
if (this._terminal.viewport) {
1349+
this._terminal.viewport.syncScrollArea();
1350+
}
13441351
this._terminal.showCursor();
13451352
break;
13461353
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
@@ -1473,7 +1480,9 @@ export class InputHandler extends Disposable implements IInputHandler {
14731480
case 66:
14741481
this._terminal.log('Switching back to normal keypad.');
14751482
this._terminal.applicationKeypad = false;
1476-
this._terminal.viewport.syncScrollArea();
1483+
if (this._terminal.viewport) {
1484+
this._terminal.viewport.syncScrollArea();
1485+
}
14771486
break;
14781487
case 9: // X10 Mouse
14791488
case 1000: // vt200 mouse
@@ -1501,18 +1510,22 @@ export class InputHandler extends Disposable implements IInputHandler {
15011510
case 25: // hide cursor
15021511
this._terminal.cursorHidden = true;
15031512
break;
1513+
case 1048: // alt screen cursor
1514+
this.restoreCursor(params);
1515+
break;
15041516
case 1049: // alt screen buffer cursor
15051517
// FALL-THROUGH
15061518
case 47: // normal screen buffer
15071519
case 1047: // normal screen buffer - clearing it first
15081520
// Ensure the selection manager has the correct buffer
15091521
this._terminal.buffers.activateNormalBuffer();
1510-
// TODO: Not sure if we need to save/restore after switching the buffer
1511-
// if (params[0] === 1049) {
1512-
// this.restoreCursor(params);
1513-
// }
1522+
if (params[0] === 1049) {
1523+
this.restoreCursor(params);
1524+
}
15141525
this._terminal.refresh(0, this._terminal.rows - 1);
1515-
this._terminal.viewport.syncScrollArea();
1526+
if (this._terminal.viewport) {
1527+
this._terminal.viewport.syncScrollArea();
1528+
}
15161529
this._terminal.showCursor();
15171530
break;
15181531
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
@@ -1791,7 +1804,9 @@ export class InputHandler extends Disposable implements IInputHandler {
17911804
this._terminal.originMode = false;
17921805
this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false
17931806
this._terminal.applicationKeypad = false; // ?
1794-
this._terminal.viewport.syncScrollArea();
1807+
if (this._terminal.viewport) {
1808+
this._terminal.viewport.syncScrollArea();
1809+
}
17951810
this._terminal.applicationCursor = false;
17961811
this._terminal.buffer.scrollTop = 0;
17971812
this._terminal.buffer.scrollBottom = this._terminal.rows - 1;
@@ -1858,7 +1873,7 @@ export class InputHandler extends Disposable implements IInputHandler {
18581873
public saveCursor(params: number[]): void {
18591874
this._terminal.buffer.savedX = this._terminal.buffer.x;
18601875
this._terminal.buffer.savedY = this._terminal.buffer.y;
1861-
this._terminal.savedCurAttr = this._terminal.curAttr;
1876+
this._terminal.buffer.savedCurAttr = this._terminal.curAttr;
18621877
}
18631878

18641879

@@ -1870,7 +1885,7 @@ export class InputHandler extends Disposable implements IInputHandler {
18701885
public restoreCursor(params: number[]): void {
18711886
this._terminal.buffer.x = this._terminal.buffer.savedX || 0;
18721887
this._terminal.buffer.y = this._terminal.buffer.savedY || 0;
1873-
this._terminal.curAttr = this._terminal.savedCurAttr || DEFAULT_ATTR;
1888+
this._terminal.curAttr = this._terminal.buffer.savedCurAttr || DEFAULT_ATTR;
18741889
}
18751890

18761891

‎src/Terminal.ts

-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
170170
public savedCols: number;
171171

172172
public curAttr: number;
173-
public savedCurAttr: number;
174173

175174
public params: (string | number)[];
176175
public currentParam: string | number;

‎src/Types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export interface IInputHandlingTerminal extends IEventEmitter {
4949
wraparoundMode: boolean;
5050
bracketedPasteMode: boolean;
5151
curAttr: number;
52-
savedCurAttr: number;
5352
savedCols: number;
5453
x10Mouse: boolean;
5554
vt200Mouse: boolean;
@@ -290,6 +289,7 @@ export interface IBuffer {
290289
hasScrollback: boolean;
291290
savedY: number;
292291
savedX: number;
292+
savedCurAttr: number;
293293
isCursorInViewport: boolean;
294294
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;
295295
getWrappedRangeForLine(y: number): { first: number, last: number };
@@ -306,7 +306,7 @@ export interface IBufferSet extends IEventEmitter {
306306
active: IBuffer;
307307

308308
activateNormalBuffer(): void;
309-
activateAltBuffer(): void;
309+
activateAltBuffer(fillAttr?: number): void;
310310
}
311311

312312
export interface ISelectionManager {

‎src/utils/TestUtils.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
182182
wraparoundMode: boolean;
183183
bracketedPasteMode: boolean;
184184
curAttr: number;
185-
savedCurAttr: number;
186185
savedCols: number;
187186
x10Mouse: boolean;
188187
vt200Mouse: boolean;
@@ -304,6 +303,7 @@ export class MockBuffer implements IBuffer {
304303
scrollTop: number;
305304
savedY: number;
306305
savedX: number;
306+
savedCurAttr: number;
307307
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
308308
return Buffer.prototype.translateBufferLineToString.apply(this, arguments);
309309
}

0 commit comments

Comments
 (0)
Please sign in to comment.