fix behavior, first BS tests

This commit is contained in:
Jörg Breitbart
2020-02-17 12:22:53 +01:00
parent f736b6c299
commit 0c703276b1
5 changed files with 107 additions and 8 deletions
+57
View File
@@ -1445,4 +1445,61 @@ describe('InputHandler', () => {
assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']);
});
});
describe('BS with reverseWraparound set/unset', () => {
const ttyBS = '\x08 \x08'; // tty ICANON sends <BS SP BS> on pressing BS
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal({cols: 5, rows: 5, scrollback: 1});
});
describe('reverseWraparound unset (default)', () => {
it('cannot delete last cell', () => {
term.writeSync('12345');
term.writeSync(ttyBS);
assert.deepEqual(getLines(term, 1), ['123 5']);
term.writeSync(ttyBS.repeat(10));
assert.deepEqual(getLines(term, 1), [' 5']);
});
it('cannot access prev line', () => {
term.writeSync('12345'.repeat(2));
term.writeSync(ttyBS);
assert.deepEqual(getLines(term, 2), ['12345', '123 5']);
term.writeSync(ttyBS.repeat(10));
assert.deepEqual(getLines(term, 2), ['12345', ' 5']);
});
});
describe('reverseWraparound set', () => {
it('can delete last cell', () => {
term.writeSync('\x1b[?45h');
term.writeSync('12345');
term.writeSync(ttyBS);
assert.deepEqual(getLines(term, 1), ['1234 ']);
term.writeSync(ttyBS.repeat(7));
assert.deepEqual(getLines(term, 1), [' ']);
});
it('can access prev line if wrapped', () => {
term.writeSync('\x1b[?45h');
term.writeSync('12345'.repeat(2));
term.writeSync(ttyBS);
assert.deepEqual(getLines(term, 2), ['12345', '1234 ']);
term.writeSync(ttyBS.repeat(7));
assert.deepEqual(getLines(term, 2), ['12 ', ' ']);
});
it('should lift isWrapped', () => {
term.writeSync('\x1b[?45h');
term.writeSync('12345'.repeat(2));
assert.equal(term.buffer.lines.get(1).isWrapped, true);
term.writeSync(ttyBS.repeat(7));
assert.equal(term.buffer.lines.get(1).isWrapped, false);
});
it('stops at hard NLs', () => {
term.writeSync('\x1b[?45h');
term.writeSync('12345\r\n');
term.writeSync('12345'.repeat(2));
term.writeSync(ttyBS.repeat(50));
assert.deepEqual(getLines(term, 3), ['12345', ' ', ' ']);
assert.equal(term.buffer.x, 0);
assert.equal(term.buffer.y, 1);
});
});
});
});
+47 -8
View File
@@ -544,6 +544,10 @@ export class InputHandler extends Disposable implements IInputHandler {
// autowrap - DECAWM
// automatically wraps to the beginning of the next line
if (wraparoundMode) {
// clear left over cells to the right
while (buffer.x < cols) {
bufferRow.setCellFromCodePoint(buffer.x++, 0, 1, curAttr.fg, curAttr.bg);
}
buffer.x = 0;
buffer.y++;
if (buffer.y === buffer.scrollBottom + 1) {
@@ -717,21 +721,48 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y C0 BS "Backspace" "\b, \x08" "Move the cursor one position to the left."
*/
public backspace(): void {
//this._restrictCursor();
// xterm allows deleting of the last col if reverseWraparound is set
// otherwise the last cell cannot be deleted by BS
if (!this._coreService.decPrivateModes.reverseWraparound) {
this._restrictCursor();
} else {
// we still clamp the cursor to sane values here, but have to allow x=cols as well
this._bufferService.buffer.x = Math.min(this._bufferService.cols, Math.max(0, this._bufferService.buffer.x));
this._bufferService.buffer.y = this._coreService.decPrivateModes.origin
? Math.min(this._bufferService.buffer.scrollBottom, Math.max(this._bufferService.buffer.scrollTop, this._bufferService.buffer.y))
: Math.min(this._bufferService.rows - 1, Math.max(0, this._bufferService.buffer.y));
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
}
const buffer = this._bufferService.buffer;
if (buffer.x > 0) {
buffer.x--;
} else {
// reverse wraparound
if (buffer.x === 0 && buffer.y && buffer.lines.get(buffer.y + buffer.ybase).isWrapped) {
buffer.y--;
buffer.x = this._bufferService.cols - 1;
// TODO: skip last col if it is empty after wide char
// TODO: test in xterm - should we peek/unshift from scrollback if y == 0?
if (this._coreService.decPrivateModes.reverseWraparound
&& buffer.x === 0
&& buffer.y > 0
&& buffer.lines.get(buffer.y + buffer.ybase).isWrapped)
{
// unset isWrapped
buffer.lines.get(buffer.y + buffer.ybase).isWrapped = false;
buffer.y--;
buffer.x = this._bufferService.cols - 1;
// find last taken cell - last cell can have 3 different states:
// - hasContent(true) + hasWidth(1): narrow char - we are done
// - hasWidth(0): second part of wide char - we are done
// - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one cell further back
const line = buffer.lines.get(buffer.y + buffer.ybase);
if (line.hasWidth(buffer.x) && !line.hasContent(buffer.x)) {
buffer.x--;
// We do this only once, since width=1 + hasContent=false currently happens only once before
// early wrapping of a wide char.
// This needs to be fixed once we support graphemes taking more than 2 cells.
}
}
}
if (buffer.x >= this._bufferService.cols) {
buffer.x = this._bufferService.cols - 1;
}
// should we use _restrictCursor() here? FIXME: how about origin mode / scroll margins?
buffer.x = Math.max(Math.min(buffer.x, this._bufferService.cols - 1), 0);
}
/**
@@ -1710,6 +1741,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 9 | X10 xterm mouse protocol. | #Y |
* | 12 | Start Blinking Cursor. | #Y |
* | 25 | Show Cursor (DECTCEM). | #Y |
* | 45 | Reverse wrap-around. | #Y |
* | 47 | Use Alternate Screen Buffer. | #Y |
* | 66 | Application keypad (DECNKM). | #Y |
* | 1000 | X11 xterm mouse protocol. | #Y |
@@ -1761,6 +1793,9 @@ export class InputHandler extends Disposable implements IInputHandler {
case 12:
// this.cursorBlink = true;
break;
case 45:
this._coreService.decPrivateModes.reverseWraparound = true;
break;
case 66:
this._logService.debug('Serial port requested application keypad.');
this._coreService.decPrivateModes.applicationKeypad = true;
@@ -1944,6 +1979,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 9 | Don't send Mouse X & Y on button press. | #Y |
* | 12 | Stop Blinking Cursor. | #Y |
* | 25 | Hide Cursor (DECTCEM). | #Y |
* | 45 | No reverse wrap-around. | #Y |
* | 47 | Use Normal Screen Buffer. | #Y |
* | 66 | Numeric keypad (DECNKM). | #Y |
* | 1000 | Don't send Mouse reports. | #Y |
@@ -1988,6 +2024,9 @@ export class InputHandler extends Disposable implements IInputHandler {
case 12:
// this.cursorBlink = false;
break;
case 45:
this._coreService.decPrivateModes.reverseWraparound = false;
break;
case 66:
this._logService.debug('Switching back to normal keypad.');
this._coreService.decPrivateModes.applicationKeypad = false;
+1
View File
@@ -62,6 +62,7 @@ export class MockCoreService implements ICoreService {
applicationCursorKeys: false,
applicationKeypad: false,
origin: false,
reverseWraparound: false,
wraparound: true
};
onData: IEvent<string> = new EventEmitter<string>().event;
+1
View File
@@ -155,6 +155,7 @@ export interface IDecPrivateModes {
applicationCursorKeys: boolean;
applicationKeypad: boolean;
origin: boolean;
reverseWraparound: boolean;
wraparound: boolean; // defaults: xterm - true, vt100 - false
}
+1
View File
@@ -12,6 +12,7 @@ const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({
applicationCursorKeys: false,
applicationKeypad: false,
origin: false,
reverseWraparound: false,
wraparound: true // defaults: xterm - true, vt100 - false
});