diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts
index 13da69616b..87d911c2a4 100644
--- a/fixtures/typings-test/typings-test.ts
+++ b/fixtures/typings-test/typings-test.ts
@@ -4,7 +4,7 @@
///
-import { Terminal } from 'xterm';
+import { Terminal, IDisposable } from 'xterm';
namespace constructor {
{
@@ -119,6 +119,12 @@ namespace methods_core {
const t: Terminal = new Terminal();
t.attachCustomKeyEventHandler((e: KeyboardEvent) => true);
t.attachCustomKeyEventHandler((e: KeyboardEvent) => false);
+ const d1: IDisposable = t.addCsiHandler("x",
+ (params: number[], collect: string): boolean => params[0]===1);
+ d1.dispose();
+ const d2: IDisposable = t.addOscHandler(199,
+ (data: string): boolean => true);
+ d2.dispose();
}
namespace options {
{
diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts
index e92f412574..0f1d63cc0d 100644
--- a/src/EscapeSequenceParser.test.ts
+++ b/src/EscapeSequenceParser.test.ts
@@ -1169,6 +1169,73 @@ describe('EscapeSequenceParser', function (): void {
parser2.parse(INPUT);
chai.expect(csi).eql([]);
});
+ describe('CSI custom handlers', () => {
+ it('Prevent fallback', () => {
+ const csiCustom: [string, number[], string][] = [];
+ parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
+ parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
+ parser2.parse(INPUT);
+ chai.expect(csi).eql([], 'Should not fallback to original handler');
+ chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
+ });
+ it('Allow fallback', () => {
+ const csiCustom: [string, number[], string][] = [];
+ parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
+ parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return false; });
+ parser2.parse(INPUT);
+ chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler');
+ chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
+ });
+ it('Multiple custom handlers fallback once', () => {
+ const csiCustom: [string, number[], string][] = [];
+ const csiCustom2: [string, number[], string][] = [];
+ parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
+ parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
+ parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return false; });
+ parser2.parse(INPUT);
+ chai.expect(csi).eql([], 'Should not fallback to original handler');
+ chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
+ chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]);
+ });
+ it('Multiple custom handlers no fallback', () => {
+ const csiCustom: [string, number[], string][] = [];
+ const csiCustom2: [string, number[], string][] = [];
+ parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
+ parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
+ parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return true; });
+ parser2.parse(INPUT);
+ chai.expect(csi).eql([], 'Should not fallback to original handler');
+ chai.expect(csiCustom).eql([], 'Should not fallback once');
+ chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]);
+ });
+ it('Execution order should go from latest handler down to the original', () => {
+ const order: number[] = [];
+ parser2.setCsiHandler('m', () => order.push(1));
+ parser2.addCsiHandler('m', () => { order.push(2); return false; });
+ parser2.addCsiHandler('m', () => { order.push(3); return false; });
+ parser2.parse('\x1b[0m');
+ chai.expect(order).eql([3, 2, 1]);
+ });
+ it('Dispose should work', () => {
+ const csiCustom: [string, number[], string][] = [];
+ parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
+ const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
+ customHandler.dispose();
+ parser2.parse(INPUT);
+ chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
+ chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed');
+ });
+ it('Should not corrupt the parser when dispose is called twice', () => {
+ const csiCustom: [string, number[], string][] = [];
+ parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
+ const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
+ customHandler.dispose();
+ customHandler.dispose();
+ parser2.parse(INPUT);
+ chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
+ chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed');
+ });
+ });
it('EXECUTE handler', function (): void {
parser2.setExecuteHandler('\n', function (): void {
exe.push('\n');
@@ -1196,6 +1263,73 @@ describe('EscapeSequenceParser', function (): void {
parser2.parse(INPUT);
chai.expect(osc).eql([]);
});
+ describe('OSC custom handlers', () => {
+ it('Prevent fallback', () => {
+ const oscCustom: [number, string][] = [];
+ parser2.setOscHandler(1, data => osc.push([1, data]));
+ parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
+ parser2.parse(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, data => osc.push([1, data]));
+ parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return false; });
+ parser2.parse(INPUT);
+ chai.expect(osc).eql([[1, 'foo=bar']], 'Should fallback to original handler');
+ chai.expect(oscCustom).eql([[1, 'foo=bar']]);
+ });
+ it('Multiple custom handlers fallback once', () => {
+ const oscCustom: [number, string][] = [];
+ const oscCustom2: [number, string][] = [];
+ parser2.setOscHandler(1, data => osc.push([1, data]));
+ parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
+ parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return false; });
+ parser2.parse(INPUT);
+ chai.expect(osc).eql([], 'Should not fallback to original handler');
+ chai.expect(oscCustom).eql([[1, 'foo=bar']]);
+ chai.expect(oscCustom2).eql([[1, 'foo=bar']]);
+ });
+ it('Multiple custom handlers no fallback', () => {
+ const oscCustom: [number, string][] = [];
+ const oscCustom2: [number, string][] = [];
+ parser2.setOscHandler(1, data => osc.push([1, data]));
+ parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
+ parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return true; });
+ parser2.parse(INPUT);
+ chai.expect(osc).eql([], 'Should not fallback to original handler');
+ chai.expect(oscCustom).eql([], 'Should not fallback once');
+ chai.expect(oscCustom2).eql([[1, 'foo=bar']]);
+ });
+ it('Execution order should go from latest handler down to the original', () => {
+ const order: number[] = [];
+ parser2.setOscHandler(1, () => order.push(1));
+ parser2.addOscHandler(1, () => { order.push(2); return false; });
+ parser2.addOscHandler(1, () => { order.push(3); return false; });
+ parser2.parse('\x1b]1;foo=bar\x1b\\');
+ chai.expect(order).eql([3, 2, 1]);
+ });
+ it('Dispose should work', () => {
+ const oscCustom: [number, string][] = [];
+ parser2.setOscHandler(1, data => osc.push([1, data]));
+ const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
+ customHandler.dispose();
+ parser2.parse(INPUT);
+ chai.expect(osc).eql([[1, 'foo=bar']]);
+ chai.expect(oscCustom).eql([], 'Should not use custom handler as it was disposed');
+ });
+ it('Should not corrupt the parser when dispose is called twice', () => {
+ const oscCustom: [number, string][] = [];
+ parser2.setOscHandler(1, data => osc.push([1, data]));
+ const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
+ customHandler.dispose();
+ customHandler.dispose();
+ parser2.parse(INPUT);
+ chai.expect(osc).eql([[1, 'foo=bar']]);
+ chai.expect(oscCustom).eql([], 'Should not use custom handler as it was disposed');
+ });
+ });
it('DCS handler', function (): void {
parser2.setDcsHandler('+p', {
hook: function (collect: string, params: number[], flag: number): void {
diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index f489884177..6d3de0e0ed 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -4,8 +4,16 @@
*/
import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';
+import { IDisposable } from 'xterm';
import { Disposable } from './common/Lifecycle';
+interface IHandlerCollection {
+ [key: string]: T[];
+}
+
+type CsiHandler = (params: number[], collect: string) => boolean | void;
+type OscHandler = (data: string) => boolean | void;
+
/**
* Returns an array filled with numbers between the low and high parameters (right exclusive).
* @param low The low number.
@@ -222,9 +230,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
// handler lookup containers
protected _printHandler: (data: string, start: number, end: number) => void;
protected _executeHandlers: any;
- protected _csiHandlers: any;
+ protected _csiHandlers: IHandlerCollection;
protected _escHandlers: any;
- protected _oscHandlers: any;
+ protected _oscHandlers: IHandlerCollection;
protected _dcsHandlers: any;
protected _activeDcsHandler: IDcsHandler | null;
protected _errorHandler: (state: IParsingState) => IParsingState;
@@ -278,8 +286,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._errorHandlerFb = null;
this._printHandler = null;
this._executeHandlers = null;
- this._csiHandlers = null;
this._escHandlers = null;
+ this._csiHandlers = null;
this._oscHandlers = null;
this._dcsHandlers = null;
this._activeDcsHandler = null;
@@ -303,8 +311,24 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._executeHandlerFb = callback;
}
+ addCsiHandler(flag: string, callback: CsiHandler): IDisposable {
+ const index = flag.charCodeAt(0);
+ if (this._csiHandlers[index] === undefined) {
+ this._csiHandlers[index] = [];
+ }
+ const handlerList = this._csiHandlers[index];
+ handlerList.push(callback);
+ return {
+ dispose: () => {
+ const handlerIndex = handlerList.indexOf(callback);
+ if (handlerIndex !== -1) {
+ handlerList.splice(handlerIndex, 1);
+ }
+ }
+ };
+ }
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
- this._csiHandlers[flag.charCodeAt(0)] = callback;
+ this._csiHandlers[flag.charCodeAt(0)] = [callback];
}
clearCsiHandler(flag: string): void {
if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
@@ -323,8 +347,23 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._escHandlerFb = callback;
}
+ addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
+ if (this._oscHandlers[ident] === undefined) {
+ this._oscHandlers[ident] = [];
+ }
+ const handlerList = this._oscHandlers[ident];
+ handlerList.push(callback);
+ return {
+ dispose: () => {
+ const handlerIndex = handlerList.indexOf(callback);
+ if (handlerIndex !== -1) {
+ handlerList.splice(handlerIndex, 1);
+ }
+ }
+ };
+ }
setOscHandler(ident: number, callback: (data: string) => void): void {
- this._oscHandlers[ident] = callback;
+ this._oscHandlers[ident] = [callback];
}
clearOscHandler(ident: number): void {
if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
@@ -461,9 +500,17 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
}
break;
case ParserAction.CSI_DISPATCH:
- callback = this._csiHandlers[code];
- if (callback) callback(params, collect);
- else this._csiHandlerFb(collect, params, code);
+ // Trigger CSI Handler
+ const handlers = this._csiHandlers[code];
+ let j = handlers ? handlers.length - 1 : -1;
+ for (; j >= 0; j--) {
+ if (handlers[j](params, collect)) {
+ break;
+ }
+ }
+ if (j < 0) {
+ this._csiHandlerFb(collect, params, code);
+ }
break;
case ParserAction.PARAM:
if (code === 0x3b) params.push(0);
@@ -538,9 +585,17 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
// or with an explicit NaN OSC handler
const identifier = parseInt(osc.substring(0, idx));
const content = osc.substring(idx + 1);
- callback = this._oscHandlers[identifier];
- if (callback) callback(content);
- else this._oscHandlerFb(identifier, content);
+ // Trigger OSC Handler
+ const handlers = this._oscHandlers[identifier];
+ let j = handlers ? handlers.length - 1 : -1;
+ for (; j >= 0; j--) {
+ if (handlers[j](content)) {
+ break;
+ }
+ }
+ if (j < 0) {
+ this._oscHandlerFb(identifier, content);
+ }
}
}
if (code === 0x1b) transition |= ParserState.ESCAPE;
diff --git a/src/InputHandler.ts b/src/InputHandler.ts
index 7604b01f69..eb1ca1050b 100644
--- a/src/InputHandler.ts
+++ b/src/InputHandler.ts
@@ -12,6 +12,7 @@ import { FLAGS } from './renderer/Types';
import { wcwidth } from './CharWidth';
import { EscapeSequenceParser } from './EscapeSequenceParser';
import { ICharset } from './core/Types';
+import { IDisposable } from 'xterm';
import { Disposable } from './common/Lifecycle';
/**
@@ -465,6 +466,13 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.updateRange(buffer.y);
}
+ addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
+ return this._parser.addCsiHandler(flag, callback);
+ }
+ addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
+ return this._parser.addOscHandler(ident, callback);
+ }
+
/**
* BEL
* Bell (Ctrl-G).
diff --git a/src/Terminal.ts b/src/Terminal.ts
index bc8fb103a2..bed45e46ec 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -1413,6 +1413,15 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
this._customKeyEventHandler = customKeyEventHandler;
}
+ /** Add handler for CSI escape sequence. See xterm.d.ts for details. */
+ public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
+ return this._inputHandler.addCsiHandler(flag, callback);
+ }
+ /** Add handler for OSC escape sequence. See xterm.d.ts for details. */
+ public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
+ return this._inputHandler.addOscHandler(ident, callback);
+ }
+
/**
* Registers a link matcher, allowing custom link patterns to be matched and
* handled.
diff --git a/src/Types.ts b/src/Types.ts
index 8ebb28d3e3..b54b9d6610 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -492,6 +492,8 @@ export interface IEscapeSequenceParser extends IDisposable {
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void;
clearCsiHandler(flag: string): void;
setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void;
+ addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
+ addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
setEscHandler(collectAndFlag: string, callback: () => void): void;
clearEscHandler(collectAndFlag: string): void;
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 8ff7cf2b3b..87fcfaef99 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -59,6 +59,12 @@ export class Terminal implements ITerminalApi {
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
}
+ public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
+ return this._core.addCsiHandler(flag, callback);
+ }
+ 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);
}
diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts
index 2afdebb54e..3e0b864307 100644
--- a/src/renderer/BaseRenderLayer.ts
+++ b/src/renderer/BaseRenderLayer.ts
@@ -236,12 +236,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void {
this._ctx.font = this._getFont(terminal, false, false);
- this._ctx.textBaseline = 'top';
+ this._ctx.textBaseline = 'middle';
this._clipRow(terminal, y);
this._ctx.fillText(
charData[CHAR_DATA_CHAR_INDEX],
x * this._scaledCellWidth + this._scaledCharLeft,
- y * this._scaledCellHeight + this._scaledCharTop);
+ (y + 0.5) * this._scaledCellHeight + this._scaledCharTop);
}
/**
@@ -295,7 +295,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
private _drawUncachedChars(terminal: ITerminal, chars: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean): void {
this._ctx.save();
this._ctx.font = this._getFont(terminal, bold, italic);
- this._ctx.textBaseline = 'top';
+ this._ctx.textBaseline = 'middle';
if (fg === INVERTED_DEFAULT_COLOR) {
this._ctx.fillStyle = this._colors.background.css;
@@ -316,7 +316,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.fillText(
chars,
x * this._scaledCellWidth + this._scaledCharLeft,
- y * this._scaledCellHeight + this._scaledCharTop);
+ (y + 0.5) * this._scaledCellHeight + this._scaledCharTop);
this._ctx.restore();
}
diff --git a/src/renderer/atlas/CharAtlasGenerator.ts b/src/renderer/atlas/CharAtlasGenerator.ts
index e40215cfe8..cadcce2ec1 100644
--- a/src/renderer/atlas/CharAtlasGenerator.ts
+++ b/src/renderer/atlas/CharAtlasGenerator.ts
@@ -29,7 +29,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
ctx.save();
ctx.fillStyle = config.colors.foreground.css;
ctx.font = getFont(config.fontWeight, config);
- ctx.textBaseline = 'top';
+ ctx.textBaseline = 'middle';
// Default color
for (let i = 0; i < 256; i++) {
@@ -37,7 +37,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
ctx.beginPath();
ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);
ctx.clip();
- ctx.fillText(String.fromCharCode(i), i * cellWidth, 0);
+ ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight / 2);
ctx.restore();
}
// Default color bold
@@ -48,7 +48,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
ctx.beginPath();
ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);
ctx.clip();
- ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight);
+ ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight * 1.5);
ctx.restore();
}
ctx.restore();
@@ -64,7 +64,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
ctx.clip();
ctx.fillStyle = config.colors.ansi[colorIndex].css;
- ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
+ ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2);
ctx.restore();
}
}
@@ -80,7 +80,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
ctx.clip();
ctx.fillStyle = config.colors.ansi[colorIndex].css;
- ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
+ ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2);
ctx.restore();
}
}
diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts
index 72010768a3..e311c369a3 100644
--- a/src/renderer/atlas/DynamicCharAtlas.ts
+++ b/src/renderer/atlas/DynamicCharAtlas.ts
@@ -250,7 +250,7 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
const fontStyle = glyph.italic ? 'italic' : '';
this._tmpCtx.font =
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
- this._tmpCtx.textBaseline = 'top';
+ this._tmpCtx.textBaseline = 'middle';
this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css;
@@ -259,7 +259,7 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
this._tmpCtx.globalAlpha = DIM_OPACITY;
}
// Draw the character
- this._tmpCtx.fillText(glyph.chars, 0, 0);
+ this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight / 2);
this._tmpCtx.restore();
// clear the background from the character to avoid issues with drawing over the previous
diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts
index a0cefd677c..c5ef212da6 100644
--- a/src/renderer/dom/DomRenderer.ts
+++ b/src/renderer/dom/DomRenderer.ts
@@ -364,9 +364,11 @@ export class DomRenderer extends EventEmitter implements IRenderer {
return;
}
const span = row.children[x];
- span.style.textDecoration = enabled ? 'underline' : 'none';
- x = (x + 1) % cols;
- if (x === 0) {
+ if (span) {
+ span.style.textDecoration = enabled ? 'underline' : 'none';
+ }
+ if (++x >= cols) {
+ x = 0;
y++;
}
}
diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts
index 10033a3355..e6e4aaa32f 100644
--- a/src/ui/TestUtils.test.ts
+++ b/src/ui/TestUtils.test.ts
@@ -54,6 +54,12 @@ export class MockTerminal implements ITerminal {
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
throw new Error('Method not implemented.');
}
+ addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
+ throw new Error('Method not implemented.');
+ }
+ addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
+ throw new Error('Method not implemented.');
+ }
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
throw new Error('Method not implemented.');
}
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 7528bb5540..0ceab01dcc 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -481,6 +481,29 @@ declare module 'xterm' {
*/
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
+ /**
+ * (EXPERIMENTAL) Adds a handler for CSI escape sequences.
+ * @param flag The flag should be one-character string, which specifies the
+ * final character (e.g "m" for SGR) of the CSI sequence.
+ * @param callback The function to handle the escape sequence. The callback
+ * is called with the numerical params, as well as the special characters
+ * (e.g. "$" for DECSCPP). 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(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
+
+ /**
+ * (EXPERIMENTAL) Adds a handler for OSC escape sequences.
+ * @param ident The number (first parameter) of the sequence.
+ * @param callback The function to handle the escape sequence. 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.