restrict cursor movements

This commit is contained in:
Jörg Breitbart
2019-07-06 15:59:18 +02:00
parent d18b799624
commit eb7b8f9020
2 changed files with 387 additions and 74 deletions
+339
View File
@@ -546,4 +546,343 @@ describe('InputHandler', () => {
assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [5, 0, 0]);
});
});
describe('cursor positioning', () => {
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal({cols: 10, rows: 10});
});
function getCursor(term: TestTerminal): number[] {
return [
term.buffer.x,
term.buffer.y
];
}
it('cursor forward (CUF)', () => {
term.writeSync('\x1b[C');
assert.deepEqual(getCursor(term), [1, 0]);
term.writeSync('\x1b[1C');
assert.deepEqual(getCursor(term), [2, 0]);
term.writeSync('\x1b[4C');
assert.deepEqual(getCursor(term), [6, 0]);
term.writeSync('\x1b[100C');
assert.deepEqual(getCursor(term), [9, 0]);
// should not change y
term.buffer.x = 8;
term.buffer.y = 4;
term.writeSync('\x1b[C');
assert.deepEqual(getCursor(term), [9, 4]);
});
it('cursor backward (CUB)', () => {
term.writeSync('\x1b[D');
assert.deepEqual(getCursor(term), [0, 0]);
term.writeSync('\x1b[1D');
assert.deepEqual(getCursor(term), [0, 0]);
// place cursor at end of first line
term.writeSync('\x1b[100C');
term.writeSync('\x1b[D');
assert.deepEqual(getCursor(term), [8, 0]);
term.writeSync('\x1b[1D');
assert.deepEqual(getCursor(term), [7, 0]);
term.writeSync('\x1b[4D');
assert.deepEqual(getCursor(term), [3, 0]);
term.writeSync('\x1b[100D');
assert.deepEqual(getCursor(term), [0, 0]);
// should not change y
term.buffer.x = 4;
term.buffer.y = 4;
term.writeSync('\x1b[D');
assert.deepEqual(getCursor(term), [3, 4]);
});
it('cursor down (CUD)', () => {
term.writeSync('\x1b[B');
assert.deepEqual(getCursor(term), [0, 1]);
term.writeSync('\x1b[1B');
assert.deepEqual(getCursor(term), [0, 2]);
term.writeSync('\x1b[4B');
assert.deepEqual(getCursor(term), [0, 6]);
term.writeSync('\x1b[100B');
assert.deepEqual(getCursor(term), [0, 9]);
// should not change x
term.buffer.x = 8;
term.buffer.y = 0;
term.writeSync('\x1b[B');
assert.deepEqual(getCursor(term), [8, 1]);
});
it('cursor up (CUU)', () => {
term.writeSync('\x1b[A');
assert.deepEqual(getCursor(term), [0, 0]);
term.writeSync('\x1b[1A');
assert.deepEqual(getCursor(term), [0, 0]);
// place cursor at beginning of last row
term.writeSync('\x1b[100B');
term.writeSync('\x1b[A');
assert.deepEqual(getCursor(term), [0, 8]);
term.writeSync('\x1b[1A');
assert.deepEqual(getCursor(term), [0, 7]);
term.writeSync('\x1b[4A');
assert.deepEqual(getCursor(term), [0, 3]);
term.writeSync('\x1b[100A');
assert.deepEqual(getCursor(term), [0, 0]);
// should not change x
term.buffer.x = 8;
term.buffer.y = 9;
term.writeSync('\x1b[A');
assert.deepEqual(getCursor(term), [8, 8]);
});
it('cursor next line (CNL)', () => {
term.writeSync('\x1b[E');
assert.deepEqual(getCursor(term), [0, 1]);
term.writeSync('\x1b[1E');
assert.deepEqual(getCursor(term), [0, 2]);
term.writeSync('\x1b[4E');
assert.deepEqual(getCursor(term), [0, 6]);
term.writeSync('\x1b[100E');
assert.deepEqual(getCursor(term), [0, 9]);
// should reset x to zero
term.buffer.x = 8;
term.buffer.y = 0;
term.writeSync('\x1b[E');
assert.deepEqual(getCursor(term), [0, 1]);
});
it('cursor previous line (CPL)', () => {
term.writeSync('\x1b[F');
assert.deepEqual(getCursor(term), [0, 0]);
term.writeSync('\x1b[1F');
assert.deepEqual(getCursor(term), [0, 0]);
// place cursor at beginning of last row
term.writeSync('\x1b[100E');
term.writeSync('\x1b[F');
assert.deepEqual(getCursor(term), [0, 8]);
term.writeSync('\x1b[1F');
assert.deepEqual(getCursor(term), [0, 7]);
term.writeSync('\x1b[4F');
assert.deepEqual(getCursor(term), [0, 3]);
term.writeSync('\x1b[100F');
assert.deepEqual(getCursor(term), [0, 0]);
// should reset x to zero
term.buffer.x = 8;
term.buffer.y = 9;
term.writeSync('\x1b[F');
assert.deepEqual(getCursor(term), [0, 8]);
});
it('cursor character absolute (CHA)', () => {
term.writeSync('\x1b[G');
assert.deepEqual(getCursor(term), [0, 0]);
term.writeSync('\x1b[1G');
assert.deepEqual(getCursor(term), [0, 0]);
term.writeSync('\x1b[2G');
assert.deepEqual(getCursor(term), [1, 0]);
term.writeSync('\x1b[5G');
assert.deepEqual(getCursor(term), [4, 0]);
term.writeSync('\x1b[100G');
assert.deepEqual(getCursor(term), [9, 0]);
});
it('cursor position (CUP)', () => {
term.buffer.x = 5;
term.buffer.y = 5;
term.writeSync('\x1b[H');
assert.deepEqual(getCursor(term), [0, 0]);
term.buffer.x = 5;
term.buffer.y = 5;
term.writeSync('\x1b[1H');
assert.deepEqual(getCursor(term), [0, 0]);
term.buffer.x = 5;
term.buffer.y = 5;
term.writeSync('\x1b[1;1H');
assert.deepEqual(getCursor(term), [0, 0]);
term.buffer.x = 5;
term.buffer.y = 5;
term.writeSync('\x1b[8H');
assert.deepEqual(getCursor(term), [0, 7]);
term.buffer.x = 5;
term.buffer.y = 5;
term.writeSync('\x1b[;8H');
assert.deepEqual(getCursor(term), [7, 0]);
term.buffer.x = 5;
term.buffer.y = 5;
term.writeSync('\x1b[100;100H');
assert.deepEqual(getCursor(term), [9, 9]);
});
it('horizontal position absolute (HPA)', () => {
term.writeSync('\x1b[`');
assert.deepEqual(getCursor(term), [0, 0]);
term.writeSync('\x1b[1`');
assert.deepEqual(getCursor(term), [0, 0]);
term.writeSync('\x1b[2`');
assert.deepEqual(getCursor(term), [1, 0]);
term.writeSync('\x1b[5`');
assert.deepEqual(getCursor(term), [4, 0]);
term.writeSync('\x1b[100`');
assert.deepEqual(getCursor(term), [9, 0]);
});
it('horizontal position relative (HPR)', () => {
term.writeSync('\x1b[a');
assert.deepEqual(getCursor(term), [1, 0]);
term.writeSync('\x1b[1a');
assert.deepEqual(getCursor(term), [2, 0]);
term.writeSync('\x1b[4a');
assert.deepEqual(getCursor(term), [6, 0]);
term.writeSync('\x1b[100a');
assert.deepEqual(getCursor(term), [9, 0]);
// should not change y
term.buffer.x = 8;
term.buffer.y = 4;
term.writeSync('\x1b[a');
assert.deepEqual(getCursor(term), [9, 4]);
});
it('vertical position absolute (VPA)', () => {
term.writeSync('\x1b[d');
assert.deepEqual(getCursor(term), [0, 0]);
term.writeSync('\x1b[1d');
assert.deepEqual(getCursor(term), [0, 0]);
term.writeSync('\x1b[2d');
assert.deepEqual(getCursor(term), [0, 1]);
term.writeSync('\x1b[5d');
assert.deepEqual(getCursor(term), [0, 4]);
term.writeSync('\x1b[100d');
assert.deepEqual(getCursor(term), [0, 9]);
// should not change x
term.buffer.x = 8;
term.buffer.y = 4;
term.writeSync('\x1b[d');
assert.deepEqual(getCursor(term), [8, 0]);
});
it('vertical position relative (VPR)', () => {
term.writeSync('\x1b[e');
assert.deepEqual(getCursor(term), [0, 1]);
term.writeSync('\x1b[1e');
assert.deepEqual(getCursor(term), [0, 2]);
term.writeSync('\x1b[4e');
assert.deepEqual(getCursor(term), [0, 6]);
term.writeSync('\x1b[100e');
assert.deepEqual(getCursor(term), [0, 9]);
// should not change x
term.buffer.x = 8;
term.buffer.y = 4;
term.writeSync('\x1b[e');
assert.deepEqual(getCursor(term), [8, 5]);
});
describe('should clamp cursor into addressible range', () => {
it('CUF', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[C');
assert.deepEqual(getCursor(term), [9, 9]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[C');
assert.deepEqual(getCursor(term), [1, 0]);
});
it('CUB', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[D');
assert.deepEqual(getCursor(term), [8, 9]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[D');
assert.deepEqual(getCursor(term), [0, 0]);
});
it('CUD', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[B');
assert.deepEqual(getCursor(term), [9, 9]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[B');
assert.deepEqual(getCursor(term), [0, 1]);
});
it('CUU', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[A');
assert.deepEqual(getCursor(term), [9, 8]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[A');
assert.deepEqual(getCursor(term), [0, 0]);
});
it('CNL', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[E');
assert.deepEqual(getCursor(term), [0, 9]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[E');
assert.deepEqual(getCursor(term), [0, 1]);
});
it('CPL', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[F');
assert.deepEqual(getCursor(term), [0, 8]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[F');
assert.deepEqual(getCursor(term), [0, 0]);
});
it('CHA', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[5G');
assert.deepEqual(getCursor(term), [4, 9]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[5G');
assert.deepEqual(getCursor(term), [4, 0]);
});
it('CUP', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[5;5H');
assert.deepEqual(getCursor(term), [4, 4]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[5;5H');
assert.deepEqual(getCursor(term), [4, 4]);
});
it('HPA', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[5`');
assert.deepEqual(getCursor(term), [4, 9]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[5`');
assert.deepEqual(getCursor(term), [4, 0]);
});
it('HPR', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[a');
assert.deepEqual(getCursor(term), [9, 9]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[a');
assert.deepEqual(getCursor(term), [1, 0]);
});
it('VPA', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[5d');
assert.deepEqual(getCursor(term), [9, 4]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[5d');
assert.deepEqual(getCursor(term), [0, 4]);
});
it('VPR', () => {
term.buffer.x = 10000;
term.buffer.y = 10000;
term.writeSync('\x1b[e');
assert.deepEqual(getCursor(term), [9, 9]);
term.buffer.x = -10000;
term.buffer.y = -10000;
term.writeSync('\x1b[e');
assert.deepEqual(getCursor(term), [0, 1]);
});
});
});
});
+48 -74
View File
@@ -598,6 +598,22 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.updateRange(this._terminal.buffer.y);
}
// restrict cursor changes to addressible cols/rows
private _restrictCursor(): void {
// cols
if (this._terminal.buffer.x < 0) {
this._terminal.buffer.x = 0;
} else if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x = this._terminal.cols - 1;
}
// rows
if (this._terminal.buffer.y < 0) {
this._terminal.buffer.y = 0;
} else if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
}
/**
* CSI Ps A
* Cursor Up Ps Times (default = 1) (CUU).
@@ -607,10 +623,9 @@ export class InputHandler extends Disposable implements IInputHandler {
if (param < 1) {
param = 1;
}
this._restrictCursor();
this._terminal.buffer.y -= param;
if (this._terminal.buffer.y < 0) {
this._terminal.buffer.y = 0;
}
this._restrictCursor();
}
/**
@@ -622,14 +637,9 @@ export class InputHandler extends Disposable implements IInputHandler {
if (param < 1) {
param = 1;
}
this._restrictCursor();
this._terminal.buffer.y += param;
if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x--;
}
this._restrictCursor();
}
/**
@@ -641,10 +651,9 @@ export class InputHandler extends Disposable implements IInputHandler {
if (param < 1) {
param = 1;
}
this._restrictCursor();
this._terminal.buffer.x += param;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x = this._terminal.cols - 1;
}
this._restrictCursor();
}
/**
@@ -656,49 +665,42 @@ export class InputHandler extends Disposable implements IInputHandler {
if (param < 1) {
param = 1;
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x--;
}
this._restrictCursor();
this._terminal.buffer.x -= param;
if (this._terminal.buffer.x < 0) {
this._terminal.buffer.x = 0;
}
this._restrictCursor();
}
/**
* CSI Ps E
* Cursor Next Line Ps Times (default = 1) (CNL).
* same as CSI Ps B ?
* Other than cursorDown (CUD) also set the cursor to first column.
*/
public cursorNextLine(params: number[]): void {
let param = params[0];
if (param < 1) {
param = 1;
}
this._restrictCursor();
this._terminal.buffer.y += param;
if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
this._terminal.buffer.x = 0;
this._restrictCursor();
}
/**
* CSI Ps F
* Cursor Preceding Line Ps Times (default = 1) (CNL).
* reuse CSI Ps A ?
* Cursor Previous Line Ps Times (default = 1) (CPL).
* Other than cursorUp (CUU) also set the cursor to first column.
*/
public cursorPrecedingLine(params: number[]): void {
let param = params[0];
if (param < 1) {
param = 1;
}
this._restrictCursor();
this._terminal.buffer.y -= param;
if (this._terminal.buffer.y < 0) {
this._terminal.buffer.y = 0;
}
this._terminal.buffer.x = 0;
this._restrictCursor();
}
@@ -711,7 +713,9 @@ export class InputHandler extends Disposable implements IInputHandler {
if (param < 1) {
param = 1;
}
this._restrictCursor();
this._terminal.buffer.x = param - 1;
this._restrictCursor();
}
/**
@@ -720,7 +724,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public cursorPosition(params: number[]): void {
let col: number;
let row: number = params[0] - 1;
const row: number = params[0] - 1;
if (params.length >= 2) {
col = params[1] - 1;
@@ -728,20 +732,10 @@ export class InputHandler extends Disposable implements IInputHandler {
col = 0;
}
if (row < 0) {
row = 0;
} else if (row >= this._terminal.rows) {
row = this._terminal.rows - 1;
}
if (col < 0) {
col = 0;
} else if (col >= this._terminal.cols) {
col = this._terminal.cols - 1;
}
this._restrictCursor();
this._terminal.buffer.x = col;
this._terminal.buffer.y = row;
this._restrictCursor();
}
/**
@@ -1017,32 +1011,31 @@ export class InputHandler extends Disposable implements IInputHandler {
/**
* CSI Pm ` Character Position Absolute
* [column] (default = [row,1]) (HPA).
* Currently same functionality as CHA.
*/
public charPosAbsolute(params: number[]): void {
let param = params[0];
if (param < 1) {
param = 1;
}
this._restrictCursor();
this._terminal.buffer.x = param - 1;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x = this._terminal.cols - 1;
}
this._restrictCursor();
}
/**
* CSI Pm a Character Position Relative
* [columns] (default = [row,col+1]) (HPR)
* reuse CSI Ps C ?
* Currently same functionality as CUF.
*/
public hPositionRelative(params: number[]): void {
let param = params[0];
if (param < 1) {
param = 1;
}
this._restrictCursor();
this._terminal.buffer.x += param;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x = this._terminal.cols - 1;
}
this._restrictCursor();
}
/**
@@ -1155,10 +1148,9 @@ export class InputHandler extends Disposable implements IInputHandler {
if (param < 1) {
param = 1;
}
this._restrictCursor();
this._terminal.buffer.y = param - 1;
if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
this._restrictCursor();
}
/**
@@ -1171,37 +1163,19 @@ export class InputHandler extends Disposable implements IInputHandler {
if (param < 1) {
param = 1;
}
this._restrictCursor();
this._terminal.buffer.y += param;
if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x--;
}
this._restrictCursor();
}
/**
* CSI Ps ; Ps f
* Horizontal and Vertical Position [row;column] (default =
* [1,1]) (HVP).
* Same as CUP.
*/
public hVPosition(params: number[]): void {
if (params.length < 2) {
params.push(1);
}
if (params[0] < 1) params[0] = 1;
if (params[1] < 1) params[1] = 1;
this._terminal.buffer.y = params[0] - 1;
if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
this._terminal.buffer.x = params[1] - 1;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x = this._terminal.cols - 1;
}
this.cursorPosition(params);
}
/**