Merge branch 'master' into left_right_scroll

This commit is contained in:
Jörg Breitbart
2019-08-17 17:57:31 +02:00
33 changed files with 986 additions and 767 deletions
+6 -3
View File
@@ -343,9 +343,12 @@ export class SearchAddon implements ITerminalAddon {
return false;
}
terminal.select(result.col, result.row, result.term.length);
let scroll = result.row - terminal.buffer.viewportY;
scroll = scroll - Math.floor(terminal.rows / 2);
terminal.scrollLines(scroll);
// If it is not in the viewport then we scroll else it just gets selected
if (result.row > (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) {
let scroll = result.row - terminal.buffer.viewportY;
scroll = scroll - Math.floor(terminal.rows / 2);
terminal.scrollLines(scroll);
}
return true;
}
}
-3
View File
@@ -20,9 +20,6 @@ startServer();
* For production builds see `webpack.config.js` in the root directory. If that is built the demo
* can use that by switching out which `Terminal` is imported in `client.ts`, this is useful for
* validating that the packaged version works correctly.
*
* The addons are not webpacked right now and are built directly to `lib/` via `tsc` as they are
* the legacy format (`applyAddon`) and will be removed soon anyway.
*/
const clientConfig = {
entry: path.resolve(__dirname, 'client.ts'),
@@ -27,5 +27,5 @@ r
s
t
uvwxyz


The end.
@@ -1,25 +1,25 @@
1
2
6
7
8
9 ABC
6
7
8
9 ABC
DEF
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
yz qrstu vwx
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
yz vwx
The end.
@@ -1,3 +1,3 @@
1D2D3D4D5D6D7D8D9ABCDEFaDbDcDdDeDfDgDhDiDjDkDlDmDnDoDpDqDrDsDtDuvwxyz


The end.
@@ -1,4 +1,3 @@
2
6
7
8
@@ -21,5 +20,6 @@ DEF
o
p
q
The end. rstu vwx
yz rstu vwx
The end.
@@ -1,4 +1,3 @@
6 C
8 ^^^^
9 vvvv DL on line 11, expected: ACD_
10 A
@@ -13,14 +12,14 @@
19 vvvv IL on line 21, expected: A_
20 A
22 ^^^^
24 A
25 B
27 vvvv DL on line 28, expected: B_
23 vvvv IL on line 24, expected: _A
25 B
26 ^^^^
28 A
29 B
30 ^^^^
31
32
32
@@ -0,0 +1,24 @@
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x12
@@ -0,0 +1,25 @@
a
b
c
d
e
f
g
h
i
2
k
l
m
n
o
p
q
r
1
t
u
v
w
x
@@ -0,0 +1,24 @@
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x1
@@ -0,0 +1,25 @@
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
1
+36 -16
View File
@@ -21,8 +21,8 @@ import { AttributeData } from 'common/buffer/AttributeData';
import { IAttributeData, IDisposable } from 'common/Types';
import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService } from 'common/services/Services';
import { ISelectionService } from 'browser/services/Services';
import { OscHandlerFactory } from 'common/parser/OscParser';
import { DcsHandlerFactory } from 'common/parser/DcsParser';
import { OscHandler } from 'common/parser/OscParser';
import { DcsHandler } from 'common/parser/DcsParser';
/**
* Map collect to glevel. Used in `selectCharset`.
@@ -245,10 +245,10 @@ export class InputHandler extends Disposable implements IInputHandler {
* OSC handler
*/
// 0 - icon name + title
this._parser.setOscHandler(0, new OscHandlerFactory((data: string) => this.setTitle(data)));
this._parser.setOscHandler(0, new OscHandler((data: string) => this.setTitle(data)));
// 1 - icon name
// 2 - title
this._parser.setOscHandler(2, new OscHandlerFactory((data: string) => this.setTitle(data)));
this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data)));
// 3 - set property X in the form "prop=value"
// 4 - Change Color Number
// 5 - Change Special Color Number
@@ -433,10 +433,13 @@ export class InputHandler extends Disposable implements IInputHandler {
if (wraparoundMode) {
buffer.x = 0;
buffer.y++;
if (buffer.y > buffer.scrollBottom) {
if (buffer.y === buffer.scrollBottom + 1) {
buffer.y--;
this._terminal.scroll(true);
} else {
if (buffer.y >= this._bufferService.rows) {
buffer.y = this._bufferService.rows - 1;
}
// The line already exists (eg. the initial viewport), mark it as a
// wrapped line
buffer.lines.get(buffer.y).isWrapped = true;
@@ -506,7 +509,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* Forward addDcsHandler from parser.
*/
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
return this._parser.addDcsHandler(id, new DcsHandlerFactory(callback));
return this._parser.addDcsHandler(id, new DcsHandler(callback));
}
/**
@@ -520,7 +523,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* Forward addOscHandler from parser.
*/
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this._parser.addOscHandler(ident, new OscHandlerFactory(callback));
return this._parser.addOscHandler(ident, new OscHandler(callback));
}
/**
@@ -543,9 +546,11 @@ export class InputHandler extends Disposable implements IInputHandler {
buffer.x = 0;
}
buffer.y++;
if (buffer.y > buffer.scrollBottom) {
if (buffer.y === buffer.scrollBottom + 1) {
buffer.y--;
this._terminal.scroll();
} else if (buffer.y >= this._bufferService.rows) {
buffer.y = this._bufferService.rows - 1;
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (buffer.x >= this._bufferService.cols) {
@@ -646,7 +651,13 @@ export class InputHandler extends Disposable implements IInputHandler {
* Cursor Up Ps Times (default = 1) (CUU).
*/
public cursorUp(params: IParams): void {
this._moveCursor(0, -(params.params[0] || 1));
// stop at scrollTop
const diffToTop = this._bufferService.buffer.y - this._bufferService.buffer.scrollTop;
if (diffToTop >= 0) {
this._moveCursor(0, -Math.min(diffToTop, params.params[0] || 1));
} else {
this._moveCursor(0, -(params.params[0] || 1));
}
}
/**
@@ -654,7 +665,13 @@ export class InputHandler extends Disposable implements IInputHandler {
* Cursor Down Ps Times (default = 1) (CUD).
*/
public cursorDown(params: IParams): void {
this._moveCursor(0, params.params[0] || 1);
// stop at scrollBottom
const diffToBottom = this._bufferService.buffer.scrollBottom - this._bufferService.buffer.y;
if (diffToBottom >= 0) {
this._moveCursor(0, Math.min(diffToBottom, params.params[0] || 1));
} else {
this._moveCursor(0, params.params[0] || 1);
}
}
/**
@@ -679,7 +696,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* Other than cursorDown (CUD) also set the cursor to first column.
*/
public cursorNextLine(params: IParams): void {
this._moveCursor(0, params.params[0] || 1);
this.cursorDown(params);
this._bufferService.buffer.x = 0;
}
@@ -689,7 +706,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* Other than cursorUp (CUU) also set the cursor to first column.
*/
public cursorPrecedingLine(params: IParams): void {
this._moveCursor(0, -(params.params[0] || 1));
this.cursorUp(params);
this._bufferService.buffer.x = 0;
}
@@ -1031,7 +1048,7 @@ export class InputHandler extends Disposable implements IInputHandler {
while (param--) {
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1);
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(this._terminal.eraseAttrData()));
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
}
@@ -1633,7 +1650,6 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
/**
* Helper to extract and apply color params/subparams.
* Returns advance for params index.
@@ -1906,6 +1922,7 @@ export class InputHandler extends Disposable implements IInputHandler {
break;
}
}
public deviceStatusPrivate(params: IParams): void {
// modern xterm doesnt seem to
// respond to any of these except ?6, 6, and 5
@@ -2134,10 +2151,13 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public index(): void {
this._restrictCursor();
const buffer = this._bufferService.buffer;
this._bufferService.buffer.y++;
if (this._bufferService.buffer.y > this._bufferService.buffer.scrollBottom) {
this._bufferService.buffer.y--;
if (buffer.y === buffer.scrollBottom + 1) {
buffer.y--;
this._terminal.scroll();
} else if (buffer.y >= this._bufferService.rows) {
buffer.y = this._bufferService.rows - 1;
}
this._restrictCursor();
}
+235
View File
@@ -1123,6 +1123,241 @@ describe('Terminal', () => {
});
});
});
describe('Buffer.stringIndexToBufferIndex', () => {
let terminal: TestTerminal;
beforeEach(() => {
terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5});
});
it('multiline ascii', () => {
const input = 'This is ASCII text spanning multiple lines.';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 0; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
}
});
it('combining e\u0301 in a sentence', () => {
const input = 'Sitting in the cafe\u0301 drinking coffee.';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 0; i < 19; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
}
// string index 18 & 19 point to combining char e\u0301 ---> same buffer Index
assert.deepEqual(
terminal.buffer.stringIndexToBufferIndex(0, 18),
terminal.buffer.stringIndexToBufferIndex(0, 19));
// after the combining char every string index has an offset of -1
for (let i = 19; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex);
}
});
it('multiline combining e\u0301', () => {
const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
// every buffer cell index contains 2 string indices
for (let i = 0; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex);
}
});
it('surrogate char in a sentence', () => {
const input = 'The 𝄞 is a clef widely used in modern notation.';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 0; i < 5; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
}
// string index 4 & 5 point to surrogate char 𝄞 ---> same buffer Index
assert.deepEqual(
terminal.buffer.stringIndexToBufferIndex(0, 4),
terminal.buffer.stringIndexToBufferIndex(0, 5));
// after the combining char every string index has an offset of -1
for (let i = 5; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex);
}
});
it('multiline surrogate char', () => {
const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
// every buffer cell index contains 2 string indices
for (let i = 0; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex);
}
});
it('surrogate char with combining', () => {
// eye of Ra with acute accent - string length of 3
const input = '𓂀\u0301 - the eye hiroglyph with an acute accent.';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
// index 0..2 should map to 0
assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 1));
assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 2));
for (let i = 2; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([((i - 2) / terminal.cols) | 0, (i - 2) % terminal.cols], bufferIndex);
}
});
it('multiline surrogate with combining', () => {
const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
// every buffer cell index contains 3 string indices
for (let i = 0; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([(((i / 3) | 0) / terminal.cols) | 0, ((i / 3) | 0) % terminal.cols], bufferIndex);
}
});
it('fullwidth chars', () => {
const input = 'These 123 are some fat numbers.';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 0; i < 6; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
}
// string index 6, 7, 8 take 2 cells
assert.deepEqual([0, 8], terminal.buffer.stringIndexToBufferIndex(0, 7));
assert.deepEqual([1, 0], terminal.buffer.stringIndexToBufferIndex(0, 8));
// rest of the string has offset of +3
for (let i = 9; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([((i + 3) / terminal.cols) | 0, (i + 3) % terminal.cols], bufferIndex);
}
});
it('multiline fullwidth chars', () => {
const input = '12345678901234567890';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 9; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
assert.deepEqual([((i << 1) / terminal.cols) | 0, (i << 1) % terminal.cols], bufferIndex);
}
});
it('fullwidth combining with emoji - match emoji cell', () => {
const input = 'Lots of ¥\u0301 make me 😃.';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
const stringIndex = s.match(/😃/).index;
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex);
assert(terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars(), '😃');
});
it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => {
const input = 'a12345678901234567890';
// the 'a' at the beginning moves all fullwidth chars one to the right
// now the end of the line contains a dangling empty cell since
// the next fullwidth char has to wrap early
// the dangling last cell is wrongly added in the string
// --> fixable after resolving #1685
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 10; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
const j = (i - 0) << 1;
assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex);
}
});
it('test fully wrapped buffer up to last char', () => {
const input = Array(6).join('1234567890');
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 0; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars());
}
});
it('test fully wrapped buffer up to last char with full width odd', () => {
const input = 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301'
+ 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 0; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
assert.equal(
(!(i % 3))
? input[i]
: (i % 3 === 1)
? input.substr(i, 2)
: input.substr(i - 1, 2),
terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars());
}
});
it('should handle \t in lines correctly', () => {
const input = '\thttps://google.de';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(s, Array(terminal.optionsService.options.tabStopWidth + 1).join(' ') + 'https://google.de');
});
});
describe('BufferStringIterator', function(): void {
it('iterator does not overflow buffer limits', function(): void {
const terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5});
const data = [
'aaaaaaaaaa',
'aaaaaaaaa\n',
'aaaaaaaaaa',
'aaaaaaaaa\n',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaa\n',
'aaaaaaaaaa',
'aaaaaaaaaa'
];
terminal.writeSync(data.join(''));
// brute force test with insane values
expect(() => {
for (let overscan = 0; overscan < 20; ++overscan) {
for (let start = -10; start < 20; ++start) {
for (let end = -10; end < 20; ++end) {
const it = terminal.buffer.iterator(false, start, end, overscan, overscan);
while (it.hasNext()) {
it.next();
}
}
}
}
}).to.not.throw();
});
});
});
class TestLinkifier extends Linkifier {
+5 -2
View File
@@ -394,6 +394,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
case 'rendererType':
if (this._renderService) {
this._renderService.setRenderer(this._createRenderer());
this._renderService.onResize(this.cols, this.rows);
}
break;
case 'scrollback':
@@ -643,7 +644,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
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)));
this.register(this._selectionService.onLinuxMouseSelection(text => {
// If there's a new selection, put it into the textarea, focus and select it
@@ -664,10 +664,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
// This event listener must be registered aftre MouseZoneManager is created
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService.onMouseDown(e)));
// apply mouse event classes set by escape codes before terminal was attached
this.element.classList.toggle('enable-mouse-events', this.mouseEvents);
if (this.mouseEvents) {
this._selectionService.disable();
this.element.classList.add('enable-mouse-events');
} else {
this._selectionService.enable();
}
+1 -7
View File
@@ -17,12 +17,6 @@ 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',
@@ -42,7 +36,7 @@ const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slic
describe('Escape Sequence Files', function(): void {
this.timeout(20000);
this.timeout(1000);
let ptyTerm: any;
let slaveEnd: any;
+1 -1
View File
@@ -116,7 +116,7 @@ export interface IInputHandler {
/** CSI a */ hPositionRelative(params: IParams): void;
/** CSI b */ repeatPrecedingCharacter(params: IParams): void;
/** CSI c */ sendDeviceAttributesPrimary(params: IParams): void;
sendDeviceAttributesSecondary(params: IParams): void;
/** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void;
/** CSI d */ linePosAbsolute(params: IParams): void;
/** CSI e */ vPositionRelative(params: IParams): void;
/** CSI f */ hVPosition(params: IParams): void;
+1 -1
View File
@@ -108,7 +108,7 @@ export class RenderService extends Disposable implements IRenderService {
}
public setRenderer(renderer: IRenderer): void {
// TODO: RenderCoordinator should be the only one to dispose the renderer
// TODO: RenderService should be the only one to dispose the renderer
this._renderer.dispose();
this._renderer = renderer;
this.refreshRows(0, this._rowCount - 1);
+3 -2
View File
@@ -563,9 +563,10 @@ export class SelectionService implements ISelectionService {
// to be sent to the pty.
event.stopImmediatePropagation();
// Something went wrong
// Do nothing if there is no selection start, this can happen if the first
// click in the terminal is an incremental click
if (!this._model.selectionStart) {
throw new Error('Selection start position was not set before mousemove event');
return;
}
// Record the previous position so we know whether to redraw the selection
-233
View File
@@ -1163,237 +1163,4 @@ describe('Buffer', () => {
assert.equal(str3, '😁a');
});
});
// describe('stringIndexToBufferIndex', () => {
// let terminal: TestTerminal;
// beforeEach(() => {
// terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5});
// });
// it('multiline ascii', () => {
// const input = 'This is ASCII text spanning multiple lines.';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// for (let i = 0; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
// }
// });
// it('combining e\u0301 in a sentence', () => {
// const input = 'Sitting in the cafe\u0301 drinking coffee.';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// for (let i = 0; i < 19; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
// }
// // string index 18 & 19 point to combining char e\u0301 ---> same buffer Index
// assert.deepEqual(
// terminal.buffer.stringIndexToBufferIndex(0, 18),
// terminal.buffer.stringIndexToBufferIndex(0, 19));
// // after the combining char every string index has an offset of -1
// for (let i = 19; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex);
// }
// });
// it('multiline combining e\u0301', () => {
// const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// // every buffer cell index contains 2 string indices
// for (let i = 0; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex);
// }
// });
// it('surrogate char in a sentence', () => {
// const input = 'The 𝄞 is a clef widely used in modern notation.';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// for (let i = 0; i < 5; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
// }
// // string index 4 & 5 point to surrogate char 𝄞 ---> same buffer Index
// assert.deepEqual(
// terminal.buffer.stringIndexToBufferIndex(0, 4),
// terminal.buffer.stringIndexToBufferIndex(0, 5));
// // after the combining char every string index has an offset of -1
// for (let i = 5; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex);
// }
// });
// it('multiline surrogate char', () => {
// const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// // every buffer cell index contains 2 string indices
// for (let i = 0; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex);
// }
// });
// it('surrogate char with combining', () => {
// // eye of Ra with acute accent - string length of 3
// const input = '𓂀\u0301 - the eye hiroglyph with an acute accent.';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// // index 0..2 should map to 0
// assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 1));
// assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 2));
// for (let i = 2; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([((i - 2) / terminal.cols) | 0, (i - 2) % terminal.cols], bufferIndex);
// }
// });
// it('multiline surrogate with combining', () => {
// const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// // every buffer cell index contains 3 string indices
// for (let i = 0; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([(((i / 3) | 0) / terminal.cols) | 0, ((i / 3) | 0) % terminal.cols], bufferIndex);
// }
// });
// it('fullwidth chars', () => {
// const input = 'These 123 are some fat numbers.';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// for (let i = 0; i < 6; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
// }
// // string index 6, 7, 8 take 2 cells
// assert.deepEqual([0, 8], terminal.buffer.stringIndexToBufferIndex(0, 7));
// assert.deepEqual([1, 0], terminal.buffer.stringIndexToBufferIndex(0, 8));
// // rest of the string has offset of +3
// for (let i = 9; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([((i + 3) / terminal.cols) | 0, (i + 3) % terminal.cols], bufferIndex);
// }
// });
// it('multiline fullwidth chars', () => {
// const input = '12345678901234567890';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// for (let i = 9; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
// assert.deepEqual([((i << 1) / terminal.cols) | 0, (i << 1) % terminal.cols], bufferIndex);
// }
// });
// it('fullwidth combining with emoji - match emoji cell', () => {
// const input = 'Lots of ¥\u0301 make me 😃.';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// const stringIndex = s.match(/😃/).index;
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex);
// assert(terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars(), '😃');
// });
// it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => {
// const input = 'a12345678901234567890';
// // the 'a' at the beginning moves all fullwidth chars one to the right
// // now the end of the line contains a dangling empty cell since
// // the next fullwidth char has to wrap early
// // the dangling last cell is wrongly added in the string
// // --> fixable after resolving #1685
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// for (let i = 10; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
// const j = (i - 0) << 1;
// assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex);
// }
// });
// it('test fully wrapped buffer up to last char', () => {
// const input = Array(6).join('1234567890');
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// for (let i = 0; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
// assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars());
// }
// });
// it('test fully wrapped buffer up to last char with full width odd', () => {
// const input = 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301'
// + 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(input, s);
// for (let i = 0; i < input.length; ++i) {
// const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
// assert.equal(
// (!(i % 3))
// ? input[i]
// : (i % 3 === 1)
// ? input.substr(i, 2)
// : input.substr(i - 1, 2),
// terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars());
// }
// });
// it('should handle \t in lines correctly', () => {
// const input = '\thttps://google.de';
// terminal.writeSync(input);
// const s = terminal.buffer.iterator(true).next().content;
// assert.equal(s, Array(optionsService.options.tabStopWidth + 1).join(' ') + 'https://google.de');
// });
// });
// describe('BufferStringIterator', function(): void {
// it('iterator does not overflow buffer limits', function(): void {
// const terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5});
// const data = [
// 'aaaaaaaaaa',
// 'aaaaaaaaa\n',
// 'aaaaaaaaaa',
// 'aaaaaaaaa\n',
// 'aaaaaaaaaa',
// 'aaaaaaaaaa',
// 'aaaaaaaaaa',
// 'aaaaaaaaa\n',
// 'aaaaaaaaaa',
// 'aaaaaaaaaa'
// ];
// terminal.writeSync(data.join(''));
// // brute force test with insane values
// expect(() => {
// for (let overscan = 0; overscan < 20; ++overscan) {
// for (let start = -10; start < 20; ++start) {
// for (let end = -10; end < 20; ++end) {
// const it = terminal.buffer.iterator(false, start, end, overscan, overscan);
// while (it.hasNext()) {
// it.next();
// }
// }
// }
// }
// }).to.not.throw();
// });
// });
});
+1 -1
View File
@@ -40,7 +40,7 @@ export interface IBuffer {
nextStop(x?: number): number;
prevStop(x?: number): number;
getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine;
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[];
stringIndexToBufferIndex(lineIndex: number, stringIndex: number, trimRight?: boolean): number[];
iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator;
getNullCell(attr?: IAttributeData): ICellData;
getWhitespaceCell(attr?: IAttributeData): ICellData;
+19 -19
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { assert } from 'chai';
import { DcsParser, DcsHandlerFactory } from 'common/parser/DcsParser';
import { DcsParser, DcsHandler } from 'common/parser/DcsParser';
import { IDcsHandler, IParams, IFunctionIdentifier } from 'common/parser/Types';
import { utf32ToString, StringToUtf32 } from 'common/input/TextDecoder';
import { Params } from 'common/parser/Params';
@@ -75,7 +75,7 @@ describe('DcsParser', () => {
beforeEach(() => {
reports = [];
parser = new DcsParser();
parser.setDcsHandlerFallback((id, action, data) => {
parser.setHandlerFallback((id, action, data) => {
if (action === 'HOOK') {
data = data.toArray();
}
@@ -84,7 +84,7 @@ describe('DcsParser', () => {
});
describe('handler registration', () => {
it('setDcsHandler', () => {
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th'));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th'));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
@@ -100,8 +100,8 @@ describe('DcsParser', () => {
]);
});
it('clearDcsHandler', () => {
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th'));
parser.clearDcsHandler(identifier({intermediates: '+', final: 'p'}));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th'));
parser.clearHandler(identifier({intermediates: '+', final: 'p'}));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
@@ -117,8 +117,8 @@ describe('DcsParser', () => {
]);
});
it('addDcsHandler', () => {
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
parser.addDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2'));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2'));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
@@ -137,8 +137,8 @@ describe('DcsParser', () => {
]);
});
it('addDcsHandler with return false', () => {
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
parser.addDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
@@ -157,8 +157,8 @@ describe('DcsParser', () => {
]);
});
it('dispose handlers', () => {
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
const dispo = parser.addDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
const dispo = parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true));
dispo.dispose();
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
@@ -176,7 +176,7 @@ describe('DcsParser', () => {
});
describe('DcsHandlerFactory', () => {
it('should be called once on end(true)', () => {
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push([params.toArray(), data])));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data])));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
@@ -186,7 +186,7 @@ describe('DcsParser', () => {
assert.deepEqual(reports, [[[1, 2, 3], 'Here comes the mouse!']]);
});
it('should not be called on end(false)', () => {
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push([params.toArray(), data])));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data])));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
@@ -196,8 +196,8 @@ describe('DcsParser', () => {
assert.deepEqual(reports, []);
});
it('should be disposable', () => {
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push(['one', params.toArray(), data])));
const dispo = parser.addDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push(['two', params.toArray(), data])));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['one', params.toArray(), data])));
const dispo = parser.addHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['two', params.toArray(), data])));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
@@ -215,8 +215,8 @@ describe('DcsParser', () => {
assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'some other data']]);
});
it('should respect return false', () => {
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push(['one', params.toArray(), data])));
parser.addDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => { reports.push(['two', params.toArray(), data]); return false; }));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['one', params.toArray(), data])));
parser.addHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['two', params.toArray(), data]); return false; }));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
@@ -227,7 +227,7 @@ describe('DcsParser', () => {
});
it('should work up to payload limit', function(): void {
this.timeout(10000);
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push([params.toArray(), data])));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data])));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
const data = toUtf32('A'.repeat(1000));
for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) {
@@ -238,7 +238,7 @@ describe('DcsParser', () => {
});
it('should abort for payload limit +1', function(): void {
this.timeout(10000);
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push([params.toArray(), data])));
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data])));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('A'.repeat(1000));
for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) {
+12 -12
View File
@@ -4,25 +4,25 @@
*/
import { IDisposable } from 'common/Types';
import { IDcsHandler, IParams, IHandlerCollection, IDcsParser, DcsFallbackHandler } from 'common/parser/Types';
import { IDcsHandler, IParams, IHandlerCollection, IDcsParser, DcsFallbackHandlerType } from 'common/parser/Types';
import { utf32ToString } from 'common/input/TextDecoder';
import { Params } from 'common/parser/Params';
import { PAYLOAD_LIMIT } from 'common/parser/Constants';
const EMPTY_HANDLERS: IDcsHandler[] = [];
export class DcsParser implements IDcsParser {
private _handlers: IHandlerCollection<IDcsHandler> = Object.create(null);
private _empty: IDcsHandler[] = [];
private _active: IDcsHandler[] = this._empty;
private _active: IDcsHandler[] = EMPTY_HANDLERS;
private _ident: number = 0;
private _handlerFb: DcsFallbackHandler = () => {};
private _handlerFb: DcsFallbackHandlerType = () => {};
public dispose(): void {
this._handlers = Object.create(null);
this._handlerFb = () => {};
}
public addDcsHandler(ident: number, handler: IDcsHandler): IDisposable {
public addHandler(ident: number, handler: IDcsHandler): IDisposable {
if (this._handlers[ident] === undefined) {
this._handlers[ident] = [];
}
@@ -38,15 +38,15 @@ export class DcsParser implements IDcsParser {
};
}
public setDcsHandler(ident: number, handler: IDcsHandler): void {
public setHandler(ident: number, handler: IDcsHandler): void {
this._handlers[ident] = [handler];
}
public clearDcsHandler(ident: number): void {
public clearHandler(ident: number): void {
if (this._handlers[ident]) delete this._handlers[ident];
}
public setDcsHandlerFallback(handler: DcsFallbackHandler): void {
public setHandlerFallback(handler: DcsFallbackHandlerType): void {
this._handlerFb = handler;
}
@@ -54,7 +54,7 @@ export class DcsParser implements IDcsParser {
if (this._active.length) {
this.unhook(false);
}
this._active = this._empty;
this._active = EMPTY_HANDLERS;
this._ident = 0;
}
@@ -62,7 +62,7 @@ export class DcsParser implements IDcsParser {
// always reset leftover handlers
this.reset();
this._ident = ident;
this._active = this._handlers[ident] || this._empty;
this._active = this._handlers[ident] || EMPTY_HANDLERS;
if (!this._active.length) {
this._handlerFb(this._ident, 'HOOK', params);
} else {
@@ -98,7 +98,7 @@ export class DcsParser implements IDcsParser {
this._active[j].unhook(false);
}
}
this._active = this._empty;
this._active = EMPTY_HANDLERS;
this._ident = 0;
}
}
@@ -107,7 +107,7 @@ export class DcsParser implements IDcsParser {
* Convenient class to create a DCS handler from a single callback function.
* Note: The payload is currently limited to 50 MB (hardcoded).
*/
export class DcsHandlerFactory implements IDcsHandler {
export class DcsHandler implements IDcsHandler {
private _data = '';
private _params: IParams | undefined;
private _hitLimit: boolean = false;
+57 -57
View File
@@ -3,15 +3,15 @@
* @license MIT
*/
import { IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandler } from 'common/parser/Types';
import { IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandlerType } from 'common/parser/Types';
import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser';
import * as chai from 'chai';
import { StringToUtf32, stringFromCodePoint, utf32ToString } from 'common/input/TextDecoder';
import { ParserState } from 'common/parser/Constants';
import { Params } from 'common/parser/Params';
import { OscHandlerFactory } from 'common/parser/OscParser';
import { OscHandler } from 'common/parser/OscParser';
import { IDisposable } from 'common/Types';
import { DcsHandlerFactory } from 'common/parser/DcsParser';
import { DcsHandler } from 'common/parser/DcsParser';
function r(a: number, b: number): string[] {
@@ -24,7 +24,7 @@ function r(a: number, b: number): string[] {
}
class MockOscPutParser implements IOscParser {
private _fallback: OscFallbackHandler = () => {};
private _fallback: OscFallbackHandlerType = () => {};
public data = '';
public reset(): void {
this.data = '';
@@ -41,16 +41,16 @@ class MockOscPutParser implements IOscParser {
this._fallback(id, 'END', this.data.slice(this.data.indexOf(';') + 1));
}
}
addOscHandler(ident: number, handler: IOscHandler): IDisposable {
addHandler(ident: number, handler: IOscHandler): IDisposable {
throw new Error('not implemented');
}
setOscHandler(ident: number, handler: IOscHandler): void {
setHandler(ident: number, handler: IOscHandler): void {
throw new Error('not implemented');
}
clearOscHandler(ident: number): void {
clearHandler(ident: number): void {
throw new Error('not implemented');
}
setOscHandlerFallback(handler: OscFallbackHandler): void {
setHandlerFallback(handler: OscFallbackHandlerType): void {
this._fallback = handler;
}
}
@@ -1220,26 +1220,26 @@ describe('EscapeSequenceParser', function (): void {
});
describe('ESC custom handlers', () => {
it('prevent fallback', () => {
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
parse(parser2, INPUT);
chai.expect(esc).eql(['custom - %G']);
});
it('allow fallback', () => {
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return false; });
parse(parser2, INPUT);
chai.expect(esc).eql(['custom - %G', 'default - %G']);
});
it('Multiple custom handlers fallback once', () => {
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return false; });
parse(parser2, INPUT);
chai.expect(esc).eql(['custom2 - %G', 'custom - %G']);
});
it('Multiple custom handlers no fallback', () => {
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return true; });
parse(parser2, INPUT);
@@ -1247,21 +1247,21 @@ describe('EscapeSequenceParser', function (): void {
});
it('Execution order should go from latest handler down to the original', () => {
const order: number[] = [];
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => order.push(1));
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { order.push(1); });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { order.push(2); return false; });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { order.push(3); return false; });
parse(parser2, '\x1b%G');
chai.expect(order).eql([3, 2, 1]);
});
it('Dispose should work', () => {
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
const dispo = parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
dispo.dispose();
parse(parser2, INPUT);
chai.expect(esc).eql(['default - %G']);
});
it('Should not corrupt the parser when dispose is called twice', () => {
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
const dispo = parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
dispo.dispose();
dispo.dispose();
@@ -1284,7 +1284,7 @@ describe('EscapeSequenceParser', function (): void {
describe('CSI custom handlers', () => {
it('Prevent fallback', () => {
const csiCustom: [string, ParamsArray, string][] = [];
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
parse(parser2, INPUT);
chai.expect(csi).eql([], 'Should not fallback to original handler');
@@ -1292,7 +1292,7 @@ describe('EscapeSequenceParser', function (): void {
});
it('Allow fallback', () => {
const csiCustom: [string, ParamsArray, string][] = [];
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return false; });
parse(parser2, INPUT);
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler');
@@ -1301,7 +1301,7 @@ describe('EscapeSequenceParser', function (): void {
it('Multiple custom handlers fallback once', () => {
const csiCustom: [string, ParamsArray, string][] = [];
const csiCustom2: [string, ParamsArray, string][] = [];
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
parser2.addCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return false; });
parse(parser2, INPUT);
@@ -1312,7 +1312,7 @@ describe('EscapeSequenceParser', function (): void {
it('Multiple custom handlers no fallback', () => {
const csiCustom: [string, ParamsArray, string][] = [];
const csiCustom2: [string, ParamsArray, string][] = [];
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
parser2.addCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return true; });
parse(parser2, INPUT);
@@ -1322,7 +1322,7 @@ describe('EscapeSequenceParser', function (): void {
});
it('Execution order should go from latest handler down to the original', () => {
const order: number[] = [];
parser2.setCsiHandler({final: 'm'}, () => order.push(1));
parser2.setCsiHandler({final: 'm'}, () => { order.push(1); });
parser2.addCsiHandler({final: 'm'}, () => { order.push(2); return false; });
parser2.addCsiHandler({final: 'm'}, () => { order.push(3); return false; });
parse(parser2, '\x1b[0m');
@@ -1330,7 +1330,7 @@ describe('EscapeSequenceParser', function (): void {
});
it('Dispose should work', () => {
const csiCustom: [string, ParamsArray, string][] = [];
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
const customHandler = parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
customHandler.dispose();
parse(parser2, INPUT);
@@ -1339,7 +1339,7 @@ describe('EscapeSequenceParser', function (): void {
});
it('Should not corrupt the parser when dispose is called twice', () => {
const csiCustom: [string, ParamsArray, string][] = [];
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
const customHandler = parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
customHandler.dispose();
customHandler.dispose();
@@ -1364,7 +1364,7 @@ describe('EscapeSequenceParser', function (): void {
chai.expect(exe).eql(['\n']);
});
it('OSC handler', function (): void {
parser2.setOscHandler(1, new OscHandlerFactory(function (data: string): void {
parser2.setOscHandler(1, new OscHandler(function (data: string): void {
osc.push([1, data]);
}));
parse(parser2, INPUT);
@@ -1378,16 +1378,16 @@ describe('EscapeSequenceParser', function (): void {
describe('OSC custom handlers', () => {
it('Prevent fallback', () => {
const oscCustom: [number, string][] = [];
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return true; }));
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));
parse(parser2, INPUT);
chai.expect(osc).eql([], 'Should not fallback to original handler');
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
});
it('Allow fallback', () => {
const oscCustom: [number, string][] = [];
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return false; }));
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return false; }));
parse(parser2, INPUT);
chai.expect(osc).eql([[1, 'foo=bar']], 'Should fallback to original handler');
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
@@ -1395,9 +1395,9 @@ describe('EscapeSequenceParser', function (): void {
it('Multiple custom handlers fallback once', () => {
const oscCustom: [number, string][] = [];
const oscCustom2: [number, string][] = [];
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return true; }));
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom2.push([1, data]); return false; }));
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));
parser2.addOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return false; }));
parse(parser2, INPUT);
chai.expect(osc).eql([], 'Should not fallback to original handler');
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
@@ -1406,9 +1406,9 @@ describe('EscapeSequenceParser', function (): void {
it('Multiple custom handlers no fallback', () => {
const oscCustom: [number, string][] = [];
const oscCustom2: [number, string][] = [];
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return true; }));
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom2.push([1, data]); return true; }));
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));
parser2.addOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return true; }));
parse(parser2, INPUT);
chai.expect(osc).eql([], 'Should not fallback to original handler');
chai.expect(oscCustom).eql([], 'Should not fallback once');
@@ -1416,16 +1416,16 @@ describe('EscapeSequenceParser', function (): void {
});
it('Execution order should go from latest handler down to the original', () => {
const order: number[] = [];
parser2.setOscHandler(1, new OscHandlerFactory(() => order.push(1)));
parser2.addOscHandler(1, new OscHandlerFactory(() => { order.push(2); return false; }));
parser2.addOscHandler(1, new OscHandlerFactory(() => { order.push(3); return false; }));
parser2.setOscHandler(1, new OscHandler(() => order.push(1)));
parser2.addOscHandler(1, new OscHandler(() => { order.push(2); return false; }));
parser2.addOscHandler(1, new OscHandler(() => { order.push(3); return false; }));
parse(parser2, '\x1b]1;foo=bar\x1b\\');
chai.expect(order).eql([3, 2, 1]);
});
it('Dispose should work', () => {
const oscCustom: [number, string][] = [];
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
const customHandler = parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return true; }));
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
const customHandler = parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));
customHandler.dispose();
parse(parser2, INPUT);
chai.expect(osc).eql([[1, 'foo=bar']]);
@@ -1433,8 +1433,8 @@ describe('EscapeSequenceParser', function (): void {
});
it('Should not corrupt the parser when dispose is called twice', () => {
const oscCustom: [number, string][] = [];
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
const customHandler = parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return true; }));
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
const customHandler = parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));
customHandler.dispose();
customHandler.dispose();
parse(parser2, INPUT);
@@ -1476,54 +1476,54 @@ describe('EscapeSequenceParser', function (): void {
const DCS_INPUT = '\x1bP1;2;3+pabc\x1b\\';
it('Prevent fallback', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parse(parser2, DCS_INPUT);
chai.expect(dcsCustom).eql([['B', [1, 2, 3], 'abc']]);
});
it('Allow fallback', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return false; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return false; }));
parse(parser2, DCS_INPUT);
chai.expect(dcsCustom).eql([['B', [1, 2, 3], 'abc'], ['A', [1, 2, 3], 'abc']]);
});
it('Multiple custom handlers fallback once', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return false; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return false; }));
parse(parser2, DCS_INPUT);
chai.expect(dcsCustom).eql([['C', [1, 2, 3], 'abc'], ['B', [1, 2, 3], 'abc']]);
});
it('Multiple custom handlers no fallback', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return true; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return true; }));
parse(parser2, DCS_INPUT);
chai.expect(dcsCustom).eql([['C', [1, 2, 3], 'abc']]);
});
it('Execution order should go from latest handler down to the original', () => {
const order: number[] = [];
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory(() => order.push(1)));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory(() => { order.push(2); return false; }));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory(() => { order.push(3); return false; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => order.push(1)));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(2); return false; }));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(3); return false; }));
parse(parser2, DCS_INPUT);
chai.expect(order).eql([3, 2, 1]);
});
it('Dispose should work', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
dispo.dispose();
parse(parser2, DCS_INPUT);
chai.expect(dcsCustom).eql([['A', [1, 2, 3], 'abc']]);
});
it('Should not corrupt the parser when dispose is called twice', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
dispo.dispose();
dispo.dispose();
parse(parser2, DCS_INPUT);
+71 -71
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandler, OscFallbackHandler, IOscParser, EscHandler, IDcsParser, DcsFallbackHandler, IFunctionIdentifier } from 'common/parser/Types';
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType } from 'common/parser/Types';
import { ParserState, ParserAction } from 'common/parser/Constants';
import { Disposable } from 'common/Lifecycle';
import { IDisposable } from 'common/Types';
@@ -238,19 +238,19 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _collect: number;
// handler lookup containers
protected _printHandler: (data: Uint32Array, start: number, end: number) => void;
protected _executeHandlers: any;
protected _csiHandlers: IHandlerCollection<CsiHandler>;
protected _escHandlers: IHandlerCollection<EscHandler>;
protected _printHandler: PrintHandlerType;
protected _executeHandlers: {[flag: number]: ExecuteHandlerType};
protected _csiHandlers: IHandlerCollection<CsiHandlerType>;
protected _escHandlers: IHandlerCollection<EscHandlerType>;
protected _oscParser: IOscParser;
protected _dcsParser: IDcsParser;
protected _errorHandler: (state: IParsingState) => IParsingState;
// fallback handlers
protected _printHandlerFb: (data: Uint32Array, start: number, end: number) => void;
protected _executeHandlerFb: (code: number) => void;
protected _csiHandlerFb: (ident: number, params: IParams) => void;
protected _escHandlerFb: (ident: number) => void;
protected _printHandlerFb: PrintFallbackHandlerType;
protected _executeHandlerFb: ExecuteFallbackHandlerType;
protected _csiHandlerFb: CsiFallbackHandlerType;
protected _escHandlerFb: EscFallbackHandlerType;
protected _errorHandlerFb: (state: IParsingState) => IParsingState;
constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) {
@@ -328,47 +328,74 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
}
public dispose(): void {
this._executeHandlers = null;
this._csiHandlers = Object.create(null);
this._executeHandlers = Object.create(null);
this._escHandlers = Object.create(null);
this._oscParser.dispose();
this._dcsParser.dispose();
}
public setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {
this._printHandler = callback;
public setPrintHandler(handler: PrintHandlerType): void {
this._printHandler = handler;
}
public clearPrintHandler(): void {
this._printHandler = this._printHandlerFb;
}
public setExecuteHandler(flag: string, callback: () => void): void {
this._executeHandlers[flag.charCodeAt(0)] = callback;
}
public clearExecuteHandler(flag: string): void {
if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];
}
public setExecuteHandlerFallback(callback: (code: number) => void): void {
this._executeHandlerFb = callback;
}
public addCsiHandler(id: IFunctionIdentifier, callback: CsiHandler): IDisposable {
const ident = this._identifier(id);
if (this._csiHandlers[ident] === undefined) {
this._csiHandlers[ident] = [];
public addEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable {
const ident = this._identifier(id, [0x30, 0x7e]);
if (this._escHandlers[ident] === undefined) {
this._escHandlers[ident] = [];
}
const handlerList = this._csiHandlers[ident];
handlerList.push(callback);
const handlerList = this._escHandlers[ident];
handlerList.push(handler);
return {
dispose: () => {
const handlerIndex = handlerList.indexOf(callback);
const handlerIndex = handlerList.indexOf(handler);
if (handlerIndex !== -1) {
handlerList.splice(handlerIndex, 1);
}
}
};
}
public setCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => void): void {
this._csiHandlers[this._identifier(id)] = [callback];
public setEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): void {
this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [handler];
}
public clearEscHandler(id: IFunctionIdentifier): void {
if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])];
}
public setEscHandlerFallback(handler: EscFallbackHandlerType): void {
this._escHandlerFb = handler;
}
public setExecuteHandler(flag: string, handler: ExecuteHandlerType): void {
this._executeHandlers[flag.charCodeAt(0)] = handler;
}
public clearExecuteHandler(flag: string): void {
if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];
}
public setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void {
this._executeHandlerFb = handler;
}
public addCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable {
const ident = this._identifier(id);
if (this._csiHandlers[ident] === undefined) {
this._csiHandlers[ident] = [];
}
const handlerList = this._csiHandlers[ident];
handlerList.push(handler);
return {
dispose: () => {
const handlerIndex = handlerList.indexOf(handler);
if (handlerIndex !== -1) {
handlerList.splice(handlerIndex, 1);
}
}
};
}
public setCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): void {
this._csiHandlers[this._identifier(id)] = [handler];
}
public clearCsiHandler(id: IFunctionIdentifier): void {
if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)];
@@ -377,56 +404,30 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._csiHandlerFb = callback;
}
public addEscHandler(id: IFunctionIdentifier, callback: EscHandler): IDisposable {
const ident = this._identifier(id, [0x30, 0x7e]);
if (this._escHandlers[ident] === undefined) {
this._escHandlers[ident] = [];
}
const handlerList = this._escHandlers[ident];
handlerList.push(callback);
return {
dispose: () => {
const handlerIndex = handlerList.indexOf(callback);
if (handlerIndex !== -1) {
handlerList.splice(handlerIndex, 1);
}
}
};
public addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {
return this._dcsParser.addHandler(this._identifier(id), handler);
}
public setEscHandler(id: IFunctionIdentifier, callback: () => void): void {
this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [callback];
public setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void {
this._dcsParser.setHandler(this._identifier(id), handler);
}
public clearEscHandler(id: IFunctionIdentifier): void {
if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])];
public clearDcsHandler(id: IFunctionIdentifier): void {
this._dcsParser.clearHandler(this._identifier(id));
}
public setEscHandlerFallback(callback: (ident: number) => void): void {
this._escHandlerFb = callback;
public setDcsHandlerFallback(handler: DcsFallbackHandlerType): void {
this._dcsParser.setHandlerFallback(handler);
}
public addOscHandler(ident: number, handler: IOscHandler): IDisposable {
return this._oscParser.addOscHandler(ident, handler);
return this._oscParser.addHandler(ident, handler);
}
public setOscHandler(ident: number, handler: IOscHandler): void {
this._oscParser.setOscHandler(ident, handler);
this._oscParser.setHandler(ident, handler);
}
public clearOscHandler(ident: number): void {
this._oscParser.clearOscHandler(ident);
this._oscParser.clearHandler(ident);
}
public setOscHandlerFallback(handler: OscFallbackHandler): void {
this._oscParser.setOscHandlerFallback(handler);
}
public addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {
return this._dcsParser.addDcsHandler(this._identifier(id), handler);
}
public setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void {
this._dcsParser.setDcsHandler(this._identifier(id), handler);
}
public clearDcsHandler(id: IFunctionIdentifier): void {
this._dcsParser.clearDcsHandler(this._identifier(id));
}
public setDcsHandlerFallback(handler: DcsFallbackHandler): void {
this._dcsParser.setDcsHandlerFallback(handler);
public setOscHandlerFallback(handler: OscFallbackHandlerType): void {
this._oscParser.setHandlerFallback(handler);
}
public setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
@@ -443,7 +444,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._params.reset();
this._params.addParam(0); // ZDM
this._collect = 0;
// this._activeDcsHandler = this._dcsHandlerFb;
this.precedingCodepoint = 0;
}
+19 -19
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { assert } from 'chai';
import { OscParser, OscHandlerFactory } from 'common/parser/OscParser';
import { OscParser, OscHandler } from 'common/parser/OscParser';
import { StringToUtf32, utf32ToString } from 'common/input/TextDecoder';
import { IOscHandler } from 'common/parser/Types';
import { PAYLOAD_LIMIT } from 'common/parser/Constants';
@@ -37,7 +37,7 @@ describe('OscParser', () => {
beforeEach(() => {
reports = [];
parser = new OscParser();
parser.setOscHandlerFallback((id, action, data) => {
parser.setHandlerFallback((id, action, data) => {
reports.push([id, action, data]);
});
});
@@ -78,7 +78,7 @@ describe('OscParser', () => {
});
describe('handler registration', () => {
it('setOscHandler', () => {
parser.setOscHandler(1234, new TestHandler(1234, reports, 'th'));
parser.setHandler(1234, new TestHandler(1234, reports, 'th'));
parser.start();
let data = toUtf32('1234;Here comes');
parser.put(data, 0, data.length);
@@ -94,8 +94,8 @@ describe('OscParser', () => {
]);
});
it('clearOscHandler', () => {
parser.setOscHandler(1234, new TestHandler(1234, reports, 'th'));
parser.clearOscHandler(1234);
parser.setHandler(1234, new TestHandler(1234, reports, 'th'));
parser.clearHandler(1234);
parser.start();
let data = toUtf32('1234;Here comes');
parser.put(data, 0, data.length);
@@ -111,8 +111,8 @@ describe('OscParser', () => {
]);
});
it('addOscHandler', () => {
parser.setOscHandler(1234, new TestHandler(1234, reports, 'th1'));
parser.addOscHandler(1234, new TestHandler(1234, reports, 'th2'));
parser.setHandler(1234, new TestHandler(1234, reports, 'th1'));
parser.addHandler(1234, new TestHandler(1234, reports, 'th2'));
parser.start();
let data = toUtf32('1234;Here comes');
parser.put(data, 0, data.length);
@@ -131,8 +131,8 @@ describe('OscParser', () => {
]);
});
it('addOscHandler with return false', () => {
parser.setOscHandler(1234, new TestHandler(1234, reports, 'th1'));
parser.addOscHandler(1234, new TestHandler(1234, reports, 'th2', true));
parser.setHandler(1234, new TestHandler(1234, reports, 'th1'));
parser.addHandler(1234, new TestHandler(1234, reports, 'th2', true));
parser.start();
let data = toUtf32('1234;Here comes');
parser.put(data, 0, data.length);
@@ -151,8 +151,8 @@ describe('OscParser', () => {
]);
});
it('dispose handlers', () => {
parser.setOscHandler(1234, new TestHandler(1234, reports, 'th1'));
const dispo = parser.addOscHandler(1234, new TestHandler(1234, reports, 'th2', true));
parser.setHandler(1234, new TestHandler(1234, reports, 'th1'));
const dispo = parser.addHandler(1234, new TestHandler(1234, reports, 'th2', true));
dispo.dispose();
parser.start();
let data = toUtf32('1234;Here comes');
@@ -170,7 +170,7 @@ describe('OscParser', () => {
});
describe('OscHandlerFactory', () => {
it('should be called once on end(true)', () => {
parser.setOscHandler(1234, new OscHandlerFactory(data => reports.push([1234, data])));
parser.setHandler(1234, new OscHandler(data => reports.push([1234, data])));
parser.start();
let data = toUtf32('1234;Here comes');
parser.put(data, 0, data.length);
@@ -180,7 +180,7 @@ describe('OscParser', () => {
assert.deepEqual(reports, [[1234, 'Here comes the mouse!']]);
});
it('should not be called on end(false)', () => {
parser.setOscHandler(1234, new OscHandlerFactory(data => reports.push([1234, data])));
parser.setHandler(1234, new OscHandler(data => reports.push([1234, data])));
parser.start();
let data = toUtf32('1234;Here comes');
parser.put(data, 0, data.length);
@@ -190,8 +190,8 @@ describe('OscParser', () => {
assert.deepEqual(reports, []);
});
it('should be disposable', () => {
parser.setOscHandler(1234, new OscHandlerFactory(data => reports.push(['one', data])));
const dispo = parser.addOscHandler(1234, new OscHandlerFactory(data => reports.push(['two', data])));
parser.setHandler(1234, new OscHandler(data => reports.push(['one', data])));
const dispo = parser.addHandler(1234, new OscHandler(data => reports.push(['two', data])));
parser.start();
let data = toUtf32('1234;Here comes');
parser.put(data, 0, data.length);
@@ -209,8 +209,8 @@ describe('OscParser', () => {
assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'some other data']]);
});
it('should respect return false', () => {
parser.setOscHandler(1234, new OscHandlerFactory(data => reports.push(['one', data])));
parser.addOscHandler(1234, new OscHandlerFactory(data => { reports.push(['two', data]); return false; }));
parser.setHandler(1234, new OscHandler(data => reports.push(['one', data])));
parser.addHandler(1234, new OscHandler(data => { reports.push(['two', data]); return false; }));
parser.start();
let data = toUtf32('1234;Here comes');
parser.put(data, 0, data.length);
@@ -221,7 +221,7 @@ describe('OscParser', () => {
});
it('should work up to payload limit', function(): void {
this.timeout(10000);
parser.setOscHandler(1234, new OscHandlerFactory(data => reports.push([1234, data])));
parser.setHandler(1234, new OscHandler(data => reports.push([1234, data])));
parser.start();
let data = toUtf32('1234;');
parser.put(data, 0, data.length);
@@ -234,7 +234,7 @@ describe('OscParser', () => {
});
it('should abort for payload limit +1', function(): void {
this.timeout(10000);
parser.setOscHandler(1234, new OscHandlerFactory(data => reports.push([1234, data])));
parser.setHandler(1234, new OscHandler(data => reports.push([1234, data])));
parser.start();
let data = toUtf32('1234;');
parser.put(data, 0, data.length);
+8 -9
View File
@@ -3,20 +3,19 @@
* @license MIT
*/
import { IOscHandler, IHandlerCollection, OscFallbackHandler } from 'common/parser/Types';
import { IOscHandler, IHandlerCollection, OscFallbackHandlerType, IOscParser } from 'common/parser/Types';
import { OscState, PAYLOAD_LIMIT } from 'common/parser/Constants';
import { Disposable } from 'common/Lifecycle';
import { utf32ToString } from 'common/input/TextDecoder';
import { IDisposable } from 'common/Types';
export class OscParser extends Disposable {
export class OscParser implements IOscParser {
private _state = OscState.START;
private _id = -1;
private _handlers: IHandlerCollection<IOscHandler> = Object.create(null);
private _handlerFb: OscFallbackHandler = () => { };
private _handlerFb: OscFallbackHandlerType = () => { };
public addOscHandler(ident: number, handler: IOscHandler): IDisposable {
public addHandler(ident: number, handler: IOscHandler): IDisposable {
if (this._handlers[ident] === undefined) {
this._handlers[ident] = [];
}
@@ -31,13 +30,13 @@ export class OscParser extends Disposable {
}
};
}
public setOscHandler(ident: number, handler: IOscHandler): void {
public setHandler(ident: number, handler: IOscHandler): void {
this._handlers[ident] = [handler];
}
public clearOscHandler(ident: number): void {
public clearHandler(ident: number): void {
if (this._handlers[ident]) delete this._handlers[ident];
}
public setOscHandlerFallback(handler: OscFallbackHandler): void {
public setHandlerFallback(handler: OscFallbackHandlerType): void {
this._handlerFb = handler;
}
@@ -168,7 +167,7 @@ export class OscParser extends Disposable {
* Convenient class to allow attaching string based handler functions
* as OSC handlers.
*/
export class OscHandlerFactory implements IOscHandler {
export class OscHandler implements IOscHandler {
private _data = '';
private _hitLimit: boolean = false;
+103 -67
View File
@@ -61,65 +61,84 @@ export interface IParsingState {
abort: boolean;
}
export interface IHandlerCollection<T> {
[key: string]: T[];
}
export type CsiHandler = (params: IParams) => boolean | void;
export type EscHandler = () => boolean | void;
/**
* Command handler interfaces.
*/
/**
* DCS handler signature for EscapeSequenceParser.
* EscapeSequenceParser handles DCS commands via separate
* subparsers that get hook/unhooked and can handle
* arbitrary amount of data.
*
* On entering a DSC sequence `hook` is called by
* `EscapeSequenceParser`. Use it to initialize or reset
* states needed to handle the current DCS sequence.
* Note: A DCS parser is only instantiated once, therefore
* you cannot rely on the ctor to reinitialize state.
*
* EscapeSequenceParser will call `put` several times if the
* parsed data got split, therefore you might have to collect
* `data` until `unhook` is called.
* Note: `data` is borrowed, if you cannot process the data
* in chunks you have to copy it, doing otherwise will lead to
* data losses or corruption.
*
* `unhook` marks the end of the current DCS sequence. `success`
* indicates whether the command was aborted.
*/
* CSI handler types.
* Note: `params` is borrowed.
*/
export type CsiHandlerType = (params: IParams) => boolean | void;
export type CsiFallbackHandlerType = (ident: number, params: IParams) => void;
/**
* DCS handler types.
*/
export interface IDcsHandler {
/**
* Called when a DCS command starts.
* Prepare needed data structures here.
* Note: `params` is borrowed.
*/
hook(params: IParams): void;
/**
* Incoming payload chunk.
* Note: `params` is borrowed.
*/
put(data: Uint32Array, start: number, end: number): void;
/**
* End of DCS command. `success` indicates whether the
* command finished normally or got aborted, thus final
* execution of the command should depend on `success`.
* To save memory also cleanup data structures here.
*/
unhook(success: boolean): void | boolean;
}
export type DcsFallbackHandlerType = (ident: number, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void;
export type DcsFallbackHandler = (ident: number, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void;
/**
* ESC handler types.
*/
export type EscHandlerType = () => boolean | void;
export type EscFallbackHandlerType = (identifier: number) => void;
/**
* EXECUTE handler types.
*/
export type ExecuteHandlerType = () => boolean | void;
export type ExecuteFallbackHandlerType = (ident: number) => void;
/**
* OSC handler types.
*/
export interface IOscHandler {
/**
* Announces start of this OSC command.
* Prepare needed data structures here.
*/
start(): void;
/**
* Incoming data chunk.
* Note: Data is borrowed.
*/
put(data: Uint32Array, start: number, end: number): void;
/**
* End of OSC command. `success` indicates whether the
* command finished normally or got aborted, thus execution
* of the command should depend on `success`.
* To save memory cleanup data structures in `.end`.
* command finished normally or got aborted, thus final
* execution of the command should depend on `success`.
* To save memory also cleanup data structures here.
*/
end(success: boolean): void | boolean;
}
export type OscFallbackHandlerType = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void;
/**
* PRINT handler types.
*/
export type PrintHandlerType = (data: Uint32Array, start: number, end: number) => void;
export type PrintFallbackHandlerType = PrintHandlerType;
export type OscFallbackHandler = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void;
/**
* EscapeSequenceParser interface.
@@ -144,65 +163,82 @@ export interface IEscapeSequenceParser extends IDisposable {
parse(data: Uint32Array, length: number): void;
/**
* Get string from ident number.
* Get string from numercial function identifier `ident`.
* Useful in fallback handlers which expose the low level
* numcerical function identifier for debugging purposes.
* Note: A full back translation to `IFunctionIdentifier`
* is not implemented.
*/
identToString(ident: number): string;
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void;
setPrintHandler(handler: PrintHandlerType): void;
clearPrintHandler(): void;
setExecuteHandler(flag: string, callback: () => void): void;
clearExecuteHandler(flag: string): void;
setExecuteHandlerFallback(callback: (code: number) => void): void;
setCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => void): void;
clearCsiHandler(id: IFunctionIdentifier): void;
setCsiHandlerFallback(callback: (identifier: number, params: IParams) => void): void;
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable;
setEscHandler(id: IFunctionIdentifier, callback: () => void): void;
setEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): void;
clearEscHandler(id: IFunctionIdentifier): void;
setEscHandlerFallback(callback: (identifier: number) => void): void;
addEscHandler(id: IFunctionIdentifier, handler: EscHandler): IDisposable;
setEscHandlerFallback(handler: EscFallbackHandlerType): void;
addEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable;
setOscHandler(ident: number, handler: IOscHandler): void;
clearOscHandler(ident: number): void;
setOscHandlerFallback(handler: OscFallbackHandler): void;
addOscHandler(ident: number, handler: IOscHandler): IDisposable;
setExecuteHandler(flag: string, handler: ExecuteHandlerType): void;
clearExecuteHandler(flag: string): void;
setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void;
setCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): void;
clearCsiHandler(id: IFunctionIdentifier): void;
setCsiHandlerFallback(callback: CsiFallbackHandlerType): void;
addCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable;
setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void;
clearDcsHandler(id: IFunctionIdentifier): void;
setDcsHandlerFallback(handler: DcsFallbackHandler): void;
setDcsHandlerFallback(handler: DcsFallbackHandlerType): void;
addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable;
setErrorHandler(callback: (state: IParsingState) => IParsingState): void;
setOscHandler(ident: number, handler: IOscHandler): void;
clearOscHandler(ident: number): void;
setOscHandlerFallback(handler: OscFallbackHandlerType): void;
addOscHandler(ident: number, handler: IOscHandler): IDisposable;
setErrorHandler(handler: (state: IParsingState) => IParsingState): void;
clearErrorHandler(): void;
}
export interface IOscParser extends IDisposable {
addOscHandler(ident: number, handler: IOscHandler): IDisposable;
setOscHandler(ident: number, handler: IOscHandler): void;
clearOscHandler(ident: number): void;
setOscHandlerFallback(handler: OscFallbackHandler): void;
/**
* Subparser interfaces.
* The subparsers are instantiated in `EscapeSequenceParser` and
* called during `EscapeSequenceParser.parse`.
*/
export interface ISubParser<T, U> extends IDisposable {
reset(): void;
start(): void;
addHandler(ident: number, handler: T): IDisposable;
setHandler(ident: number, handler: T): void;
clearHandler(ident: number): void;
setHandlerFallback(handler: U): void;
put(data: Uint32Array, start: number, end: number): void;
}
export interface IOscParser extends ISubParser<IOscHandler, OscFallbackHandlerType> {
start(): void;
end(success: boolean): void;
}
export interface IDcsParser extends IDisposable {
addDcsHandler(ident: number, handler: IDcsHandler): IDisposable;
setDcsHandler(ident: number, handler: IDcsHandler): void;
clearDcsHandler(ident: number): void;
setDcsHandlerFallback(handler: DcsFallbackHandler): void;
reset(): void;
export interface IDcsParser extends ISubParser<IDcsHandler, DcsFallbackHandlerType> {
hook(ident: number, params: IParams): void;
put(data: Uint32Array, start: number, end: number): void;
unhook(success: boolean): void;
}
/**
* Interface to denote a specific ESC, CSI or DCS handler slot.
* The values are used to create an integer respresentation during handler
* regristation before passed to the subparsers as `ident`.
* The integer translation is made to allow a faster handler access
* in `EscapeSequenceParser.parse`.
*/
export interface IFunctionIdentifier {
prefix?: string;
intermediates?: string;
final: string;
}
export interface IHandlerCollection<T> {
[key: string]: T[];
}
+26 -17
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm';
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier } from 'xterm';
import { ITerminal } from '../Types';
import { IBufferLine } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
@@ -11,11 +11,12 @@ import { Terminal as TerminalCore } from '../Terminal';
import * as Strings from '../browser/LocalizableStrings';
import { IEvent } from 'common/EventEmitter';
import { AddonManager } from './AddonManager';
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
import { IParams } from 'common/parser/Types';
export class Terminal implements ITerminalApi {
private _core: ITerminal;
private _addonManager: AddonManager;
private _parser: IParser;
constructor(options?: ITerminalOptions) {
this._core = new TerminalCore(options);
@@ -33,6 +34,12 @@ export class Terminal implements ITerminalApi {
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }
public get element(): HTMLElement { return this._core.element; }
public get parser(): IParser {
if (!this._parser) {
this._parser = new ParserApi(this._core);
}
return this._parser;
}
public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
public get rows(): number { return this._core.rows; }
public get cols(): number { return this._core.cols; }
@@ -57,18 +64,6 @@ export class Terminal implements ITerminalApi {
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
}
public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable {
return this._core.addCsiHandler(id, (params: IParams) => callback(params.toArray()));
}
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable {
return this._core.addDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray()));
}
public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
return this._core.addEscHandler(id, handler);
}
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this._core.addOscHandler(ident, callback);
}
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number {
return this._core.registerLinkMatcher(regex, handler, options);
}
@@ -170,9 +165,6 @@ export class Terminal implements ITerminalApi {
public reset(): void {
this._core.reset();
}
public static applyAddon(addon: any): void {
addon.apply(Terminal);
}
public loadAddon(addon: ITerminalAddon): void {
return this._addonManager.loadAddon(this, addon);
}
@@ -226,3 +218,20 @@ class BufferCellApiView implements IBufferCellApi {
public get char(): string { return this._line.getString(this._x); }
public get width(): number { return this._line.getWidth(this._x); }
}
class ParserApi implements IParser {
constructor(private _core: ITerminal) {}
public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable {
return this._core.addCsiHandler(id, (params: IParams) => callback(params.toArray()));
}
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable {
return this._core.addDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray()));
}
public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
return this._core.addEscHandler(id, handler);
}
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this._core.addOscHandler(ident, callback);
}
}
+12 -3
View File
@@ -165,7 +165,13 @@ export class DomRenderer extends Disposable implements IRenderer {
`}`;
// Blink animation
styles +=
`@keyframes blink {` +
`@keyframes blink_box_shadow {` +
` 50% {` +
` box-shadow: none;` +
` }` +
`}`;
styles +=
`@keyframes blink_block {` +
` 0% {` +
` background-color: ${this._colors.cursor.css};` +
` color: ${this._colors.cursorAccent.css};` +
@@ -181,8 +187,11 @@ export class DomRenderer extends Disposable implements IRenderer {
` outline: 1px solid ${this._colors.cursor.css};` +
` outline-offset: -1px;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS} {` +
` animation: blink 1s step-end infinite;` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` +
` animation: blink_box_shadow 1s step-end infinite;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` animation: blink_block 1s step-end infinite;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` background-color: ${this._colors.cursor.css};` +
-89
View File
@@ -334,95 +334,6 @@ describe('InputHandler Integration Tests', function(): void {
});
});
});
describe('addCsiHandler', () => {
it('should call custom CSI handler with js array params', async () => {
await page.evaluate(`
window.term.reset();
const _customCsiHandlerParams = [];
const _customCsiHandler = window.term.addCsiHandler({final: 'm'}, (params, collect) => {
_customCsiHandlerParams.push(params);
return false;
}, '');
`);
await page.evaluate(`
window.term.write('\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams');
`);
assert.deepEqual(await page.evaluate(`(() => _customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]);
});
});
describe('addDcsHandler', () => {
it('should respects return value', async () => {
await page.evaluate(`
window.term.reset();
const _customDcsHandlerCallStack = [];
const _customDcsHandlerA = window.term.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
_customDcsHandlerCallStack.push(['A', params, data]);
return false;
});
const _customDcsHandlerB = window.term.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
_customDcsHandlerCallStack.push(['B', params, data]);
return true;
});
const _customDcsHandlerC = window.term.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
_customDcsHandlerCallStack.push(['C', params, data]);
return false;
});
`);
await page.evaluate(`
window.term.write('\x1bP1;2+psome data\x1b\\\\');
`);
assert.deepEqual(await page.evaluate(`(() => _customDcsHandlerCallStack)();`), [['C', [1, 2], 'some data'], ['B', [1, 2], 'some data']]);
});
});
describe('addEscHandler', () => {
it('should respects return value', async () => {
await page.evaluate(`
window.term.reset();
const _customEscHandlerCallStack = [];
const _customEscHandlerA = window.term.addEscHandler({intermediates:'(', final: 'B'}, () => {
_customEscHandlerCallStack.push('A');
return false;
});
const _customEscHandlerB = window.term.addEscHandler({intermediates:'(', final: 'B'}, () => {
_customEscHandlerCallStack.push('B');
return true;
});
const _customEscHandlerC = window.term.addEscHandler({intermediates:'(', final: 'B'}, () => {
_customEscHandlerCallStack.push('C');
return false;
});
`);
await page.evaluate(`
window.term.write('\x1b(B');
`);
assert.deepEqual(await page.evaluate(`(() => _customEscHandlerCallStack)();`), ['C', 'B']);
});
});
describe('addOscHandler', () => {
it('should respects return value', async () => {
await page.evaluate(`
window.term.reset();
const _customOscHandlerCallStack = [];
const _customOscHandlerA = window.term.addOscHandler(1234, data => {
_customOscHandlerCallStack.push(['A', data]);
return false;
});
const _customOscHandlerB = window.term.addOscHandler(1234, data => {
_customOscHandlerCallStack.push(['B', data]);
return true;
});
const _customOscHandlerC = window.term.addOscHandler(1234, data => {
_customOscHandlerCallStack.push(['C', data]);
return false;
});
`);
await page.evaluate(`
window.term.write('\x1b]1234;some data\x07');
`);
assert.deepEqual(await page.evaluate(`(() => _customOscHandlerCallStack)();`), [['C', 'some data'], ['B', 'some data']]);
});
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
+134
View File
@@ -0,0 +1,134 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
const width = 800;
const height = 600;
describe('Parser Integration Tests', function(): void {
this.timeout(20000);
before(async function(): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
await page.goto(APP);
await openTerminal();
});
after(() => {
browser.close();
});
describe('addCsiHandler', () => {
it('should call custom CSI handler with js array params', async () => {
await page.evaluate(`
window.term.reset();
const _customCsiHandlerParams = [];
const _customCsiHandler = window.term.parser.addCsiHandler({final: 'm'}, (params, collect) => {
_customCsiHandlerParams.push(params);
return false;
}, '');
`);
await page.evaluate(`
window.term.write('\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams');
`);
assert.deepEqual(await page.evaluate(`(() => _customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]);
});
});
describe('addDcsHandler', () => {
it('should respects return value', async () => {
await page.evaluate(`
window.term.reset();
const _customDcsHandlerCallStack = [];
const _customDcsHandlerA = window.term.parser.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
_customDcsHandlerCallStack.push(['A', params, data]);
return false;
});
const _customDcsHandlerB = window.term.parser.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
_customDcsHandlerCallStack.push(['B', params, data]);
return true;
});
const _customDcsHandlerC = window.term.parser.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
_customDcsHandlerCallStack.push(['C', params, data]);
return false;
});
`);
await page.evaluate(`
window.term.write('\x1bP1;2+psome data\x1b\\\\');
`);
assert.deepEqual(await page.evaluate(`(() => _customDcsHandlerCallStack)();`), [['C', [1, 2], 'some data'], ['B', [1, 2], 'some data']]);
});
});
describe('addEscHandler', () => {
it('should respects return value', async () => {
await page.evaluate(`
window.term.reset();
const _customEscHandlerCallStack = [];
const _customEscHandlerA = window.term.parser.addEscHandler({intermediates:'(', final: 'B'}, () => {
_customEscHandlerCallStack.push('A');
return false;
});
const _customEscHandlerB = window.term.parser.addEscHandler({intermediates:'(', final: 'B'}, () => {
_customEscHandlerCallStack.push('B');
return true;
});
const _customEscHandlerC = window.term.parser.addEscHandler({intermediates:'(', final: 'B'}, () => {
_customEscHandlerCallStack.push('C');
return false;
});
`);
await page.evaluate(`
window.term.write('\x1b(B');
`);
assert.deepEqual(await page.evaluate(`(() => _customEscHandlerCallStack)();`), ['C', 'B']);
});
});
describe('addOscHandler', () => {
it('should respects return value', async () => {
await page.evaluate(`
window.term.reset();
const _customOscHandlerCallStack = [];
const _customOscHandlerA = window.term.parser.addOscHandler(1234, data => {
_customOscHandlerCallStack.push(['A', data]);
return false;
});
const _customOscHandlerB = window.term.parser.addOscHandler(1234, data => {
_customOscHandlerCallStack.push(['B', data]);
return true;
});
const _customOscHandlerC = window.term.parser.addOscHandler(1234, data => {
_customOscHandlerCallStack.push(['C', data]);
return false;
});
`);
await page.evaluate(`
window.term.write('\x1b]1234;some data\x07');
`);
assert.deepEqual(await page.evaluate(`(() => _customOscHandlerCallStack)();`), [['C', 'some data'], ['B', 'some data']]);
});
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
if (options.rendererType === 'dom') {
await page.waitForSelector('.xterm-rows');
} else {
await page.waitForSelector('.xterm-text-layer');
}
}
@@ -7,7 +7,7 @@ import { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-be
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
import { C0, C1 } from 'common/data/EscapeSequences';
import { IDcsHandler, IParams } from 'common/parser/Types';
import { OscHandlerFactory } from 'common/parser/OscParser';
import { OscHandler } from 'common/parser/OscParser';
function toUtf32(s: string): Uint32Array {
@@ -80,8 +80,8 @@ perfContext('Parser throughput - 50MB data', () => {
parser.setExecuteHandler(C1.IND, () => {});
parser.setExecuteHandler(C1.NEL, () => {});
parser.setExecuteHandler(C1.HTS, () => {});
parser.setOscHandler(0, new OscHandlerFactory((data) => {}));
parser.setOscHandler(2, new OscHandlerFactory((data) => {}));
parser.setOscHandler(0, new OscHandler((data) => {}));
parser.setOscHandler(2, new OscHandler((data) => {}));
parser.setEscHandler({final: '7'}, () => {});
parser.setEscHandler({final: '8'}, () => {});
parser.setEscHandler({final: 'D'}, () => {});
+104 -100
View File
@@ -207,47 +207,47 @@ declare module 'xterm' {
*/
export interface ITheme {
/** The default foreground color */
foreground?: string,
foreground?: string;
/** The default background color */
background?: string,
background?: string;
/** The cursor color */
cursor?: string,
cursor?: string;
/** The accent color of the cursor (fg color for a block cursor) */
cursorAccent?: string,
cursorAccent?: string;
/** The selection background color (can be transparent) */
selection?: string,
selection?: string;
/** ANSI black (eg. `\x1b[30m`) */
black?: string,
black?: string;
/** ANSI red (eg. `\x1b[31m`) */
red?: string,
red?: string;
/** ANSI green (eg. `\x1b[32m`) */
green?: string,
green?: string;
/** ANSI yellow (eg. `\x1b[33m`) */
yellow?: string,
yellow?: string;
/** ANSI blue (eg. `\x1b[34m`) */
blue?: string,
blue?: string;
/** ANSI magenta (eg. `\x1b[35m`) */
magenta?: string,
magenta?: string;
/** ANSI cyan (eg. `\x1b[36m`) */
cyan?: string,
cyan?: string;
/** ANSI white (eg. `\x1b[37m`) */
white?: string,
white?: string;
/** ANSI bright black (eg. `\x1b[1;30m`) */
brightBlack?: string,
brightBlack?: string;
/** ANSI bright red (eg. `\x1b[1;31m`) */
brightRed?: string,
brightRed?: string;
/** ANSI bright green (eg. `\x1b[1;32m`) */
brightGreen?: string,
brightGreen?: string;
/** ANSI bright yellow (eg. `\x1b[1;33m`) */
brightYellow?: string,
brightYellow?: string;
/** ANSI bright blue (eg. `\x1b[1;34m`) */
brightBlue?: string,
brightBlue?: string;
/** ANSI bright magenta (eg. `\x1b[1;35m`) */
brightMagenta?: string,
brightMagenta?: string;
/** ANSI bright cyan (eg. `\x1b[1;36m`) */
brightCyan?: string,
brightCyan?: string;
/** ANSI bright white (eg. `\x1b[1;37m`) */
brightWhite?: string
brightWhite?: string;
}
/**
@@ -386,6 +386,12 @@ declare module 'xterm' {
*/
readonly markers: ReadonlyArray<IMarker>;
/**
* (EXPERIMENTAL) Get the parser interface to register
* custom escape sequence handlers.
*/
readonly parser: IParser;
/**
* Natural language strings that can be localized.
*/
@@ -500,70 +506,6 @@ declare module 'xterm' {
*/
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
/**
* Adds a handler for CSI escape sequences.
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {final: 'm'} for SGR.
* @param callback The function to handle the sequence. The callback is
* called with the numerical params. If the sequence has subparams the
* array will contain subarrays with their numercial values.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addCsiHandler or setCsiHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable;
/**
* Adds a handler for DCS escape sequences.
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS.
* @param callback The function to handle the sequence. Note that the
* function will only be called once if the sequence finished sucessfully.
* There is currently no way to intercept smaller data chunks, data chunks
* will be stored up until the sequence is finished. Since DCS sequences
* are not limited by the amount of data this might impose a problem for
* big payloads. Currently xterm.js limits DCS payload to 10 MB
* which should give enough room for most use cases.
* The function gets the payload and numerical parameters as arguments.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addDcsHandler or setDcsHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable;
/**
* Adds a handler for ESC escape sequences.
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {intermediates: '%' final: 'G'} for
* default charset selection.
* @param callback The function to handle the sequence.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addEscHandler or setEscHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable;
/**
* Adds a handler for OSC escape sequences.
* @param ident The number (first parameter) of the sequence.
* @param callback The function to handle the sequence. Note that the
* function will only be called once if the sequence finished sucessfully.
* There is currently no way to intercept smaller data chunks, data chunks
* will be stored up until the sequence is finished. Since OSC sequences
* are not limited by the amount of data this might impose a problem for
* big payloads. Currently xterm.js limits OSC payload to 10 MB
* which should give enough room for most use cases.
* The callback is called with OSC data string.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addOscHandler or setOscHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
/**
* (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to
* be matched and handled.
@@ -842,18 +784,10 @@ declare module 'xterm' {
/**
* Perform a full reset (RIS, aka '\x1bc').
*/
reset(): void
reset(): void;
/**
* Applies an addon to the Terminal prototype, making it available to all
* newly created Terminals.
* @param addon The addon to apply.
* @deprecated Use the new loadAddon API/addon format.
*/
static applyAddon(addon: any): void;
/**
* (EXPERIMENTAL) Loads an addon into this instance of xterm.js.
* Loads an addon into this instance of xterm.js.
* @param addon The addon to load.
*/
loadAddon(addon: ITerminalAddon): void;
@@ -991,17 +925,18 @@ declare module 'xterm' {
}
/**
* Data type to register a CSI, DCS or ESC callback in the parser in the form:
* (EXPERIMENTAL) Data type to register a CSI, DCS or ESC callback in the parser
* in the form:
* ESC I..I F
* CSI Prefix P..P I..I F
* DCS Prefix P..P I..I F data_bytes ST
*
*
* with these rules/restrictions:
* - prefix can only be used with CSI and DCS
* - only one leading prefix byte is recognized by the parser
* before any other parameter bytes (P..P)
* - intermediate bytes are recognized up to 2
*
*
* For custom sequences make sure to read ECMA-48 and the resources at
* vt100.net to not clash with existing sequences or reserved address space.
* General recommendations:
@@ -1011,9 +946,9 @@ declare module 'xterm' {
* thus parsers might get confused with more intermediates)
* - test against other common emulators to check whether they escape/ignore
* the sequence correctly
*
*
* Notes: OSC command registration is handled differently (see addOscHandler)
* APC, PM or SOS is currently not supported
* APC, PM or SOS is currently not supported.
*/
export interface IFunctionIdentifier {
/**
@@ -1032,4 +967,73 @@ declare module 'xterm' {
*/
final: string;
}
/**
* (EXPERIMENTAL) Parser interface.
*/
export interface IParser {
/**
* Adds a handler for CSI escape sequences.
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {final: 'm'} for SGR.
* @param callback The function to handle the sequence. The callback is
* called with the numerical params. If the sequence has subparams the
* array will contain subarrays with their numercial values.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addCsiHandler or setCsiHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable;
/**
* Adds a handler for DCS escape sequences.
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS.
* @param callback The function to handle the sequence. Note that the
* function will only be called once if the sequence finished sucessfully.
* There is currently no way to intercept smaller data chunks, data chunks
* will be stored up until the sequence is finished. Since DCS sequences
* are not limited by the amount of data this might impose a problem for
* big payloads. Currently xterm.js limits DCS payload to 10 MB
* which should give enough room for most use cases.
* The function gets the payload and numerical parameters as arguments.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addDcsHandler or setDcsHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable;
/**
* Adds a handler for ESC escape sequences.
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {intermediates: '%' final: 'G'} for
* default charset selection.
* @param callback The function to handle the sequence.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addEscHandler or setEscHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable;
/**
* Adds a handler for OSC escape sequences.
* @param ident The number (first parameter) of the sequence.
* @param callback The function to handle the sequence. Note that the
* function will only be called once if the sequence finished sucessfully.
* There is currently no way to intercept smaller data chunks, data chunks
* will be stored up until the sequence is finished. Since OSC sequences
* are not limited by the amount of data this might impose a problem for
* big payloads. Currently xterm.js limits OSC payload to 10 MB
* which should give enough room for most use cases.
* The callback is called with OSC data string.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addOscHandler or setOscHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
}
}