mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into master
This commit is contained in:
@@ -24,7 +24,7 @@ export function createProgram(gl: WebGLRenderingContext, vertexSource: string, f
|
||||
return program;
|
||||
}
|
||||
|
||||
console.log(gl.getProgramInfoLog(program));
|
||||
console.error(gl.getProgramInfoLog(program));
|
||||
gl.deleteProgram(program);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export function createShader(gl: WebGLRenderingContext, type: number, source: st
|
||||
return shader;
|
||||
}
|
||||
|
||||
console.log(gl.getShaderInfoLog(shader));
|
||||
console.error(gl.getShaderInfoLog(shader));
|
||||
gl.deleteShader(shader);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkifierEvent, ILinkifierAccessor } from '../../../../src/Types';
|
||||
import { ILinkifierAccessor } from '../../../../src/Types';
|
||||
import { Terminal } from 'xterm';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { is256Color } from '../atlas/CharAtlasUtils';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IColorSet, ILinkifierEvent } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
@@ -45,9 +45,9 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _onLinkHover(e: ILinkifierEvent): void {
|
||||
if (e.fg === INVERTED_DEFAULT_COLOR) {
|
||||
this._ctx.fillStyle = this._colors.background.css;
|
||||
} else if (is256Color(e.fg)) {
|
||||
} else if (e.fg !== undefined && is256Color(e.fg)) {
|
||||
// 256 color support
|
||||
this._ctx.fillStyle = this._colors.ansi[e.fg].css;
|
||||
this._ctx.fillStyle = this._colors.ansi[e.fg!].css;
|
||||
} else {
|
||||
this._ctx.fillStyle = this._colors.foreground.css;
|
||||
}
|
||||
|
||||
+1
-1
@@ -223,7 +223,6 @@ function initOptions(term: TerminalType): void {
|
||||
// Internal only options
|
||||
'cancelEvents',
|
||||
'convertEol',
|
||||
'debug',
|
||||
'handler',
|
||||
'screenKeys',
|
||||
'termName',
|
||||
@@ -238,6 +237,7 @@ function initOptions(term: TerminalType): void {
|
||||
fontFamily: null,
|
||||
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
logLevel: ['debug', 'info', 'warn', 'error', 'off'],
|
||||
rendererType: ['dom', 'canvas'],
|
||||
wordSeparator: null
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
+6
-6
@@ -11,7 +11,7 @@
|
||||
"prepackage": "npm run build",
|
||||
"package": "webpack",
|
||||
"start": "node demo/start",
|
||||
"lint": "tslint 'src/**/*.ts' './demo/**/*.ts' './addons/**/*.ts'",
|
||||
"lint": "tslint 'src/**/*.ts' 'addons/**/*.ts'",
|
||||
"test": "npm run test-unit",
|
||||
"posttest": "npm run lint",
|
||||
"test-api": "mocha \"**/*.api.js\"",
|
||||
@@ -36,20 +36,20 @@
|
||||
"@types/webpack": "^4.4.11",
|
||||
"@types/ws": "^6.0.1",
|
||||
"chai": "3.5.0",
|
||||
"express": "4.13.4",
|
||||
"express-ws": "2.0.0-rc.1",
|
||||
"express": "^4.17.1",
|
||||
"express-ws": "^4.0.0",
|
||||
"glob": "^7.0.5",
|
||||
"jsdom": "^11.11.0",
|
||||
"mocha": "^6.1.4",
|
||||
"node-pty": "0.7.6",
|
||||
"puppeteer": "^1.15.0",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"ts-loader": "^4.5.0",
|
||||
"tslint": "^5.9.1",
|
||||
"ts-loader": "^6.0.4",
|
||||
"tslint": "^5.18.0",
|
||||
"tslint-consistent-codestyle": "^1.13.0",
|
||||
"typescript": "3.5",
|
||||
"utf8": "^3.0.0",
|
||||
"webpack": "^4.17.1",
|
||||
"webpack": "^4.35.3",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"ws": "^7.0.0",
|
||||
"xterm-benchmark": "^0.1.3"
|
||||
|
||||
@@ -11,7 +11,7 @@ import { RenderDebouncer } from 'browser/RenderDebouncer';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { IRenderService } from 'browser/services/Services';
|
||||
|
||||
const MAX_ROWS_TO_READ = 20;
|
||||
|
||||
@@ -47,8 +47,8 @@ export class AccessibilityManager extends Disposable {
|
||||
private _charsToAnnounce: string = '';
|
||||
|
||||
constructor(
|
||||
private _terminal: ITerminal,
|
||||
private _dimensions: IRenderDimensions
|
||||
private readonly _terminal: ITerminal,
|
||||
private readonly _renderService: IRenderService
|
||||
) {
|
||||
super();
|
||||
this._accessibilityTreeRoot = document.createElement('div');
|
||||
@@ -90,6 +90,7 @@ export class AccessibilityManager extends Disposable {
|
||||
this.register(this._terminal.onA11yTab(spaceCount => this._onTab(spaceCount)));
|
||||
this.register(this._terminal.onKey(e => this._onKey(e.key)));
|
||||
this.register(this._terminal.onBlur(() => this._clearLiveRegion()));
|
||||
this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions()));
|
||||
|
||||
this._screenDprMonitor = new ScreenDprMonitor();
|
||||
this.register(this._screenDprMonitor);
|
||||
@@ -271,7 +272,7 @@ export class AccessibilityManager extends Disposable {
|
||||
}
|
||||
|
||||
private _refreshRowsDimensions(): void {
|
||||
if (!this._dimensions.actualCellHeight) {
|
||||
if (!this._renderService.dimensions.actualCellHeight) {
|
||||
return;
|
||||
}
|
||||
if (this._rowElements.length !== this._terminal.rows) {
|
||||
@@ -282,13 +283,8 @@ export class AccessibilityManager extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public setDimensions(dimensions: IRenderDimensions): void {
|
||||
this._dimensions = dimensions;
|
||||
this._refreshRowsDimensions();
|
||||
}
|
||||
|
||||
private _refreshRowDimensions(element: HTMLElement): void {
|
||||
element.style.height = `${this._dimensions.actualCellHeight}px`;
|
||||
element.style.height = `${this._renderService.dimensions.actualCellHeight}px`;
|
||||
}
|
||||
|
||||
private _announceCharacters(): void {
|
||||
|
||||
+670
-174
File diff suppressed because it is too large
Load Diff
+387
-337
File diff suppressed because it is too large
Load Diff
+132
-9
@@ -4,19 +4,18 @@
|
||||
*/
|
||||
|
||||
import { assert, expect } from 'chai';
|
||||
import { Terminal } from './Terminal';
|
||||
import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test';
|
||||
import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from './TestUtils.test';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { wcwidth } from 'common/CharWidth';
|
||||
import { IBufferService } from 'common/services/Services';
|
||||
import { Linkifier } from 'browser/Linkifier';
|
||||
import { MockLogService } from 'common/TestUtils.test';
|
||||
import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types';
|
||||
|
||||
const INIT_COLS = 80;
|
||||
const INIT_ROWS = 24;
|
||||
|
||||
class TestTerminal extends Terminal {
|
||||
public keyDown(ev: any): boolean { return this._keyDown(ev); }
|
||||
public keyPress(ev: any): boolean { return this._keyPress(ev); }
|
||||
}
|
||||
|
||||
describe('Terminal', () => {
|
||||
let term: TestTerminal;
|
||||
const termOptions = {
|
||||
@@ -750,10 +749,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();
|
||||
}
|
||||
@@ -1019,4 +1022,124 @@ describe('Terminal', () => {
|
||||
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth
|
||||
});
|
||||
});
|
||||
|
||||
describe('Linkifier unicode handling', () => {
|
||||
let terminal: TestTerminal;
|
||||
let linkifier: TestLinkifier;
|
||||
let mouseZoneManager: TestMouseZoneManager;
|
||||
|
||||
// other than the tests above unicode testing needs the full terminal instance
|
||||
// to get the special handling of fullwidth, surrogate and combining chars in the input handler
|
||||
beforeEach(() => {
|
||||
terminal = new TestTerminal({ cols: 10, rows: 5 });
|
||||
linkifier = new TestLinkifier((terminal as any)._bufferService);
|
||||
mouseZoneManager = new TestMouseZoneManager();
|
||||
linkifier.attachToDom({} as any, mouseZoneManager);
|
||||
});
|
||||
|
||||
function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void {
|
||||
terminal.writeSync(rowText);
|
||||
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
|
||||
linkifier.linkifyRows();
|
||||
// Allow linkify to happen
|
||||
setTimeout(() => {
|
||||
assert.equal(mouseZoneManager.zones.length, links.length);
|
||||
links.forEach((l, i) => {
|
||||
assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1);
|
||||
assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1);
|
||||
assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1);
|
||||
assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1);
|
||||
});
|
||||
done();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
describe('unicode before the match', () => {
|
||||
it('combining - match within one line', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done);
|
||||
});
|
||||
it('combining - match over two lines', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done);
|
||||
});
|
||||
it('surrogate - match within one line', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done);
|
||||
});
|
||||
it('surrogate - match over two lines', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done);
|
||||
});
|
||||
it('combining surrogate - match within one line', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done);
|
||||
});
|
||||
it('combining surrogate - match over two lines', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done);
|
||||
});
|
||||
it('fullwidth - match within one line', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done);
|
||||
});
|
||||
it('fullwidth - match over two lines', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done);
|
||||
});
|
||||
it('combining fullwidth - match within one line', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done);
|
||||
});
|
||||
it('combining fullwidth - match over two lines', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done);
|
||||
});
|
||||
});
|
||||
describe('unicode within the match', () => {
|
||||
it('combining - match within one line', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('test cafe\u0301', /cafe\u0301/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done);
|
||||
});
|
||||
it('combining - match over two lines', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('testtest cafe\u0301', /cafe\u0301/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done);
|
||||
});
|
||||
it('surrogate - match within one line', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('test a𝄞b', /a𝄞b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done);
|
||||
});
|
||||
it('surrogate - match over two lines', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('testtest a𝄞b', /a𝄞b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done);
|
||||
});
|
||||
it('combining surrogate - match within one line', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('test a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done);
|
||||
});
|
||||
it('combining surrogate - match over two lines', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('testtest a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done);
|
||||
});
|
||||
it('fullwidth - match within one line', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('test a1b', /a1b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done);
|
||||
});
|
||||
it('fullwidth - match over two lines', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('testtest a1b', /a1b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done);
|
||||
});
|
||||
it('combining fullwidth - match within one line', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('test a¥\u0301b', /a¥\u0301b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done);
|
||||
});
|
||||
it('combining fullwidth - match over two lines', function(done: () => void): void {
|
||||
assertLinkifiesInTerminal('testtest a¥\u0301b', /a¥\u0301b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class TestLinkifier extends Linkifier {
|
||||
constructor(bufferService: IBufferService) {
|
||||
super(bufferService, new MockLogService());
|
||||
Linkifier._timeBeforeLatency = 0;
|
||||
}
|
||||
|
||||
public get linkMatchers(): IRegisteredLinkMatcher[] { return this._linkMatchers; }
|
||||
public linkifyRows(): void { super.linkifyRows(0, this._bufferService.buffer.lines.length - 1); }
|
||||
}
|
||||
|
||||
class TestMouseZoneManager implements IMouseZoneManager {
|
||||
dispose(): void {
|
||||
}
|
||||
public clears: number = 0;
|
||||
public zones: IMouseZone[] = [];
|
||||
add(zone: IMouseZone): void {
|
||||
this.zones.push(zone);
|
||||
}
|
||||
clearAll(): void {
|
||||
this.clears++;
|
||||
}
|
||||
}
|
||||
|
||||
+65
-153
@@ -21,21 +21,21 @@
|
||||
* http://linux.die.net/man/7/urxvt
|
||||
*/
|
||||
|
||||
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, IMouseZoneManager } from './Types';
|
||||
import { IInputHandlingTerminal, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, CustomKeyEventHandler } from './Types';
|
||||
import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types';
|
||||
import { CompositionHelper } from './CompositionHelper';
|
||||
import { Viewport } from './Viewport';
|
||||
import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './Clipboard';
|
||||
import { CompositionHelper } from 'browser/input/CompositionHelper';
|
||||
import { Viewport } from 'browser/Viewport';
|
||||
import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from 'browser/Clipboard';
|
||||
import { C0 } from 'common/data/EscapeSequences';
|
||||
import { InputHandler } from './InputHandler';
|
||||
import { Renderer } from './renderer/Renderer';
|
||||
import { Linkifier } from './Linkifier';
|
||||
import { SelectionService } from './browser/services/SelectionService';
|
||||
import { Linkifier } from 'browser/Linkifier';
|
||||
import { SelectionService } from 'browser/services/SelectionService';
|
||||
import * as Browser from 'common/Platform';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import * as Strings from './browser/LocalizableStrings';
|
||||
import * as Strings from 'browser/LocalizableStrings';
|
||||
import { SoundService } from 'browser/services/SoundService';
|
||||
import { MouseZoneManager } from './MouseZoneManager';
|
||||
import { MouseZoneManager } from 'browser/MouseZoneManager';
|
||||
import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';
|
||||
import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache';
|
||||
@@ -47,7 +47,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { applyWindowsMode } from './WindowsMode';
|
||||
import { ColorManager } from 'browser/ColorManager';
|
||||
import { RenderService } from 'browser/services/RenderService';
|
||||
import { IOptionsService, IBufferService, ICoreService } from 'common/services/Services';
|
||||
import { IOptionsService, IBufferService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services';
|
||||
import { OptionsService } from 'common/services/OptionsService';
|
||||
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService } from 'browser/services/Services';
|
||||
import { CharSizeService } from 'browser/services/CharSizeService';
|
||||
@@ -58,6 +58,10 @@ import { Attributes } from 'common/buffer/Constants';
|
||||
import { MouseService } from 'browser/services/MouseService';
|
||||
import { IParams } from 'common/parser/Types';
|
||||
import { CoreService } from 'common/services/CoreService';
|
||||
import { LogService } from 'common/services/LogService';
|
||||
import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport } from 'browser/Types';
|
||||
import { DirtyRowService } from 'common/services/DirtyRowService';
|
||||
import { InstantiationService } from 'common/services/InstantiationService';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document = (typeof window !== 'undefined') ? window.document : null;
|
||||
@@ -87,7 +91,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
* The HTMLElement that the terminal is created in, set by Terminal.open.
|
||||
*/
|
||||
private _parent: HTMLElement;
|
||||
private _context: Window;
|
||||
private _document: Document;
|
||||
private _viewportScrollArea: HTMLElement;
|
||||
private _viewportElement: HTMLElement;
|
||||
@@ -110,6 +113,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
// common services
|
||||
private _bufferService: IBufferService;
|
||||
private _coreService: ICoreService;
|
||||
private _dirtyRowService: IDirtyRowService;
|
||||
private _instantiationService: IInstantiationService;
|
||||
private _logService: ILogService;
|
||||
public optionsService: IOptionsService;
|
||||
|
||||
// browser services
|
||||
@@ -146,8 +152,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
public urxvtMouse: boolean;
|
||||
|
||||
// misc
|
||||
private _refreshStart: number;
|
||||
private _refreshEnd: number;
|
||||
public savedCols: number;
|
||||
|
||||
public curAttrData: IAttributeData;
|
||||
@@ -237,10 +241,18 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
super();
|
||||
|
||||
// Setup and initialize common services
|
||||
this._instantiationService = new InstantiationService();
|
||||
this.optionsService = new OptionsService(options);
|
||||
this._bufferService = new BufferService(this.optionsService);
|
||||
this._coreService = new CoreService(() => this.scrollToBottom(), this._bufferService, this.optionsService);
|
||||
this._instantiationService.setService(IOptionsService, this.optionsService);
|
||||
this._bufferService = this._instantiationService.createInstance(BufferService);
|
||||
this._instantiationService.setService(IBufferService, this._bufferService);
|
||||
this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom());
|
||||
this._instantiationService.setService(ICoreService, this._coreService);
|
||||
this._coreService.onData(e => this._onData.fire(e));
|
||||
this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
|
||||
this._instantiationService.setService(IDirtyRowService, this._dirtyRowService);
|
||||
this._logService = this._instantiationService.createInstance(LogService);
|
||||
this._instantiationService.setService(ILogService, this._logService);
|
||||
|
||||
this._setupOptionsListeners();
|
||||
this._setup();
|
||||
@@ -297,14 +309,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._userScrolling = false;
|
||||
|
||||
// Register input handler and refire/handle events
|
||||
this._inputHandler = new InputHandler(this, this._coreService);
|
||||
this._inputHandler = new InputHandler(this, this._bufferService, this._coreService, this._dirtyRowService, this._logService, this.optionsService);
|
||||
this._inputHandler.onCursorMove(() => this._onCursorMove.fire());
|
||||
this._inputHandler.onLineFeed(() => this._onLineFeed.fire());
|
||||
this.register(this._inputHandler);
|
||||
|
||||
this._selectionService = this._selectionService || null;
|
||||
this.linkifier = this.linkifier || new Linkifier(this);
|
||||
this._mouseZoneManager = this._mouseZoneManager || null;
|
||||
this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService);
|
||||
|
||||
if (this.options.windowsMode) {
|
||||
this._windowsMode = applyWindowsMode(this);
|
||||
@@ -384,7 +394,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
case 'screenReaderMode':
|
||||
if (this.optionsService.options.screenReaderMode) {
|
||||
if (!this._accessibilityManager && this._renderService) {
|
||||
this._accessibilityManager = new AccessibilityManager(this, this._renderService.dimensions);
|
||||
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
|
||||
}
|
||||
} else {
|
||||
if (this._accessibilityManager) {
|
||||
@@ -534,8 +544,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
throw new Error('Terminal requires a parent element.');
|
||||
}
|
||||
|
||||
// Grab global elements
|
||||
this._context = this._parent.ownerDocument.defaultView;
|
||||
this._document = this._parent.ownerDocument;
|
||||
|
||||
// Create main element container
|
||||
@@ -577,11 +585,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur()));
|
||||
this._helperContainer.appendChild(this.textarea);
|
||||
|
||||
this._charSizeService = new CharSizeService(this._document, this._helperContainer, this.optionsService);
|
||||
this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
|
||||
this._instantiationService.setService(ICharSizeService, this._charSizeService);
|
||||
|
||||
this._compositionView = document.createElement('div');
|
||||
this._compositionView.classList.add('composition-view');
|
||||
this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this, this._charSizeService, this._coreService);
|
||||
this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView);
|
||||
this._helperContainer.appendChild(this._compositionView);
|
||||
|
||||
// Performance: Add viewport and helper elements from the fragment
|
||||
@@ -593,19 +602,21 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._colorManager.setTheme(this._theme);
|
||||
|
||||
const renderer = this._createRenderer();
|
||||
this._renderService = new RenderService(renderer, this.rows, this.screenElement, this.optionsService, this._charSizeService);
|
||||
this._renderService = this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement);
|
||||
this._instantiationService.setService(IRenderService, this._renderService);
|
||||
this._renderService.onRender(e => this._onRender.fire(e));
|
||||
this.onResize(e => this._renderService.resize(e.cols, e.rows));
|
||||
|
||||
this._soundService = new SoundService(this.optionsService);
|
||||
this._mouseService = new MouseService(this._renderService, this._charSizeService);
|
||||
this._soundService = this._instantiationService.createInstance(SoundService);
|
||||
this._instantiationService.setService(ISoundService, this._soundService);
|
||||
this._mouseService = this._instantiationService.createInstance(MouseService);
|
||||
this._instantiationService.setService(IMouseService, this._mouseService);
|
||||
|
||||
this._mouseZoneManager = new MouseZoneManager(this, this._mouseService);
|
||||
this.register(this._mouseZoneManager);
|
||||
this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
|
||||
this.linkifier.attachToDom(this._mouseZoneManager);
|
||||
|
||||
this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this._renderService.dimensions, this._charSizeService);
|
||||
this.viewport = this._instantiationService.createInstance(Viewport,
|
||||
(amount: number, suppressEvent: boolean) => this.scrollLines(amount, suppressEvent),
|
||||
this._viewportElement,
|
||||
this._viewportScrollArea
|
||||
);
|
||||
this.viewport.onThemeChange(this._colorManager.colors);
|
||||
this.register(this.viewport);
|
||||
|
||||
@@ -615,11 +626,11 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this.register(this.onFocus(() => this._renderService.onFocus()));
|
||||
this.register(this._renderService.onDimensionsChange(() => this.viewport.syncScrollArea()));
|
||||
|
||||
this._selectionService = new SelectionService(
|
||||
this._selectionService = this._instantiationService.createInstance(SelectionService,
|
||||
(amount: number, suppressEvent: boolean) => this.scrollLines(amount, suppressEvent),
|
||||
this.element, this.screenElement, this._charSizeService, this._bufferService, this._coreService,
|
||||
this._mouseService, this.optionsService
|
||||
);
|
||||
this.element,
|
||||
this.screenElement);
|
||||
this._instantiationService.setService(ISelectionService, this._selectionService);
|
||||
this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
|
||||
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService.onMouseDown(e)));
|
||||
this.register(this._selectionService.onRedrawRequest(e => this._renderService.onSelectionChanged(e.start, e.end, e.columnSelectMode)));
|
||||
@@ -637,6 +648,11 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
}));
|
||||
this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService.refresh()));
|
||||
|
||||
this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement);
|
||||
this.register(this._mouseZoneManager);
|
||||
this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
|
||||
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
|
||||
|
||||
// apply mouse event classes set by escape codes before terminal was attached
|
||||
this.element.classList.toggle('enable-mouse-events', this.mouseEvents);
|
||||
if (this.mouseEvents) {
|
||||
@@ -644,12 +660,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
} else {
|
||||
this._selectionService.enable();
|
||||
}
|
||||
this._inputHandler.setBrowserServices(this._selectionService);
|
||||
|
||||
if (this.options.screenReaderMode) {
|
||||
// Note that this must be done *after* the renderer is created in order to
|
||||
// ensure the correct order of the dprchange event
|
||||
this._accessibilityManager = new AccessibilityManager(this, this._renderService.dimensions);
|
||||
this._accessibilityManager.register(this._renderService.onDimensionsChange(e => this._accessibilityManager.setDimensions(e)));
|
||||
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
|
||||
}
|
||||
|
||||
// Measure the character size
|
||||
@@ -669,8 +685,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
|
||||
private _createRenderer(): IRenderer {
|
||||
switch (this.options.rendererType) {
|
||||
case 'canvas': return new Renderer(this, this._colorManager.colors, this._charSizeService); break;
|
||||
case 'dom': return new DomRenderer(this, this._colorManager.colors, this._charSizeService, this.optionsService); break;
|
||||
case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService);
|
||||
case 'dom': return new DomRenderer(this, this._colorManager.colors, this._charSizeService, this.optionsService);
|
||||
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
|
||||
}
|
||||
}
|
||||
@@ -1129,8 +1145,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
}
|
||||
|
||||
// Flag rows that need updating
|
||||
this.updateRange(this.buffer.scrollTop);
|
||||
this.updateRange(this.buffer.scrollBottom);
|
||||
this._dirtyRowService.markRangeDirty(this.buffer.scrollTop, this.buffer.scrollBottom);
|
||||
|
||||
this._onScroll.fire(this.buffer.ydisp);
|
||||
}
|
||||
@@ -1251,19 +1266,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._xoffSentToCatchUp = false;
|
||||
}
|
||||
|
||||
this._refreshStart = this.buffer.y;
|
||||
this._refreshEnd = this.buffer.y;
|
||||
|
||||
// HACK: Set the parser state based on it's state at the time of return.
|
||||
// This works around the bug #662 which saw the parser state reset in the
|
||||
// middle of parsing escape sequence in two chunks. For some reason the
|
||||
// state of the parser resets to 0 after exiting parser.parse. This change
|
||||
// just sets the state back based on the correct return statement.
|
||||
|
||||
this._inputHandler.parseUtf8(data);
|
||||
|
||||
this.updateRange(this.buffer.y);
|
||||
this.refresh(this._refreshStart, this._refreshEnd);
|
||||
this.refresh(this._dirtyRowService.start, this._dirtyRowService.end);
|
||||
|
||||
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
|
||||
break;
|
||||
@@ -1338,19 +1343,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._xoffSentToCatchUp = false;
|
||||
}
|
||||
|
||||
this._refreshStart = this.buffer.y;
|
||||
this._refreshEnd = this.buffer.y;
|
||||
|
||||
// HACK: Set the parser state based on it's state at the time of return.
|
||||
// This works around the bug #662 which saw the parser state reset in the
|
||||
// middle of parsing escape sequence in two chunks. For some reason the
|
||||
// state of the parser resets to 0 after exiting parser.parse. This change
|
||||
// just sets the state back based on the correct return statement.
|
||||
|
||||
this._inputHandler.parse(data);
|
||||
|
||||
this.updateRange(this.buffer.y);
|
||||
this.refresh(this._refreshStart, this._refreshEnd);
|
||||
this.refresh(this._dirtyRowService.start, this._dirtyRowService.end);
|
||||
|
||||
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
|
||||
break;
|
||||
@@ -1675,24 +1670,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the current state to the console.
|
||||
*/
|
||||
public log(text: string, data?: any): void {
|
||||
if (!this.options.debug) return;
|
||||
if (!this._context.console || !this._context.console.log) return;
|
||||
this._context.console.log(text, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the current state as error to the console.
|
||||
*/
|
||||
public error(text: string, data?: any): void {
|
||||
if (!this.options.debug) return;
|
||||
if (!this._context.console || !this._context.console.error) return;
|
||||
this._context.console.error(text, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the terminal.
|
||||
*
|
||||
@@ -1728,29 +1705,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._onResize.fire({ cols: x, rows: y });
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the range of rows to refresh
|
||||
* @param y The number of rows to refresh next.
|
||||
*/
|
||||
public updateRange(y: number): void {
|
||||
if (y < this._refreshStart) this._refreshStart = y;
|
||||
if (y > this._refreshEnd) this._refreshEnd = y;
|
||||
// if (y > this.refreshEnd) {
|
||||
// this.refreshEnd = y;
|
||||
// if (y > this.rows - 1) {
|
||||
// this.refreshEnd = this.rows - 1;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the range of refreshing to the maximum value
|
||||
*/
|
||||
public maxRange(): void {
|
||||
this._refreshStart = 0;
|
||||
this._refreshEnd = this.rows - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the entire buffer, making the prompt line the new first line.
|
||||
*/
|
||||
@@ -1810,46 +1764,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 {
|
||||
/**
|
||||
@@ -1891,14 +1811,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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+6
-12
@@ -4,15 +4,15 @@
|
||||
*/
|
||||
|
||||
import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
|
||||
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions, ILinkifier, ILinkMatcherOptions } from './Types';
|
||||
import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } 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';
|
||||
import { Terminal } from './Terminal';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IColorManager, IColorSet } from 'browser/Types';
|
||||
import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport } from 'browser/Types';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { IParams } from 'common/parser/Types';
|
||||
@@ -23,6 +23,8 @@ export class TestTerminal extends Terminal {
|
||||
this.writeBuffer.push(data);
|
||||
this._innerWrite();
|
||||
}
|
||||
keyDown(ev: any): boolean { return this._keyDown(ev); }
|
||||
keyPress(ev: any): boolean { return this._keyPress(ev); }
|
||||
}
|
||||
|
||||
export class MockTerminal implements ITerminal {
|
||||
@@ -295,21 +297,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 +322,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
+2
-105
@@ -6,7 +6,7 @@
|
||||
import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm';
|
||||
import { ICharset, IAttributeData, CharData } from 'common/Types';
|
||||
import { IEvent, IEventEmitter } from 'common/EventEmitter';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport } from 'browser/Types';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
import { IParams } from 'common/parser/Types';
|
||||
@@ -15,9 +15,6 @@ export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
|
||||
|
||||
export type LineData = CharData[];
|
||||
|
||||
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
|
||||
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
|
||||
|
||||
/**
|
||||
* This interface encapsulates everything needed from the Terminal by the
|
||||
* InputHandler. This cleanly separates the large amount of methods needed by
|
||||
@@ -58,32 +55,16 @@ export interface IInputHandlingTerminal {
|
||||
|
||||
bell(): void;
|
||||
focus(): void;
|
||||
updateRange(y: number): void;
|
||||
scroll(isWrapped?: boolean): void;
|
||||
setgLevel(g: number): void;
|
||||
eraseAttrData(): IAttributeData;
|
||||
is(term: string): boolean;
|
||||
setgCharset(g: number, charset: ICharset): void;
|
||||
resize(x: number, y: number): void;
|
||||
log(text: string, data?: any): void;
|
||||
reset(): void;
|
||||
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 {
|
||||
scrollBarWidth: number;
|
||||
syncScrollArea(): void;
|
||||
getLinesScrolled(ev: WheelEvent): number;
|
||||
onWheel(ev: WheelEvent): void;
|
||||
onTouchStart(ev: TouchEvent): void;
|
||||
onTouchMove(ev: TouchEvent): void;
|
||||
onThemeChange(colors: IColorSet): void;
|
||||
}
|
||||
|
||||
export interface ICompositionHelper {
|
||||
@@ -169,27 +150,7 @@ export interface IInputHandler {
|
||||
ESC |
|
||||
ESC }
|
||||
ESC ~ */ setgLevel(level: number): void;
|
||||
}
|
||||
|
||||
export interface ILinkMatcher {
|
||||
id: number;
|
||||
regex: RegExp;
|
||||
handler: LinkMatcherHandler;
|
||||
hoverTooltipCallback?: LinkMatcherHandler;
|
||||
hoverLeaveCallback?: () => void;
|
||||
matchIndex?: number;
|
||||
validationCallback?: LinkMatcherValidationCallback;
|
||||
priority?: number;
|
||||
willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
|
||||
}
|
||||
|
||||
export interface ILinkifierEvent {
|
||||
x1: number;
|
||||
y1: number;
|
||||
x2: number;
|
||||
y2: number;
|
||||
cols: number;
|
||||
fg: number;
|
||||
/** ESC # 8 */ screenAlignmentPattern(): void;
|
||||
}
|
||||
|
||||
export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
|
||||
@@ -214,7 +175,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc
|
||||
|
||||
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
|
||||
cancel(ev: Event, force?: boolean): boolean | void;
|
||||
log(text: string): void;
|
||||
showCursor(): void;
|
||||
}
|
||||
|
||||
@@ -284,58 +244,12 @@ export interface ITerminalOptions extends IPublicTerminalOptions {
|
||||
[key: string]: any;
|
||||
cancelEvents?: boolean;
|
||||
convertEol?: boolean;
|
||||
debug?: boolean;
|
||||
handler?: (data: string) => void;
|
||||
screenKeys?: boolean;
|
||||
termName?: string;
|
||||
useFlowControl?: boolean;
|
||||
}
|
||||
|
||||
export interface ILinkifier {
|
||||
onLinkHover: IEvent<ILinkifierEvent>;
|
||||
onLinkLeave: IEvent<ILinkifierEvent>;
|
||||
onLinkTooltip: IEvent<ILinkifierEvent>;
|
||||
|
||||
attachToDom(mouseZoneManager: IMouseZoneManager): void;
|
||||
linkifyRows(start: number, end: number): void;
|
||||
registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number;
|
||||
deregisterLinkMatcher(matcherId: number): boolean;
|
||||
}
|
||||
|
||||
export interface ILinkMatcherOptions {
|
||||
/**
|
||||
* The index of the link from the regex.match(text) call. This defaults to 0
|
||||
* (for regular expressions without capture groups).
|
||||
*/
|
||||
matchIndex?: number;
|
||||
/**
|
||||
* A callback that validates an individual link, returning true if valid and
|
||||
* false if invalid.
|
||||
*/
|
||||
validationCallback?: LinkMatcherValidationCallback;
|
||||
/**
|
||||
* A callback that fires when the mouse hovers over a link.
|
||||
*/
|
||||
tooltipCallback?: LinkMatcherHandler;
|
||||
/**
|
||||
* A callback that fires when the mouse leaves a link that was hovered.
|
||||
*/
|
||||
leaveCallback?: () => void;
|
||||
/**
|
||||
* The priority of the link matcher, this defines the order in which the link
|
||||
* matcher is evaluated relative to others, from highest to lowest. The
|
||||
* default value is 0.
|
||||
*/
|
||||
priority?: number;
|
||||
/**
|
||||
* A callback that fires when the mousedown and click events occur that
|
||||
* determines whether a link will be activated upon click. This enables
|
||||
* only activating a link when a certain modifier is held down, if not the
|
||||
* mouse event will continue propagation (eg. double click to select word).
|
||||
*/
|
||||
willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
|
||||
}
|
||||
|
||||
export interface IBrowser {
|
||||
isNode: boolean;
|
||||
userAgent: string;
|
||||
@@ -346,20 +260,3 @@ export interface IBrowser {
|
||||
isIphone: boolean;
|
||||
isWindows: boolean;
|
||||
}
|
||||
|
||||
export interface IMouseZoneManager extends IDisposable {
|
||||
add(zone: IMouseZone): void;
|
||||
clearAll(start?: number, end?: number): void;
|
||||
}
|
||||
|
||||
export interface IMouseZone {
|
||||
x1: number;
|
||||
x2: number;
|
||||
y1: number;
|
||||
y2: number;
|
||||
clickCallback: (e: MouseEvent) => any;
|
||||
hoverCallback: (e: MouseEvent) => any | undefined;
|
||||
tooltipCallback: (e: MouseEvent) => any | undefined;
|
||||
leaveCallback: () => any | undefined;
|
||||
willLinkActivate: (e: MouseEvent) => boolean;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import * as Clipboard from './Clipboard';
|
||||
import * as Clipboard from 'browser/Clipboard';
|
||||
|
||||
describe('evaluatePastedTextProcessing', () => {
|
||||
it('should replace carriage return and/or line feed with carriage return', () => {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user