Merge pull request #2288 from jerch/cleanup_sequences_files
fix edge cases in inputhandler methods
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
from glob import glob
|
||||
import os
|
||||
import sys
|
||||
import termios
|
||||
import atexit
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def enable_echo(fd, enabled):
|
||||
(iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(fd)
|
||||
if enabled:
|
||||
lflag |= termios.ECHO
|
||||
else:
|
||||
lflag &= ~termios.ECHO
|
||||
new_attr = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]
|
||||
termios.tcsetattr(fd, termios.TCSANOW, new_attr)
|
||||
|
||||
atexit.register(enable_echo, sys.stdin.fileno(), True)
|
||||
|
||||
output = []
|
||||
|
||||
|
||||
def log(append=False, *s):
|
||||
if append:
|
||||
output[-1] += ' ' + ' '.join(str(part) for part in s)
|
||||
else:
|
||||
output.append(' '.join(str(part) for part in s))
|
||||
|
||||
|
||||
def reset_terminal():
|
||||
sys.stdout.write('\x1bc\x1b[H')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def test():
|
||||
count = 0
|
||||
passed = 0
|
||||
for i, testfile in enumerate(sorted(glob(os.path.join(BASE_DIR, '*.in')))):
|
||||
count += 1
|
||||
log(False, os.path.basename(testfile))
|
||||
reset_terminal()
|
||||
with open(testfile) as test:
|
||||
sys.stdout.write('\x1b]0;%s\x07' % os.path.basename(testfile))
|
||||
sys.stdout.write(test.read()+'\x1bt')
|
||||
sys.stdout.flush()
|
||||
with open(os.path.join(os.path.dirname(testfile),
|
||||
os.path.basename(testfile).split('.')[0]+'.text')) as expected:
|
||||
terminal_output = sys.stdin.read()
|
||||
if not terminal_output:
|
||||
# we are in xterm
|
||||
continue
|
||||
if terminal_output != expected.read():
|
||||
log(True, '\x1b[31merror\x1b[0m')
|
||||
with open(os.path.join(os.path.dirname(testfile), 'output',
|
||||
os.path.basename(testfile)), 'w') as t_out:
|
||||
t_out.write(terminal_output)
|
||||
else:
|
||||
passed += 1
|
||||
log(True, '\x1b[32mpass\x1b[0m')
|
||||
return count, passed
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
enable_echo(sys.stdin.fileno(), False)
|
||||
count, passed = test()
|
||||
enable_echo(sys.stdin.fileno(), True)
|
||||
reset_terminal()
|
||||
for i in range(len(output)/2+1):
|
||||
if not (i+1) % 25:
|
||||
sys.stdin.read()
|
||||
print ''.join(i.ljust(40) for i in output[i*2:i*2+2])
|
||||
print '\x1b[33mcoverage: %s/%s (%d%%) tests passed.\x1b[0m' % (passed, count, passed*100/count)
|
||||
@@ -1,24 +1,25 @@
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
f
|
||||
g
|
||||
h
|
||||
i
|
||||
b
|
||||
c
|
||||
d
|
||||
f
|
||||
g
|
||||
h
|
||||
i
|
||||
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
|
||||
|
||||
|
||||
n
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
s
|
||||
w
|
||||
x
|
||||
n
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
s
|
||||
w
|
||||
x
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
6 C
|
||||
8 ^^^^
|
||||
9 vvvv DL on line 11, expected: ACD_
|
||||
10 A
|
||||
@@ -12,14 +13,14 @@
|
||||
19 vvvv IL on line 21, expected: A_
|
||||
20 A
|
||||
|
||||
|
||||
22 ^^^^
|
||||
|
||||
23 vvvv IL on line 24, expected: _A
|
||||
24 A
|
||||
25 B
|
||||
26 ^^^^
|
||||
28 A
|
||||
27 vvvv DL on line 28, expected: B_
|
||||
|
||||
28 A
|
||||
29 B
|
||||
30 ^^^^
|
||||
31
|
||||
32
|
||||
32
|
||||
@@ -1,25 +1,25 @@
|
||||
n
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
s
|
||||
t
|
||||
u
|
||||
v
|
||||
w
|
||||
x
|
||||
y
|
||||
z
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
s
|
||||
t
|
||||
u
|
||||
v
|
||||
w
|
||||
x
|
||||
y
|
||||
z
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
|
||||
11
|
||||
|
||||
11
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
efgh
|
||||
-------- set: wraparound ----------------------------------------------abcd
|
||||
efgh
|
||||
-------- unset: no wraparound -------------------------------------------abcd
|
||||
-------- unset: no wraparound -------------------------------------------abch
|
||||
this should be immediately below "no wraparound"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
Test of autowrap, mixing control and print characters.
|
||||
|
||||
|
||||
The left/right margins should have letters in order:
|
||||
|
||||
|
||||
[3;21r[?6h[19;1HA[19;80Ha
|
||||
[18;80HaB[19;80HB b
|
||||
[19;80HC c[19;2HC
|
||||
[19;80H
|
||||
[18;1HD[18;80Hd[19;1HE[19;80He
|
||||
[18;80HeF[19;80HF f
|
||||
[19;80HG g[19;2HG
|
||||
[19;80H
|
||||
[18;1HH[18;80Hh[19;1HI[19;80Hi
|
||||
[18;80HiJ[19;80HJ j
|
||||
[19;80HK k[19;2HK
|
||||
[19;80H
|
||||
[18;1HL[18;80Hl[19;1HM[19;80Hm
|
||||
[18;80HmN[19;80HN n
|
||||
[19;80HO o[19;2HO
|
||||
[19;80H
|
||||
[18;1HP[18;80Hp[19;1HQ[19;80Hq
|
||||
[18;80HqR[19;80HR r
|
||||
[19;80HS s[19;2HS
|
||||
[19;80H
|
||||
[18;1HT[18;80Ht[19;1HU[19;80Hu
|
||||
[18;80HuV[19;80HV v
|
||||
[19;80HW w[19;2HW
|
||||
[19;80H
|
||||
[18;1HX[18;80Hx[19;1HY[19;80Hy
|
||||
[18;80HyZ[19;80HZ z
|
||||
[?6l[r[22;1HPush <RETURN>
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Test of autowrap, mixing control and print characters.
|
||||
|
||||
I i
|
||||
J j
|
||||
K k
|
||||
L l
|
||||
M m
|
||||
N n
|
||||
O o
|
||||
P p
|
||||
Q q
|
||||
R r
|
||||
S s
|
||||
T t
|
||||
U u
|
||||
V v
|
||||
W w
|
||||
X x
|
||||
Y y
|
||||
Z z
|
||||
|
||||
Push <RETURN>
|
||||
|
||||
|
||||
|
||||
@@ -15,9 +15,18 @@ import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { Params } from 'common/parser/Params';
|
||||
import { MockCoreService } from 'common/TestUtils.test';
|
||||
|
||||
function getCursor(term: TestTerminal): number[] {
|
||||
return [
|
||||
term.buffer.x,
|
||||
term.buffer.y
|
||||
];
|
||||
}
|
||||
|
||||
describe('InputHandler', () => {
|
||||
describe('save and restore cursor', () => {
|
||||
const terminal = new MockInputHandlingTerminal();
|
||||
terminal.cols = 80;
|
||||
terminal.rows = 30;
|
||||
terminal.buffer.x = 1;
|
||||
terminal.buffer.y = 2;
|
||||
terminal.buffer.ybase = 0;
|
||||
@@ -697,4 +706,483 @@ describe('InputHandler', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('cursor positioning', () => {
|
||||
let term: TestTerminal;
|
||||
beforeEach(() => {
|
||||
term = new TestTerminal({cols: 10, rows: 10});
|
||||
});
|
||||
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]);
|
||||
});
|
||||
it('DCH', () => {
|
||||
term.buffer.x = 10000;
|
||||
term.buffer.y = 10000;
|
||||
term.writeSync('\x1b[P');
|
||||
assert.deepEqual(getCursor(term), [9, 9]);
|
||||
term.buffer.x = -10000;
|
||||
term.buffer.y = -10000;
|
||||
term.writeSync('\x1b[P');
|
||||
assert.deepEqual(getCursor(term), [0, 0]);
|
||||
});
|
||||
it('DCH - should delete last cell', () => {
|
||||
term.writeSync('0123456789\x1b[P');
|
||||
assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 ');
|
||||
});
|
||||
it('ECH', () => {
|
||||
term.buffer.x = 10000;
|
||||
term.buffer.y = 10000;
|
||||
term.writeSync('\x1b[X');
|
||||
assert.deepEqual(getCursor(term), [9, 9]);
|
||||
term.buffer.x = -10000;
|
||||
term.buffer.y = -10000;
|
||||
term.writeSync('\x1b[X');
|
||||
assert.deepEqual(getCursor(term), [0, 0]);
|
||||
});
|
||||
it('ECH - should delete last cell', () => {
|
||||
term.writeSync('0123456789\x1b[X');
|
||||
assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 ');
|
||||
});
|
||||
it('ICH', () => {
|
||||
term.buffer.x = 10000;
|
||||
term.buffer.y = 10000;
|
||||
term.writeSync('\x1b[@');
|
||||
assert.deepEqual(getCursor(term), [9, 9]);
|
||||
term.buffer.x = -10000;
|
||||
term.buffer.y = -10000;
|
||||
term.writeSync('\x1b[@');
|
||||
assert.deepEqual(getCursor(term), [0, 0]);
|
||||
});
|
||||
it('ICH - should delete last cell', () => {
|
||||
term.writeSync('0123456789\x1b[@');
|
||||
assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 ');
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('DECSTBM - scroll margins', () => {
|
||||
let term: TestTerminal;
|
||||
beforeEach(() => {
|
||||
term = new TestTerminal({cols: 10, rows: 10});
|
||||
});
|
||||
it('should default to whole viewport', () => {
|
||||
term.writeSync('\x1b[r');
|
||||
assert.equal(term.buffer.scrollTop, 0);
|
||||
assert.equal(term.buffer.scrollBottom, 9);
|
||||
term.writeSync('\x1b[3;7r');
|
||||
assert.equal(term.buffer.scrollTop, 2);
|
||||
assert.equal(term.buffer.scrollBottom, 6);
|
||||
term.writeSync('\x1b[0;0r');
|
||||
assert.equal(term.buffer.scrollTop, 0);
|
||||
assert.equal(term.buffer.scrollBottom, 9);
|
||||
});
|
||||
it('should clamp bottom', () => {
|
||||
term.writeSync('\x1b[3;1000r');
|
||||
assert.equal(term.buffer.scrollTop, 2);
|
||||
assert.equal(term.buffer.scrollBottom, 9);
|
||||
});
|
||||
it('should only apply for top < bottom', () => {
|
||||
term.writeSync('\x1b[7;2r');
|
||||
assert.equal(term.buffer.scrollTop, 0);
|
||||
assert.equal(term.buffer.scrollBottom, 9);
|
||||
});
|
||||
it('should home cursor', () => {
|
||||
term.buffer.x = 10000;
|
||||
term.buffer.y = 10000;
|
||||
term.writeSync('\x1b[2;7r');
|
||||
assert.deepEqual(getCursor(term), [0, 0]);
|
||||
});
|
||||
});
|
||||
describe('scroll margins', () => {
|
||||
let term: TestTerminal;
|
||||
beforeEach(() => {
|
||||
term = new TestTerminal({cols: 10, rows: 10});
|
||||
});
|
||||
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
|
||||
const res: string[] = [];
|
||||
for (let i = 0; i < limit; ++i) {
|
||||
res.push(term.buffer.lines.get(i).translateToString(true));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
it('scrollUp', () => {
|
||||
term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm');
|
||||
assert.deepEqual(getLines(term), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']);
|
||||
});
|
||||
it('scrollDown', () => {
|
||||
term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Tm');
|
||||
assert.deepEqual(getLines(term), ['m', '', '', '1', '4', '5', '6', '7', '8', '9']);
|
||||
});
|
||||
it('insertLines - out of margins', () => {
|
||||
term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r');
|
||||
assert.equal(term.buffer.scrollTop, 2);
|
||||
assert.equal(term.buffer.scrollBottom, 5);
|
||||
term.writeSync('\x1b[2Lm');
|
||||
assert.deepEqual(getLines(term), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
|
||||
term.writeSync('\x1b[2H\x1b[2Ln');
|
||||
assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']);
|
||||
// skip below scrollbottom
|
||||
term.writeSync('\x1b[7H\x1b[2Lo');
|
||||
assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']);
|
||||
term.writeSync('\x1b[8H\x1b[2Lp');
|
||||
assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']);
|
||||
term.writeSync('\x1b[100H\x1b[2Lq');
|
||||
assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']);
|
||||
});
|
||||
it('insertLines - within margins', () => {
|
||||
term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r');
|
||||
assert.equal(term.buffer.scrollTop, 2);
|
||||
assert.equal(term.buffer.scrollBottom, 5);
|
||||
term.writeSync('\x1b[3H\x1b[2Lm');
|
||||
assert.deepEqual(getLines(term), ['0', '1', 'm', '', '2', '3', '6', '7', '8', '9']);
|
||||
term.writeSync('\x1b[6H\x1b[2Ln');
|
||||
assert.deepEqual(getLines(term), ['0', '1', 'm', '', '2', 'n', '6', '7', '8', '9']);
|
||||
});
|
||||
it('deleteLines - out of margins', () => {
|
||||
term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r');
|
||||
assert.equal(term.buffer.scrollTop, 2);
|
||||
assert.equal(term.buffer.scrollBottom, 5);
|
||||
term.writeSync('\x1b[2Mm');
|
||||
assert.deepEqual(getLines(term), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
|
||||
term.writeSync('\x1b[2H\x1b[2Mn');
|
||||
assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']);
|
||||
// skip below scrollbottom
|
||||
term.writeSync('\x1b[7H\x1b[2Mo');
|
||||
assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']);
|
||||
term.writeSync('\x1b[8H\x1b[2Mp');
|
||||
assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']);
|
||||
term.writeSync('\x1b[100H\x1b[2Mq');
|
||||
assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']);
|
||||
});
|
||||
it('deleteLines - within margins', () => {
|
||||
term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r');
|
||||
assert.equal(term.buffer.scrollTop, 2);
|
||||
assert.equal(term.buffer.scrollBottom, 5);
|
||||
term.writeSync('\x1b[6H\x1b[2Mm');
|
||||
assert.deepEqual(getLines(term), ['0', '1', '2', '3', '4', 'm', '6', '7', '8', '9']);
|
||||
term.writeSync('\x1b[3H\x1b[2Mn');
|
||||
assert.deepEqual(getLines(term), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+264
-196
@@ -15,7 +15,7 @@ import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from '
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams } from 'common/parser/Types';
|
||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
|
||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IAttributeData, IDisposable } from 'common/Types';
|
||||
@@ -280,6 +280,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._parser.setEscHandler('.' + flag, () => this.selectCharset('.' + flag));
|
||||
this._parser.setEscHandler('/' + flag, () => this.selectCharset('/' + flag)); // TODO: supported?
|
||||
}
|
||||
this._parser.setEscHandler('#8', () => this.screenAlignmentPattern());
|
||||
|
||||
/**
|
||||
* error handler
|
||||
@@ -430,12 +431,12 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// row changed, get it again
|
||||
bufferRow = buffer.lines.get(buffer.y + buffer.ybase);
|
||||
} else {
|
||||
buffer.x = cols - 1;
|
||||
if (chWidth === 2) {
|
||||
// FIXME: check for xterm behavior
|
||||
// What to do here? We got a wide char that does not fit into last cell
|
||||
continue;
|
||||
}
|
||||
// FIXME: Do we have to set buffer.x to cols - 1, if not wrapping?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,6 +541,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Backspace (Ctrl-H).
|
||||
*/
|
||||
public backspace(): void {
|
||||
this._restrictCursor();
|
||||
if (this._terminal.buffer.x > 0) {
|
||||
this._terminal.buffer.x--;
|
||||
}
|
||||
@@ -550,6 +552,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Horizontal Tab (HT) (Ctrl-I).
|
||||
*/
|
||||
public tab(): void {
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
return;
|
||||
}
|
||||
const originalX = this._terminal.buffer.x;
|
||||
this._terminal.buffer.x = this._terminal.buffer.nextStop();
|
||||
if (this._terminal.options.screenReaderMode) {
|
||||
@@ -576,16 +581,37 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps @
|
||||
* Insert Ps (Blank) Character(s) (default = 1) (ICH).
|
||||
* Restrict cursor to viewport size / scroll margin (origin mode).
|
||||
*/
|
||||
public insertChars(params: IParams): void {
|
||||
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells(
|
||||
this._terminal.buffer.x,
|
||||
params.params[0] || 1,
|
||||
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
|
||||
);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
private _restrictCursor(): void {
|
||||
this._terminal.buffer.x = Math.min(this._terminal.cols - 1, Math.max(0, this._terminal.buffer.x));
|
||||
this._terminal.buffer.y = this._terminal.originMode
|
||||
? Math.min(this._terminal.buffer.scrollBottom, Math.max(this._terminal.buffer.scrollTop, this._terminal.buffer.y))
|
||||
: Math.min(this._terminal.rows - 1, Math.max(0, this._terminal.buffer.y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set absolute cursor position.
|
||||
*/
|
||||
private _setCursor(x: number, y: number): void {
|
||||
if (this._terminal.originMode) {
|
||||
this._terminal.buffer.x = x;
|
||||
this._terminal.buffer.y = this._terminal.buffer.scrollTop + y;
|
||||
} else {
|
||||
this._terminal.buffer.x = x;
|
||||
this._terminal.buffer.y = y;
|
||||
}
|
||||
this._restrictCursor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set relative cursor position.
|
||||
*/
|
||||
private _moveCursor(x: number, y: number): void {
|
||||
// for relative changes we have to make sure we are within 0 .. cols/rows - 1
|
||||
// before calculating the new position
|
||||
this._restrictCursor();
|
||||
this._setCursor(this._terminal.buffer.x + x, this._terminal.buffer.y + y);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -593,11 +619,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Cursor Up Ps Times (default = 1) (CUU).
|
||||
*/
|
||||
public cursorUp(params: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
this._terminal.buffer.y -= param;
|
||||
if (this._terminal.buffer.y < 0) {
|
||||
this._terminal.buffer.y = 0;
|
||||
}
|
||||
this._moveCursor(0, -(params.params[0] || 1));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -605,15 +627,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Cursor Down Ps Times (default = 1) (CUD).
|
||||
*/
|
||||
public cursorDown(params: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
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._moveCursor(0, params.params[0] || 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -621,11 +635,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Cursor Forward Ps Times (default = 1) (CUF).
|
||||
*/
|
||||
public cursorForward(params: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
this._terminal.buffer.x += param;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||
}
|
||||
this._moveCursor(params.params[0] || 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,55 +643,35 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Cursor Backward Ps Times (default = 1) (CUB).
|
||||
*/
|
||||
public cursorBackward(params: IParams): void {
|
||||
const param = params.params[0] || 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._terminal.buffer.x -= param;
|
||||
if (this._terminal.buffer.x < 0) {
|
||||
this._terminal.buffer.x = 0;
|
||||
}
|
||||
this._moveCursor(-(params.params[0] || 1), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
this._terminal.buffer.y += param;
|
||||
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||
this._terminal.buffer.y = this._terminal.rows - 1;
|
||||
}
|
||||
this._moveCursor(0, params.params[0] || 1);
|
||||
this._terminal.buffer.x = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
this._terminal.buffer.y -= param;
|
||||
if (this._terminal.buffer.y < 0) {
|
||||
this._terminal.buffer.y = 0;
|
||||
}
|
||||
this._moveCursor(0, -(params.params[0] || 1));
|
||||
this._terminal.buffer.x = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI Ps G
|
||||
* Cursor Character Absolute [column] (default = [row,1]) (CHA).
|
||||
*/
|
||||
public cursorCharAbsolute(params: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
this._terminal.buffer.x = param - 1;
|
||||
this._setCursor((params.params[0] || 1) - 1, this._terminal.buffer.y);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -689,23 +679,73 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Cursor Position [row;column] (default = [1,1]) (CUP).
|
||||
*/
|
||||
public cursorPosition(params: IParams): void {
|
||||
let row: number = params.params[0] - 1;
|
||||
let col: number = (params.length >= 2) ? params.params[1] - 1 : 0;
|
||||
this._setCursor(
|
||||
// col
|
||||
(params.length >= 2) ? (params.params[1] || 1) - 1 : 0,
|
||||
// row
|
||||
(params.params[0] || 1) - 1);
|
||||
}
|
||||
|
||||
if (row < 0) {
|
||||
row = 0;
|
||||
} else if (row >= this._terminal.rows) {
|
||||
row = this._terminal.rows - 1;
|
||||
/**
|
||||
* CSI Pm ` Character Position Absolute
|
||||
* [column] (default = [row,1]) (HPA).
|
||||
* Currently same functionality as CHA.
|
||||
*/
|
||||
public charPosAbsolute(params: IParams): void {
|
||||
this._setCursor((params.params[0] || 1) - 1, this._terminal.buffer.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm a Character Position Relative
|
||||
* [columns] (default = [row,col+1]) (HPR)
|
||||
* Currently same functionality as CUF.
|
||||
*/
|
||||
public hPositionRelative(params: IParams): void {
|
||||
this._moveCursor(params.params[0] || 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm d Vertical Position Absolute (VPA)
|
||||
* [row] (default = [1,column])
|
||||
*/
|
||||
public linePosAbsolute(params: IParams): void {
|
||||
this._setCursor(this._terminal.buffer.x, (params.params[0] || 1) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm e Vertical Position Relative (VPR)
|
||||
* [rows] (default = [row+1,column])
|
||||
* reuse CSI Ps B ?
|
||||
*/
|
||||
public vPositionRelative(params: IParams): void {
|
||||
this._moveCursor(0, params.params[0] || 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps ; Ps f
|
||||
* Horizontal and Vertical Position [row;column] (default =
|
||||
* [1,1]) (HVP).
|
||||
* Same as CUP.
|
||||
*/
|
||||
public hVPosition(params: IParams): void {
|
||||
this.cursorPosition(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps g Tab Clear (TBC).
|
||||
* Ps = 0 -> Clear Current Column (default).
|
||||
* Ps = 3 -> Clear All.
|
||||
* Potentially:
|
||||
* Ps = 2 -> Clear Stops on Line.
|
||||
* http://vt100.net/annarbor/aaa-ug/section6.html
|
||||
*/
|
||||
public tabClear(params: IParams): void {
|
||||
const param = params.params[0];
|
||||
if (param === 0) {
|
||||
delete this._terminal.buffer.tabs[this._terminal.buffer.x];
|
||||
} else if (param === 3) {
|
||||
this._terminal.buffer.tabs = {};
|
||||
}
|
||||
|
||||
if (col < 0) {
|
||||
col = 0;
|
||||
} else if (col >= this._terminal.cols) {
|
||||
col = this._terminal.cols - 1;
|
||||
}
|
||||
|
||||
this._terminal.buffer.x = col;
|
||||
this._terminal.buffer.y = row;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -713,12 +753,33 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).
|
||||
*/
|
||||
public cursorForwardTab(params: IParams): void {
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
return;
|
||||
}
|
||||
let param = params.params[0] || 1;
|
||||
while (param--) {
|
||||
this._terminal.buffer.x = this._terminal.buffer.nextStop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
|
||||
*/
|
||||
public cursorBackwardTab(params: IParams): void {
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
return;
|
||||
}
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._terminal.buffer;
|
||||
|
||||
while (param--) {
|
||||
buffer.x = buffer.prevStop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method to erase cells in a terminal row.
|
||||
* The cell gets replaced with the eraseChar of the terminal.
|
||||
@@ -744,7 +805,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* @param y row index
|
||||
*/
|
||||
private _resetBufferLine(y: number): void {
|
||||
this._eraseInBufferLine(y, 0, this._terminal.cols, true);
|
||||
const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y);
|
||||
line.fill(this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()));
|
||||
line.isWrapped = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -760,6 +823,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Ps = 2 -> Selective Erase All.
|
||||
*/
|
||||
public eraseInDisplay(params: IParams): void {
|
||||
this._restrictCursor();
|
||||
let j;
|
||||
switch (params.params[0]) {
|
||||
case 0:
|
||||
@@ -819,6 +883,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Ps = 2 -> Selective Erase All.
|
||||
*/
|
||||
public eraseInLine(params: IParams): void {
|
||||
this._restrictCursor();
|
||||
switch (params.params[0]) {
|
||||
case 0:
|
||||
this._eraseInBufferLine(this._terminal.buffer.y, this._terminal.buffer.x, this._terminal.cols);
|
||||
@@ -838,11 +903,16 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Insert Ps Line(s) (default = 1) (IL).
|
||||
*/
|
||||
public insertLines(params: IParams): void {
|
||||
this._restrictCursor();
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._terminal.buffer;
|
||||
|
||||
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
|
||||
return;
|
||||
}
|
||||
|
||||
const row: number = buffer.y + buffer.ybase;
|
||||
|
||||
const scrollBottomRowsOffset = this._terminal.rows - 1 - buffer.scrollBottom;
|
||||
@@ -857,6 +927,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// this.maxRange();
|
||||
this._terminal.updateRange(buffer.y);
|
||||
this._terminal.updateRange(buffer.scrollBottom);
|
||||
buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -864,11 +935,16 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Delete Ps Line(s) (default = 1) (DL).
|
||||
*/
|
||||
public deleteLines(params: IParams): void {
|
||||
this._restrictCursor();
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._terminal.buffer;
|
||||
|
||||
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
|
||||
return;
|
||||
}
|
||||
|
||||
const row: number = buffer.y + buffer.ybase;
|
||||
|
||||
let j: number;
|
||||
@@ -884,6 +960,24 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// this.maxRange();
|
||||
this._terminal.updateRange(buffer.y);
|
||||
this._terminal.updateRange(buffer.scrollBottom);
|
||||
buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps @
|
||||
* Insert Ps (Blank) Character(s) (default = 1) (ICH).
|
||||
*/
|
||||
public insertChars(params: IParams): void {
|
||||
this._restrictCursor();
|
||||
const line = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase);
|
||||
if (line) {
|
||||
line.insertCells(
|
||||
this._terminal.buffer.x,
|
||||
params.params[0] || 1,
|
||||
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
|
||||
);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -891,12 +985,16 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Delete Ps Character(s) (default = 1) (DCH).
|
||||
*/
|
||||
public deleteChars(params: IParams): void {
|
||||
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells(
|
||||
this._terminal.buffer.x,
|
||||
params.params[0] || 1,
|
||||
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
|
||||
);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
this._restrictCursor();
|
||||
const line = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase);
|
||||
if (line) {
|
||||
line.deleteCells(
|
||||
this._terminal.buffer.x,
|
||||
params.params[0] || 1,
|
||||
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
|
||||
);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -942,50 +1040,15 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Erase Ps Character(s) (default = 1) (ECH).
|
||||
*/
|
||||
public eraseChars(params: IParams): void {
|
||||
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells(
|
||||
this._terminal.buffer.x,
|
||||
this._terminal.buffer.x + (params.params[0] || 1),
|
||||
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
|
||||
);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
|
||||
*/
|
||||
public cursorBackwardTab(params: IParams): void {
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._terminal.buffer;
|
||||
|
||||
while (param--) {
|
||||
buffer.x = buffer.prevStop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm ` Character Position Absolute
|
||||
* [column] (default = [row,1]) (HPA).
|
||||
*/
|
||||
public charPosAbsolute(params: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
this._terminal.buffer.x = param - 1;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm a Character Position Relative
|
||||
* [columns] (default = [row,col+1]) (HPR)
|
||||
* reuse CSI Ps C ?
|
||||
*/
|
||||
public hPositionRelative(params: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
this._terminal.buffer.x += param;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||
this._restrictCursor();
|
||||
const line = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase);
|
||||
if (line) {
|
||||
line.replaceCells(
|
||||
this._terminal.buffer.x,
|
||||
this._terminal.buffer.x + (params.params[0] || 1),
|
||||
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
|
||||
);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1090,72 +1153,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm d Vertical Position Absolute (VPA)
|
||||
* [row] (default = [1,column])
|
||||
*/
|
||||
public linePosAbsolute(params: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
this._terminal.buffer.y = param - 1;
|
||||
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||
this._terminal.buffer.y = this._terminal.rows - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm e Vertical Position Relative (VPR)
|
||||
* [rows] (default = [row+1,column])
|
||||
* reuse CSI Ps B ?
|
||||
*/
|
||||
public vPositionRelative(params: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
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--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps ; Ps f
|
||||
* Horizontal and Vertical Position [row;column] (default =
|
||||
* [1,1]) (HVP).
|
||||
*/
|
||||
public hVPosition(params: IParams): void {
|
||||
const row = params.params[0] || 1;
|
||||
const col = (params.length > 1) ? params.params[1] || 1 : 1;
|
||||
|
||||
this._terminal.buffer.y = row - 1;
|
||||
if (this._terminal.buffer.y >= this._terminal.rows) {
|
||||
this._terminal.buffer.y = this._terminal.rows - 1;
|
||||
}
|
||||
|
||||
this._terminal.buffer.x = col - 1;
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps g Tab Clear (TBC).
|
||||
* Ps = 0 -> Clear Current Column (default).
|
||||
* Ps = 3 -> Clear All.
|
||||
* Potentially:
|
||||
* Ps = 2 -> Clear Stops on Line.
|
||||
* http://vt100.net/annarbor/aaa-ug/section6.html
|
||||
*/
|
||||
public tabClear(params: IParams): void {
|
||||
const param = params.params[0];
|
||||
if (param === 0) {
|
||||
delete this._terminal.buffer.tabs[this._terminal.buffer.x];
|
||||
} else if (param === 3) {
|
||||
this._terminal.buffer.tabs = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm h Set Mode (SM).
|
||||
* Ps = 2 -> Keyboard Action Mode (AM).
|
||||
@@ -1271,11 +1268,14 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// set VT100 mode here
|
||||
break;
|
||||
case 3: // 132 col mode
|
||||
// TODO: move DECCOLM into compat addon
|
||||
this._terminal.savedCols = this._terminal.cols;
|
||||
this._terminal.resize(132, this._terminal.rows);
|
||||
this._terminal.reset();
|
||||
break;
|
||||
case 6:
|
||||
this._terminal.originMode = true;
|
||||
this._setCursor(0, 0);
|
||||
break;
|
||||
case 7:
|
||||
this._terminal.wraparoundMode = true;
|
||||
@@ -1467,13 +1467,18 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._coreService.decPrivateModes.applicationCursorKeys = false;
|
||||
break;
|
||||
case 3:
|
||||
// TODO: move DECCOLM into compat addon
|
||||
// Note: This impl currently does not enforce col 80, instead reverts
|
||||
// to previous terminal width before entering DECCOLM 132
|
||||
if (this._terminal.cols === 132 && this._terminal.savedCols) {
|
||||
this._terminal.resize(this._terminal.savedCols, this._terminal.rows);
|
||||
}
|
||||
delete this._terminal.savedCols;
|
||||
this._terminal.reset();
|
||||
break;
|
||||
case 6:
|
||||
this._terminal.originMode = false;
|
||||
this._setCursor(0, 0);
|
||||
break;
|
||||
case 7:
|
||||
this._terminal.wraparoundMode = false;
|
||||
@@ -1913,10 +1918,19 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
if (collect) {
|
||||
return;
|
||||
}
|
||||
this._terminal.buffer.scrollTop = (params.params[0] || 1) - 1;
|
||||
this._terminal.buffer.scrollBottom = (params.length > 1 && params.params[1] && params.params[1] <= this._terminal.rows ? params.params[1] : this._terminal.rows) - 1;
|
||||
this._terminal.buffer.x = 0;
|
||||
this._terminal.buffer.y = 0;
|
||||
|
||||
const top = params.params[0] || 1;
|
||||
let bottom: number;
|
||||
|
||||
if (params.length < 2 || (bottom = params.params[1]) > this._terminal.rows || bottom === 0) {
|
||||
bottom = this._terminal.rows;
|
||||
}
|
||||
|
||||
if (bottom > top) {
|
||||
this._terminal.buffer.scrollTop = top - 1;
|
||||
this._terminal.buffer.scrollBottom = bottom - 1;
|
||||
this._setCursor(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1930,6 +1944,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._terminal.buffer.savedY = this._terminal.buffer.ybase + this._terminal.buffer.y;
|
||||
this._terminal.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg;
|
||||
this._terminal.buffer.savedCurAttrData.bg = this._terminal.curAttrData.bg;
|
||||
this._terminal.buffer.savedCharset = this._terminal.charset;
|
||||
}
|
||||
|
||||
|
||||
@@ -1943,6 +1958,11 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._terminal.buffer.y = Math.max(this._terminal.buffer.savedY - this._terminal.buffer.ybase, 0);
|
||||
this._terminal.curAttrData.fg = this._terminal.buffer.savedCurAttrData.fg;
|
||||
this._terminal.curAttrData.bg = this._terminal.buffer.savedCurAttrData.bg;
|
||||
this._terminal.charset = (this as any)._savedCharset;
|
||||
if (this._terminal.buffer.savedCharset) {
|
||||
this._terminal.charset = this._terminal.buffer.savedCharset;
|
||||
}
|
||||
this._restrictCursor();
|
||||
}
|
||||
|
||||
|
||||
@@ -2038,7 +2058,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Moves the cursor down one line in the same column.
|
||||
*/
|
||||
public index(): void {
|
||||
this._terminal.index(); // TODO: save to move from terminal?
|
||||
this._restrictCursor();
|
||||
this._terminal.buffer.y++;
|
||||
if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {
|
||||
this._terminal.buffer.y--;
|
||||
this._terminal.scroll();
|
||||
}
|
||||
this._restrictCursor();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2049,7 +2075,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* the value of the active column when the terminal receives an HTS.
|
||||
*/
|
||||
public tabSet(): void {
|
||||
this._terminal.tabSet(); // TODO: save to move from terminal?
|
||||
this._terminal.buffer.tabs[this._terminal.buffer.x] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2060,7 +2086,21 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* the page scrolls down.
|
||||
*/
|
||||
public reverseIndex(): void {
|
||||
this._terminal.reverseIndex(); // TODO: save to move from terminal?
|
||||
this._restrictCursor();
|
||||
const buffer = this._terminal.buffer;
|
||||
if (buffer.y === buffer.scrollTop) {
|
||||
// possibly move the code below to term.reverseScroll();
|
||||
// test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
|
||||
// blankLine(true) is xterm/linux behavior
|
||||
const scrollRegionHeight = buffer.scrollBottom - buffer.scrollTop;
|
||||
buffer.lines.shiftElements(buffer.y + buffer.ybase, scrollRegionHeight, 1);
|
||||
buffer.lines.set(buffer.y + buffer.ybase, buffer.getBlankLine(this._terminal.eraseAttrData()));
|
||||
this._terminal.updateRange(buffer.scrollTop);
|
||||
this._terminal.updateRange(buffer.scrollBottom);
|
||||
} else {
|
||||
buffer.y--;
|
||||
this._restrictCursor(); // quickfix to not run out of bounds
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2086,4 +2126,32 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
public setgLevel(level: number): void {
|
||||
this._terminal.setgLevel(level); // TODO: save to move from terminal?
|
||||
}
|
||||
|
||||
/**
|
||||
* ESC # 8
|
||||
* DEC mnemonic: DECALN (https://vt100.net/docs/vt510-rm/DECALN.html)
|
||||
* This control function fills the complete screen area with
|
||||
* a test pattern (E) used for adjusting screen alignment.
|
||||
*
|
||||
* TODO: move DECALN into compat addon
|
||||
*/
|
||||
public screenAlignmentPattern(): void {
|
||||
// prepare cell data
|
||||
const cell = new CellData();
|
||||
cell.content = 1 << Content.WIDTH_SHIFT | 'E'.charCodeAt(0);
|
||||
cell.fg = this._terminal.curAttrData.fg;
|
||||
cell.bg = this._terminal.curAttrData.bg;
|
||||
|
||||
const buffer = this._terminal.buffer;
|
||||
|
||||
this._setCursor(0, 0);
|
||||
for (let yOffset = 0; yOffset < this._terminal.rows; ++yOffset) {
|
||||
const row = buffer.y + buffer.ybase + yOffset;
|
||||
buffer.lines.get(row).fill(cell);
|
||||
buffer.lines.get(row).isWrapped = false;
|
||||
}
|
||||
this._terminal.updateRange(0);
|
||||
this._terminal.updateRange(this._terminal.rows);
|
||||
this._setCursor(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Terminal } from './Terminal';
|
||||
import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { wcwidth } from 'common/CharWidth';
|
||||
|
||||
const INIT_COLS = 80;
|
||||
const INIT_ROWS = 24;
|
||||
@@ -750,10 +751,14 @@ describe('Terminal', () => {
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
term.buffer.x = term.cols - 1;
|
||||
term.wraparoundMode = false;
|
||||
const width = wcwidth((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000);
|
||||
if (width !== 1) {
|
||||
continue;
|
||||
}
|
||||
term.write('a' + high + String.fromCharCode(i));
|
||||
// auto wraparound mode should cut off the rest of the line
|
||||
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a');
|
||||
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(1);
|
||||
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i));
|
||||
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(2);
|
||||
expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql('');
|
||||
term.reset();
|
||||
}
|
||||
|
||||
+6
-48
@@ -1811,46 +1811,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
}
|
||||
|
||||
/**
|
||||
* ESC
|
||||
*/
|
||||
|
||||
/**
|
||||
* ESC D Index (IND is 0x84).
|
||||
*/
|
||||
public index(): void {
|
||||
this.buffer.y++;
|
||||
if (this.buffer.y > this.buffer.scrollBottom) {
|
||||
this.buffer.y--;
|
||||
this.scroll();
|
||||
}
|
||||
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
||||
if (this.buffer.x >= this.cols) {
|
||||
this.buffer.x--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ESC M Reverse Index (RI is 0x8d).
|
||||
*
|
||||
* Move the cursor up one row, inserting a new blank line if necessary.
|
||||
*/
|
||||
public reverseIndex(): void {
|
||||
if (this.buffer.y === this.buffer.scrollTop) {
|
||||
// possibly move the code below to term.reverseScroll();
|
||||
// test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
|
||||
// blankLine(true) is xterm/linux behavior
|
||||
const scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop;
|
||||
this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1);
|
||||
this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.buffer.getBlankLine(this.eraseAttrData()));
|
||||
this.updateRange(this.buffer.scrollTop);
|
||||
this.updateRange(this.buffer.scrollBottom);
|
||||
} else {
|
||||
this.buffer.y--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ESC c Full Reset (RIS).
|
||||
* Reset terminal.
|
||||
* Note: Calling this directly from JS is synchronous but does not clear
|
||||
* input buffers and does not reset the parser, thus the terminal will
|
||||
* continue to apply pending input data.
|
||||
* If you need in band reset (synchronous with input data) consider
|
||||
* using DECSTR (soft reset, CSI ! p) or RIS instead (hard reset, ESC c).
|
||||
*/
|
||||
public reset(): void {
|
||||
/**
|
||||
@@ -1892,14 +1858,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ESC H Tab Set (HTS is 0x88).
|
||||
*/
|
||||
public tabSet(): void {
|
||||
this.buffer.tabs[this.buffer.x] = true;
|
||||
}
|
||||
|
||||
// TODO: Remove cancel function and cancelEvents option
|
||||
public cancel(ev: Event, force?: boolean): boolean {
|
||||
if (!this.options.cancelEvents && !force) {
|
||||
|
||||
+92
-112
@@ -1,45 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* This file contains integration tests for xterm.js.
|
||||
*/
|
||||
|
||||
import * as glob from 'glob';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import * as pty from 'node-pty';
|
||||
import { Terminal } from './Terminal';
|
||||
import { IViewport } from './Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';
|
||||
import { IDisposable } from 'xterm';
|
||||
|
||||
class TestTerminal extends Terminal {
|
||||
innerWrite(): void { this._innerWrite(); }
|
||||
// all test files expect terminal in 80x25
|
||||
const COLS = 80;
|
||||
const ROWS = 25;
|
||||
|
||||
const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')});
|
||||
const SKIP_FILES = [
|
||||
't0070-DECSTBM_LF.in', // lineFeed not working correctly
|
||||
't0071-DECSTBM_IND.in',
|
||||
't0072-DECSTBM_NEL.in',
|
||||
't0075-DECSTBM_CUU_CUD.in',
|
||||
't0076-DECSTBM_IL_DL.in', // not working due to lineFeed
|
||||
't0077-DECSTBM_quirks.in',
|
||||
't0084-CBT.in',
|
||||
't0101-NLM.in',
|
||||
't0103-reverse_wrap.in',
|
||||
't0504-vim.in'
|
||||
];
|
||||
if (os.platform() === 'darwin') {
|
||||
// These are failing on macOS only (termios related?)
|
||||
SKIP_FILES.push(
|
||||
't0003-line_wrap.in',
|
||||
't0005-CR.in',
|
||||
't0009-NEL.in',
|
||||
't0503-zsh_ls_color.in'
|
||||
);
|
||||
}
|
||||
// filter skipFilenames
|
||||
const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slice(-1)[0]) === -1);
|
||||
|
||||
let primitivePty: any;
|
||||
|
||||
// fake sychronous pty write - read
|
||||
// we just pipe the data from slave to master as a child program would do
|
||||
// pty.js opens pipe fds with O_NONBLOCK
|
||||
// just wait 10ms instead of setting fds to blocking mode
|
||||
function ptyWriteRead(data: string, cb: (result: string) => void): void {
|
||||
fs.writeSync(primitivePty.slave, data);
|
||||
setTimeout(() => {
|
||||
const b = new Buffer(64000);
|
||||
const bytes = fs.readSync(primitivePty.master, b, 0, 64000, null);
|
||||
cb(b.toString('utf8', 0, bytes));
|
||||
describe('Escape Sequence Files', function(): void {
|
||||
this.timeout(20000);
|
||||
|
||||
let ptyTerm: any;
|
||||
let slaveEnd: any;
|
||||
let term: Terminal;
|
||||
let customHandler: IDisposable | undefined;
|
||||
|
||||
before(() => {
|
||||
ptyTerm = (pty as any).open({cols: COLS, rows: ROWS});
|
||||
slaveEnd = ptyTerm._slave;
|
||||
term = new Terminal({cols: COLS, rows: ROWS});
|
||||
ptyTerm._master.on('data', (data: string) => term.write(data));
|
||||
});
|
||||
}
|
||||
|
||||
// make sure raw pty is at x=0 and has no pending data
|
||||
function ptyReset(cb: (result: string) => void): void {
|
||||
ptyWriteRead('\r\n', cb);
|
||||
}
|
||||
after(() => {
|
||||
ptyTerm._master.end();
|
||||
ptyTerm._master.destroy();
|
||||
});
|
||||
|
||||
FILES.forEach(filename => {
|
||||
it(filename.split('/').slice(-1)[0], async () => {
|
||||
// reset terminal and handler
|
||||
if (customHandler) {
|
||||
customHandler.dispose();
|
||||
}
|
||||
slaveEnd.write('\r\n');
|
||||
term.reset();
|
||||
slaveEnd.write('\x1bc\x1b[H');
|
||||
|
||||
// register handler to trigger viewport scraping, wait for it to finish
|
||||
let content = '';
|
||||
const OSC_CODE = 12345;
|
||||
await new Promise(resolve => {
|
||||
customHandler = term.addOscHandler(OSC_CODE, () => {
|
||||
// grab terminal viewport content
|
||||
content = terminalToString(term);
|
||||
resolve();
|
||||
return true;
|
||||
});
|
||||
// write file to slave
|
||||
slaveEnd.write(fs.readFileSync(filename, 'utf8'));
|
||||
// trigger custom sequence
|
||||
slaveEnd.write(`\x1b]${OSC_CODE};\x07`);
|
||||
});
|
||||
|
||||
// compare with expected output (right trimmed)
|
||||
const expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
|
||||
const expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n');
|
||||
if (content !== expectedRightTrimmed) {
|
||||
throw new Error(formatError(fs.readFileSync(filename, 'utf8'), content, expected));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
/* debug helpers */
|
||||
// generate colorful noisy output to compare xterm and emulator cell states
|
||||
function formatError(input: string, output: string, expected: string): string {
|
||||
function addLineNumber(start: number, color: string): (s: string) => string {
|
||||
@@ -51,10 +112,10 @@ function formatError(input: string, output: string, expected: string): string {
|
||||
}
|
||||
const line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890';
|
||||
let s = '';
|
||||
s += '\n\x1b[34m' + JSON.stringify(input);
|
||||
s += '\n\x1b[33m ' + line80 + '\n';
|
||||
s += `\n\x1b[34m${JSON.stringify(input)}`;
|
||||
s += `\n\x1b[33m ${line80}\n`;
|
||||
s += output.split('\n').map(addLineNumber(0, '\x1b[31m')).join('\n');
|
||||
s += '\n\x1b[33m ' + line80 + '\n';
|
||||
s += `\n\x1b[33m ${line80}\n`;
|
||||
s += expected.split('\n').map(addLineNumber(0, '\x1b[32m')).join('\n');
|
||||
return s;
|
||||
}
|
||||
@@ -64,10 +125,7 @@ function terminalToString(term: Terminal): string {
|
||||
let result = '';
|
||||
let lineText = '';
|
||||
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
|
||||
lineText = '';
|
||||
for (let cell = 0; cell < term.cols; ++cell) {
|
||||
lineText += term.buffer.lines.get(line).loadCell(cell, new CellData()).getChars() || WHITESPACE_CELL_CHAR;
|
||||
}
|
||||
lineText = term.buffer.lines.get(line).translateToString(true);
|
||||
// rtrim empty cells as xterm does
|
||||
lineText = lineText.replace(/\s+$/, '');
|
||||
result += lineText;
|
||||
@@ -75,81 +133,3 @@ function terminalToString(term: Terminal): string {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Skip tests on Windows since pty.open isn't supported
|
||||
if (os.platform() !== 'win32') {
|
||||
const consoleLog = console.log;
|
||||
|
||||
// expect files need terminal at 80x25!
|
||||
const cols = 80;
|
||||
const rows = 25;
|
||||
|
||||
/** some helpers for pty interaction */
|
||||
// we need a pty in between to get the termios decorations
|
||||
// for the basic test cases a raw pty device is enough
|
||||
primitivePty = (<any>pty).native.open(cols, rows);
|
||||
|
||||
/** tests */
|
||||
describe('xterm output comparison', function(): void {
|
||||
this.timeout(10000);
|
||||
let xterm: TestTerminal;
|
||||
|
||||
beforeEach(() => {
|
||||
xterm = new TestTerminal({ cols: cols, rows: rows });
|
||||
xterm.refresh = () => {};
|
||||
xterm.viewport = <IViewport>{
|
||||
syncScrollArea: () => {}
|
||||
};
|
||||
});
|
||||
|
||||
// omit stack trace for escape sequence files
|
||||
Error.stackTraceLimit = 0;
|
||||
const files = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')});
|
||||
// for (let i = 0; i < files.length; ++i) console.debug(i, files[i]);
|
||||
// only successful tests for now
|
||||
const skip = [
|
||||
10, 16, 17, 19, 32, 34, 35, 36, 39,
|
||||
40, 42, 43, 44, 45, 46, 47, 48, 49, 50,
|
||||
51, 52, 54, 55, 56, 57, 58, 59, 60, 61,
|
||||
63, 68
|
||||
];
|
||||
// These are failing on macOS only
|
||||
if (os.platform() === 'darwin') {
|
||||
skip.push(3, 7, 11, 67);
|
||||
}
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (skip.indexOf(i) >= 0) {
|
||||
continue;
|
||||
}
|
||||
((filename: string) => {
|
||||
const inFile = fs.readFileSync(filename, 'utf8');
|
||||
it(filename.split('/').slice(-1)[0], done => {
|
||||
ptyReset(() => {
|
||||
ptyWriteRead(inFile, fromPty => {
|
||||
// uncomment this to get log from terminal
|
||||
// console.log = function(){};
|
||||
|
||||
// Perform a synchronous .write(data)
|
||||
xterm.writeBuffer.push(fromPty);
|
||||
xterm.innerWrite();
|
||||
|
||||
const fromEmulator = terminalToString(xterm);
|
||||
console.log = consoleLog;
|
||||
const expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
|
||||
|
||||
// Some of the tests have whitespace on the right of lines, we trim all the linex
|
||||
// from xterm.js so ignore this for now at least.
|
||||
const expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n');
|
||||
if (fromEmulator !== expectedRightTrimmed) {
|
||||
// uncomment to get noisy output
|
||||
throw new Error(formatError(inFile, fromEmulator, expected));
|
||||
// throw new Error('mismatch');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
})(files[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+2
-10
@@ -6,7 +6,7 @@
|
||||
import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
|
||||
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions, ILinkifier, ILinkMatcherOptions } from './Types';
|
||||
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
|
||||
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types';
|
||||
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types';
|
||||
import { Buffer } from 'common/buffer/Buffer';
|
||||
import * as Browser from 'common/Platform';
|
||||
import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm';
|
||||
@@ -295,21 +295,12 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
|
||||
addDisposableListener(type: string, handler: XtermListener): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
tabSet(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
handler(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
handleTitle(title: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
index(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
reverseIndex(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockBuffer implements IBuffer {
|
||||
@@ -329,6 +320,7 @@ export class MockBuffer implements IBuffer {
|
||||
scrollTop: number;
|
||||
savedY: number;
|
||||
savedX: number;
|
||||
savedCharset: ICharset | null;
|
||||
savedCurAttrData = new AttributeData();
|
||||
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
|
||||
return Buffer.prototype.translateBufferLineToString.apply(this, arguments);
|
||||
|
||||
Vendored
+1
-3
@@ -70,10 +70,7 @@ export interface IInputHandlingTerminal {
|
||||
showCursor(): void;
|
||||
refresh(start: number, end: number): void;
|
||||
error(text: string, data?: any): void;
|
||||
tabSet(): void;
|
||||
handleTitle(title: string): void;
|
||||
index(): void;
|
||||
reverseIndex(): void;
|
||||
}
|
||||
|
||||
export interface IViewport extends IDisposable {
|
||||
@@ -169,6 +166,7 @@ export interface IInputHandler {
|
||||
ESC |
|
||||
ESC }
|
||||
ESC ~ */ setgLevel(level: number): void;
|
||||
/** ESC # 8 */ screenAlignmentPattern(): void;
|
||||
}
|
||||
|
||||
export interface ILinkMatcher {
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
|
||||
import { CircularList, IInsertEvent } from 'common/CircularList';
|
||||
import { IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from 'common/buffer/Types';
|
||||
import { IBufferLine, ICellData, IAttributeData } from 'common/Types';
|
||||
import { IBufferLine, ICellData, IAttributeData, ICharset } from 'common/Types';
|
||||
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from 'common/buffer/Constants';
|
||||
import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from 'common/buffer/BufferReflow';
|
||||
import { Marker } from 'common/buffer/Marker';
|
||||
import { IOptionsService, IBufferService } from 'common/services/Services';
|
||||
import { DEFAULT_CHARSET } from 'common/data/Charsets';
|
||||
|
||||
export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
|
||||
|
||||
@@ -35,6 +36,7 @@ export class Buffer implements IBuffer {
|
||||
public savedY: number = 0;
|
||||
public savedX: number = 0;
|
||||
public savedCurAttrData = DEFAULT_ATTR_DATA.clone();
|
||||
public savedCharset: ICharset | null = DEFAULT_CHARSET;
|
||||
public markers: Marker[] = [];
|
||||
private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
||||
private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]);
|
||||
|
||||
Vendored
+2
-1
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IAttributeData, ICircularList, IBufferLine, ICellData, IMarker } from 'common/Types';
|
||||
import { IAttributeData, ICircularList, IBufferLine, ICellData, IMarker, ICharset } from 'common/Types';
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
|
||||
// BufferIndex denotes a position in the buffer: [rowIndex, colIndex]
|
||||
@@ -31,6 +31,7 @@ export interface IBuffer {
|
||||
hasScrollback: boolean;
|
||||
savedY: number;
|
||||
savedX: number;
|
||||
savedCharset: ICharset | null;
|
||||
savedCurAttrData: IAttributeData;
|
||||
isCursorInViewport: boolean;
|
||||
markers: IMarker[];
|
||||
|
||||
@@ -466,10 +466,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
break;
|
||||
case ParserAction.EXECUTE:
|
||||
this.precedingCodepoint = 0;
|
||||
callback = this._executeHandlers[code];
|
||||
if (callback) callback();
|
||||
else this._executeHandlerFb(code);
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
case ParserAction.IGNORE:
|
||||
break;
|
||||
@@ -488,10 +488,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
// inject values: currently not implemented
|
||||
break;
|
||||
case ParserAction.CSI_DISPATCH:
|
||||
// dont reset preceding codepoint for REP itself
|
||||
if (code !== 98) { // 'b'
|
||||
this.precedingCodepoint = 0;
|
||||
}
|
||||
// Trigger CSI Handler
|
||||
const handlers = this._csiHandlers[code];
|
||||
let j = handlers ? handlers.length - 1 : -1;
|
||||
@@ -504,6 +500,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
if (j < 0) {
|
||||
this._csiHandlerFb(collect, params, code);
|
||||
}
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
case ParserAction.PARAM:
|
||||
// inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a)
|
||||
@@ -529,10 +526,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
collect += String.fromCharCode(code);
|
||||
break;
|
||||
case ParserAction.ESC_DISPATCH:
|
||||
this.precedingCodepoint = 0;
|
||||
callback = this._escHandlers[collect + String.fromCharCode(code)];
|
||||
if (callback) callback(collect, code);
|
||||
else this._escHandlerFb(collect, code);
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
case ParserAction.CLEAR:
|
||||
osc = '';
|
||||
@@ -541,7 +538,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
collect = '';
|
||||
break;
|
||||
case ParserAction.DCS_HOOK:
|
||||
this.precedingCodepoint = 0;
|
||||
dcsHandler = this._dcsHandlers[collect + String.fromCharCode(code)];
|
||||
if (!dcsHandler) dcsHandler = this._dcsHandlerFb;
|
||||
dcsHandler.hook(collect, params, code);
|
||||
@@ -569,6 +565,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
params.reset();
|
||||
params.addParam(0); // ZDM
|
||||
collect = '';
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
case ParserAction.OSC_START:
|
||||
osc = '';
|
||||
@@ -584,7 +581,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
break;
|
||||
case ParserAction.OSC_END:
|
||||
this.precedingCodepoint = 0;
|
||||
if (osc && code !== 0x18 && code !== 0x1a) {
|
||||
// NOTE: OSC subparsing is not part of the original parser
|
||||
// we do basic identifier parsing here to offer a jump table for OSC as well
|
||||
@@ -616,6 +612,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
params.reset();
|
||||
params.addParam(0); // ZDM
|
||||
collect = '';
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
}
|
||||
currentState = transition & TableAccess.TRANSITION_STATE_MASK;
|
||||
|
||||
Reference in New Issue
Block a user