Skip to content

Commit 4d660de

Browse files
committed
hooks for custom control sequences
This fixes (at least partially) issue xtermjs#1176 "Add a way to plugin a custom control sequence handler".
1 parent b5f1c33 commit 4d660de

File tree

5 files changed

+104
-12
lines changed

5 files changed

+104
-12
lines changed

src/EscapeSequenceParser.ts

+66-8
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,39 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
301301
this._executeHandlerFb = callback;
302302
}
303303

304+
private _removeHandler(array: any[], callback: any): void {
305+
if (array) {
306+
for (let i = array.length; --i >= 0; ) {
307+
if (array[i] == callback) {
308+
array.splice(i, 1);
309+
return;
310+
}
311+
}
312+
}
313+
}
314+
315+
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
316+
let index = flag.charCodeAt(0);
317+
let array = this._csiHandlers[index];
318+
if (! array) { this._csiHandlers[index] = array = new Array(); }
319+
array.push(callback);
320+
}
321+
322+
removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
323+
let index = flag.charCodeAt(0);
324+
let array = this._csiHandlers[index];
325+
this._removeHandler(array, callback);
326+
if (array && array.length == 0)
327+
delete this._csiHandlers[index];
328+
}
329+
/* deprecated */
304330
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
305-
this._csiHandlers[flag.charCodeAt(0)] = callback;
331+
this.clearCsiHandler(flag);
332+
this.addCsiHandler(flag, (params: number[], collect: string): boolean => {
333+
callback(params, collect); return true;
334+
});
306335
}
336+
/* deprecated */
307337
clearCsiHandler(flag: string): void {
308338
if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
309339
}
@@ -321,9 +351,25 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
321351
this._escHandlerFb = callback;
322352
}
323353

354+
addOscHandler(ident: number, callback: (data: string) => boolean): void {
355+
let array = this._oscHandlers[ident];
356+
if (! array) { this._oscHandlers[ident] = array = new Array(); }
357+
array.push(callback);
358+
}
359+
removeOscHandler(ident: number, callback: (data: string) => boolean): void {
360+
let array = this._oscHandlers[ident];
361+
this._removeHandler(array, callback);
362+
if (array && array.length == 0)
363+
delete this._oscHandlers[ident];
364+
}
365+
/* deprecated */
324366
setOscHandler(ident: number, callback: (data: string) => void): void {
325-
this._oscHandlers[ident] = callback;
367+
this.clearOscHandler(ident);
368+
this.addOscHandler(ident, (data: string): boolean => {
369+
callback(data); return true;
370+
});
326371
}
372+
/* deprecated */
327373
clearOscHandler(ident: number): void {
328374
if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
329375
}
@@ -463,9 +509,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
463509
}
464510
break;
465511
case ParserAction.CSI_DISPATCH:
466-
callback = this._csiHandlers[code];
467-
if (callback) callback(params, collect);
468-
else this._csiHandlerFb(collect, params, code);
512+
let cHandler = this._csiHandlers[code];
513+
if (cHandler) {
514+
for (let i = cHandler.length; ;) {
515+
if (--i < 0) { cHandler = null; break; }
516+
if ((cHandler[i])(params, collect))
517+
break;
518+
}
519+
}
520+
if (! cHandler) this._csiHandlerFb(collect, params, code);
469521
break;
470522
case ParserAction.PARAM:
471523
if (code === 0x3b) params.push(0);
@@ -532,9 +584,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
532584
// or with an explicit NaN OSC handler
533585
const identifier = parseInt(osc.substring(0, idx));
534586
const content = osc.substring(idx + 1);
535-
callback = this._oscHandlers[identifier];
536-
if (callback) callback(content);
537-
else this._oscHandlerFb(identifier, content);
587+
let oHandler = this._oscHandlers[identifier];
588+
if (oHandler) {
589+
for (let i = oHandler.length; ;) {
590+
if (--i < 0) { oHandler = null; break; }
591+
if ((oHandler[i])(content))
592+
break;
593+
}
594+
}
595+
if (! oHandler) this._oscHandlerFb(identifier, content);
538596
}
539597
}
540598
if (code === 0x1b) transition |= ParserState.ESCAPE;

src/InputHandler.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* @license MIT
55
*/
66

7-
import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
7+
import { IVtInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
88
import { C0, C1 } from './common/data/EscapeSequences';
99
import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
1010
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer';
@@ -112,7 +112,7 @@ class DECRQSS implements IDcsHandler {
112112
* Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand
113113
* each function's header comment.
114114
*/
115-
export class InputHandler extends Disposable implements IInputHandler {
115+
export class InputHandler extends Disposable implements IVtInputHandler {
116116
private _surrogateFirst: string;
117117

118118
constructor(
@@ -465,6 +465,19 @@ export class InputHandler extends Disposable implements IInputHandler {
465465
this._terminal.updateRange(buffer.y);
466466
}
467467

468+
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
469+
this._parser.addCsiHandler(flag, callback);
470+
}
471+
removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
472+
this._parser.removeCsiHandler(flag, callback);
473+
}
474+
addOscHandler(ident: number, callback: (data: string) => boolean): void {
475+
this._parser.setOscHandler(ident, callback);
476+
}
477+
removeOscHandler(ident: number, callback: (data: string) => boolean): void {
478+
this._parser.removeOscHandler(ident, callback);
479+
}
480+
468481
/**
469482
* BEL
470483
* Bell (Ctrl-G).

src/Terminal.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* http://linux.die.net/man/7/urxvt
2222
*/
2323

24-
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
24+
import { IInputHandlingTerminal, IInputHandler, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
2525
import { IMouseZoneManager } from './ui/Types';
2626
import { IRenderer } from './renderer/Types';
2727
import { BufferSet } from './BufferSet';
@@ -1286,6 +1286,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
12861286
this.refresh(0, this.rows - 1);
12871287
}
12881288

1289+
public get inputHandler(): IInputHandler {
1290+
return this._inputHandler;
1291+
}
1292+
12891293
/**
12901294
* Scroll the display of the terminal by a number of pages.
12911295
* @param pageCount The number of pages to scroll (negative scrolls up).

src/Types.ts

+14
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,16 @@ export interface IInputHandler {
182182
ESC ~ */ setgLevel(level: number): void;
183183
}
184184

185+
/*
186+
* An InputHandler for VT-style terminals
187+
*/
188+
export interface IVtInputHandler extends IInputHandler {
189+
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void;
190+
removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void;
191+
addOscHandler(ident: number, callback: (data: string) => boolean): void;
192+
removeOscHandler(ident: number, callback: (data: string) => boolean): void;
193+
}
194+
185195
export interface ILinkMatcher {
186196
id: number;
187197
regex: RegExp;
@@ -492,6 +502,10 @@ export interface IEscapeSequenceParser extends IDisposable {
492502
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void;
493503
clearCsiHandler(flag: string): void;
494504
setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void;
505+
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void;
506+
removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void;
507+
addOscHandler(ident: number, callback: (data: string) => boolean): void;
508+
removeOscHandler(ident: number, callback: (data: string) => boolean): void;
495509

496510
setEscHandler(collectAndFlag: string, callback: () => void): void;
497511
clearEscHandler(collectAndFlag: string): void;

src/public/Terminal.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm';
7-
import { ITerminal } from '../Types';
7+
import { ITerminal, IInputHandler } from '../Types';
88
import { Terminal as TerminalCore } from '../Terminal';
99
import * as Strings from '../Strings';
1010

@@ -15,6 +15,9 @@ export class Terminal implements ITerminalApi {
1515
this._core = new TerminalCore(options);
1616
}
1717

18+
public get inputHandler(): IInputHandler {
19+
return (this._core as TerminalCore).inputHandler;
20+
}
1821
public get element(): HTMLElement { return this._core.element; }
1922
public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
2023
public get rows(): number { return this._core.rows; }

0 commit comments

Comments
 (0)