From 4d660deff3db9f3eea3923dcaa970fde0e9deaf8 Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Tue, 4 Dec 2018 19:00:44 -0800
Subject: [PATCH 01/14] hooks for custom control sequences

This fixes (at least partially) issue #1176
"Add a way to plugin a custom control sequence handler".
---
 src/EscapeSequenceParser.ts | 74 +++++++++++++++++++++++++++++++++----
 src/InputHandler.ts         | 17 ++++++++-
 src/Terminal.ts             |  6 ++-
 src/Types.ts                | 14 +++++++
 src/public/Terminal.ts      |  5 ++-
 5 files changed, 104 insertions(+), 12 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index b38c50f543..7a5da2c851 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -301,9 +301,39 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     this._executeHandlerFb = callback;
   }
 
+  private _removeHandler(array: any[], callback: any): void {
+    if (array) {
+      for (let i = array.length; --i >= 0; ) {
+        if (array[i] == callback) {
+          array.splice(i, 1);
+            return;
+        }
+      }
+    }
+  }
+
+  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
+    let index = flag.charCodeAt(0);
+    let array = this._csiHandlers[index];
+    if (! array) { this._csiHandlers[index] = array = new Array(); }
+    array.push(callback);
+  }
+
+  removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
+    let index = flag.charCodeAt(0);
+    let array = this._csiHandlers[index];
+    this._removeHandler(array, callback);
+    if (array && array.length == 0)
+      delete this._csiHandlers[index];
+  }
+  /* deprecated */
   setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
-    this._csiHandlers[flag.charCodeAt(0)] = callback;
+    this.clearCsiHandler(flag);
+    this.addCsiHandler(flag, (params: number[], collect: string): boolean => {
+        callback(params, collect); return true;
+    });
   }
+  /* deprecated */
   clearCsiHandler(flag: string): void {
     if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
   }
@@ -321,9 +351,25 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     this._escHandlerFb = callback;
   }
 
+  addOscHandler(ident: number, callback: (data: string) => boolean): void {
+    let array = this._oscHandlers[ident];
+    if (! array) { this._oscHandlers[ident] = array = new Array(); }
+    array.push(callback);
+  }
+  removeOscHandler(ident: number, callback: (data: string) => boolean): void {
+    let array = this._oscHandlers[ident];
+    this._removeHandler(array, callback);
+    if (array && array.length == 0)
+      delete this._oscHandlers[ident];
+  }
+  /* deprecated */
   setOscHandler(ident: number, callback: (data: string) => void): void {
-    this._oscHandlers[ident] = callback;
+    this.clearOscHandler(ident);
+    this.addOscHandler(ident, (data: string): boolean => {
+      callback(data); return true;
+    });
   }
+  /* deprecated */
   clearOscHandler(ident: number): void {
     if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
   }
@@ -463,9 +509,15 @@ 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);
+          let cHandler = this._csiHandlers[code];
+          if (cHandler) {
+            for (let i = cHandler.length; ;) {
+              if (--i < 0) { cHandler = null; break; }
+              if ((cHandler[i])(params, collect))
+                break;
+            }
+          }
+          if (! cHandler) this._csiHandlerFb(collect, params, code);
           break;
         case ParserAction.PARAM:
           if (code === 0x3b) params.push(0);
@@ -532,9 +584,15 @@ 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);
+              let oHandler = this._oscHandlers[identifier];
+              if (oHandler) {
+                for (let i = oHandler.length; ;) {
+                  if (--i < 0) { oHandler = null; break; }
+                  if ((oHandler[i])(content))
+                    break;
+                }
+              }
+              if (! oHandler) this._oscHandlerFb(identifier, content);
             }
           }
           if (code === 0x1b) transition |= ParserState.ESCAPE;
diff --git a/src/InputHandler.ts b/src/InputHandler.ts
index 7604b01f69..2c931a2bb0 100644
--- a/src/InputHandler.ts
+++ b/src/InputHandler.ts
@@ -4,7 +4,7 @@
  * @license MIT
  */
 
-import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
+import { IVtInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
 import { C0, C1 } from './common/data/EscapeSequences';
 import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
 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 {
  * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand
  * each function's header comment.
  */
-export class InputHandler extends Disposable implements IInputHandler {
+export class InputHandler extends Disposable implements IVtInputHandler {
   private _surrogateFirst: string;
 
   constructor(
@@ -465,6 +465,19 @@ export class InputHandler extends Disposable implements IInputHandler {
     this._terminal.updateRange(buffer.y);
   }
 
+  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
+    this._parser.addCsiHandler(flag, callback);
+  }
+  removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
+    this._parser.removeCsiHandler(flag, callback);
+  }
+  addOscHandler(ident: number, callback: (data: string) => boolean): void {
+    this._parser.setOscHandler(ident, callback);
+  }
+  removeOscHandler(ident: number, callback: (data: string) => boolean): void {
+    this._parser.removeOscHandler(ident, callback);
+  }
+
   /**
    * BEL
    * Bell (Ctrl-G).
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 2cfc1ca88b..fea0cb699d 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -21,7 +21,7 @@
  *   http://linux.die.net/man/7/urxvt
  */
 
-import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
+import { IInputHandlingTerminal, IInputHandler, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
 import { IMouseZoneManager } from './ui/Types';
 import { IRenderer } from './renderer/Types';
 import { BufferSet } from './BufferSet';
@@ -1286,6 +1286,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
     this.refresh(0, this.rows - 1);
   }
 
+  public get inputHandler(): IInputHandler {
+    return this._inputHandler;
+  }
+
   /**
    * Scroll the display of the terminal by a number of pages.
    * @param pageCount The number of pages to scroll (negative scrolls up).
diff --git a/src/Types.ts b/src/Types.ts
index 430c6575bd..93dd7c8aa8 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -182,6 +182,16 @@ export interface IInputHandler {
       ESC ~ */ setgLevel(level: number): void;
 }
 
+/*
+ * An InputHandler for VT-style terminals
+ */
+export interface IVtInputHandler extends IInputHandler {
+  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void;
+  removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void;
+  addOscHandler(ident: number, callback: (data: string) => boolean): void;
+  removeOscHandler(ident: number, callback: (data: string) => boolean): void;
+}
+
 export interface ILinkMatcher {
   id: number;
   regex: RegExp;
@@ -492,6 +502,10 @@ 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): void;
+  removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void;
+  addOscHandler(ident: number, callback: (data: string) => boolean): void;
+  removeOscHandler(ident: number, callback: (data: string) => boolean): void;
 
   setEscHandler(collectAndFlag: string, callback: () => void): void;
   clearEscHandler(collectAndFlag: string): void;
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 8ff7cf2b3b..de15fad08c 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -4,7 +4,7 @@
  */
 
 import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm';
-import { ITerminal } from '../Types';
+import { ITerminal, IInputHandler } from '../Types';
 import { Terminal as TerminalCore } from '../Terminal';
 import * as Strings from '../Strings';
 
@@ -15,6 +15,9 @@ export class Terminal implements ITerminalApi {
     this._core = new TerminalCore(options);
   }
 
+  public get inputHandler(): IInputHandler {
+    return (this._core as TerminalCore).inputHandler;
+  }
   public get element(): HTMLElement { return this._core.element; }
   public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
   public get rows(): number { return this._core.rows; }

From b68974517e2f9f02b0304db064020e25afc527fd Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Wed, 5 Dec 2018 14:45:57 -0800
Subject: [PATCH 02/14] Cleanups required by tslink.

---
 src/EscapeSequenceParser.ts | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index 7a5da2c851..5a2c6cd37a 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -304,7 +304,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
   private _removeHandler(array: any[], callback: any): void {
     if (array) {
       for (let i = array.length; --i >= 0; ) {
-        if (array[i] == callback) {
+        if (array[i] === callback) {
           array.splice(i, 1);
             return;
         }
@@ -313,18 +313,19 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
   }
 
   addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
-    let index = flag.charCodeAt(0);
+    const index = flag.charCodeAt(0);
     let array = this._csiHandlers[index];
     if (! array) { this._csiHandlers[index] = array = new Array(); }
     array.push(callback);
   }
 
   removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
-    let index = flag.charCodeAt(0);
-    let array = this._csiHandlers[index];
+    const index = flag.charCodeAt(0);
+    const array = this._csiHandlers[index];
     this._removeHandler(array, callback);
-    if (array && array.length == 0)
+    if (array && array.length === 0) {
       delete this._csiHandlers[index];
+    }
   }
   /* deprecated */
   setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
@@ -357,10 +358,11 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     array.push(callback);
   }
   removeOscHandler(ident: number, callback: (data: string) => boolean): void {
-    let array = this._oscHandlers[ident];
+    const array = this._oscHandlers[ident];
     this._removeHandler(array, callback);
-    if (array && array.length == 0)
+    if (array && array.length === 0) {
       delete this._oscHandlers[ident];
+    }
   }
   /* deprecated */
   setOscHandler(ident: number, callback: (data: string) => void): void {
@@ -513,8 +515,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
           if (cHandler) {
             for (let i = cHandler.length; ;) {
               if (--i < 0) { cHandler = null; break; }
-              if ((cHandler[i])(params, collect))
-                break;
+              if ((cHandler[i])(params, collect)) { break; }
             }
           }
           if (! cHandler) this._csiHandlerFb(collect, params, code);
@@ -588,8 +589,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
               if (oHandler) {
                 for (let i = oHandler.length; ;) {
                   if (--i < 0) { oHandler = null; break; }
-                  if ((oHandler[i])(content))
-                    break;
+                  if ((oHandler[i])(content)) { break; }
                 }
               }
               if (! oHandler) this._oscHandlerFb(identifier, content);

From 03115639a959c19ad9a5b6e112cab2066cb46c7f Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Sun, 9 Dec 2018 10:56:34 -0800
Subject: [PATCH 03/14] Optimize parsing of OSC_STRING to minimize string
 concatenation.

---
 src/EscapeSequenceParser.ts | 21 +++++++++++++++------
 1 file changed, 15 insertions(+), 6 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index 5a2c6cd37a..28a946d1dc 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -439,7 +439,11 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
       }
 
       // normal transition & action lookup
-      transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION;
+      transition = (code < 0xa0
+        ? (table[currentState << 8 | code])
+        : currentState === ParserState.OSC_STRING
+        ? (ParserAction.OSC_PUT << 4) | ParserState.OSC_STRING
+        : DEFAULT_TRANSITION);
       switch (transition >> 4) {
         case ParserAction.PRINT:
           print = (~print) ? print : i;
@@ -471,10 +475,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
               case ParserState.GROUND:
                 print = (~print) ? print : i;
                 break;
-              case ParserState.OSC_STRING:
-                osc += String.fromCharCode(code);
-                transition |= ParserState.OSC_STRING;
-                break;
               case ParserState.CSI_IGNORE:
                 transition |= ParserState.CSI_IGNORE;
                 break;
@@ -570,7 +570,16 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
           osc = '';
           break;
         case ParserAction.OSC_PUT:
-          osc += data.charAt(i);
+          for (let j = i + 1; ; j++) {
+            if (j >= l
+                || ((code = data.charCodeAt(j)) <= 0x9f
+                    && (table[ParserState.OSC_STRING << 8 | code] >> 4
+                        !== ParserAction.OSC_PUT))) {
+              osc += data.substring(i, j);
+              i = j - 1;
+              break;
+            }
+          }
           break;
         case ParserAction.OSC_END:
           if (osc && code !== 0x18 && code !== 0x1a) {

From 30a667c3be32cbccc57405b58a787a41668491e4 Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Sun, 9 Dec 2018 13:11:17 -0800
Subject: [PATCH 04/14] Revert "Optimize parsing of OSC_STRING to minimize
 string concatenation."

This reverts commit 03115639a959c19ad9a5b6e112cab2066cb46c7f.
---
 src/EscapeSequenceParser.ts | 21 ++++++---------------
 1 file changed, 6 insertions(+), 15 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index 28a946d1dc..5a2c6cd37a 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -439,11 +439,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
       }
 
       // normal transition & action lookup
-      transition = (code < 0xa0
-        ? (table[currentState << 8 | code])
-        : currentState === ParserState.OSC_STRING
-        ? (ParserAction.OSC_PUT << 4) | ParserState.OSC_STRING
-        : DEFAULT_TRANSITION);
+      transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION;
       switch (transition >> 4) {
         case ParserAction.PRINT:
           print = (~print) ? print : i;
@@ -475,6 +471,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
               case ParserState.GROUND:
                 print = (~print) ? print : i;
                 break;
+              case ParserState.OSC_STRING:
+                osc += String.fromCharCode(code);
+                transition |= ParserState.OSC_STRING;
+                break;
               case ParserState.CSI_IGNORE:
                 transition |= ParserState.CSI_IGNORE;
                 break;
@@ -570,16 +570,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
           osc = '';
           break;
         case ParserAction.OSC_PUT:
-          for (let j = i + 1; ; j++) {
-            if (j >= l
-                || ((code = data.charCodeAt(j)) <= 0x9f
-                    && (table[ParserState.OSC_STRING << 8 | code] >> 4
-                        !== ParserAction.OSC_PUT))) {
-              osc += data.substring(i, j);
-              i = j - 1;
-              break;
-            }
-          }
+          osc += data.charAt(i);
           break;
         case ParserAction.OSC_END:
           if (osc && code !== 0x18 && code !== 0x1a) {

From ffb2708a8128a0cf1637c1f57de3c35ceca6029b Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Tue, 11 Dec 2018 16:50:14 -0800
Subject: [PATCH 05/14] Revert "Cleanups required by tslink."

This reverts commit b68974517e2f9f02b0304db064020e25afc527fd.
---
 src/EscapeSequenceParser.ts | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index 76b9dd686f..90405a4275 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -306,7 +306,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
   private _removeHandler(array: any[], callback: any): void {
     if (array) {
       for (let i = array.length; --i >= 0; ) {
-        if (array[i] === callback) {
+        if (array[i] == callback) {
           array.splice(i, 1);
             return;
         }
@@ -315,19 +315,18 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
   }
 
   addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
-    const index = flag.charCodeAt(0);
+    let index = flag.charCodeAt(0);
     let array = this._csiHandlers[index];
     if (! array) { this._csiHandlers[index] = array = new Array(); }
     array.push(callback);
   }
 
   removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
-    const index = flag.charCodeAt(0);
-    const array = this._csiHandlers[index];
+    let index = flag.charCodeAt(0);
+    let array = this._csiHandlers[index];
     this._removeHandler(array, callback);
-    if (array && array.length === 0) {
+    if (array && array.length == 0)
       delete this._csiHandlers[index];
-    }
   }
   /* deprecated */
   setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
@@ -360,11 +359,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     array.push(callback);
   }
   removeOscHandler(ident: number, callback: (data: string) => boolean): void {
-    const array = this._oscHandlers[ident];
+    let array = this._oscHandlers[ident];
     this._removeHandler(array, callback);
-    if (array && array.length === 0) {
+    if (array && array.length == 0)
       delete this._oscHandlers[ident];
-    }
   }
   /* deprecated */
   setOscHandler(ident: number, callback: (data: string) => void): void {
@@ -513,7 +511,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
           if (cHandler) {
             for (let i = cHandler.length; ;) {
               if (--i < 0) { cHandler = null; break; }
-              if ((cHandler[i])(params, collect)) { break; }
+              if ((cHandler[i])(params, collect))
+                break;
             }
           }
           if (! cHandler) this._csiHandlerFb(collect, params, code);
@@ -595,7 +594,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
               if (oHandler) {
                 for (let i = oHandler.length; ;) {
                   if (--i < 0) { oHandler = null; break; }
-                  if ((oHandler[i])(content)) { break; }
+                  if ((oHandler[i])(content))
+                    break;
                 }
               }
               if (! oHandler) this._oscHandlerFb(identifier, content);

From 53fd04a8867a9e67519df38600d2a4c08b0e3065 Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Tue, 11 Dec 2018 16:50:40 -0800
Subject: [PATCH 06/14] Revert "hooks for custom control sequences"

This reverts commit 4d660deff3db9f3eea3923dcaa970fde0e9deaf8.
---
 src/EscapeSequenceParser.ts | 74 ++++---------------------------------
 src/InputHandler.ts         | 17 +--------
 src/Terminal.ts             |  6 +--
 src/Types.ts                | 14 -------
 src/public/Terminal.ts      |  5 +--
 5 files changed, 12 insertions(+), 104 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index 90405a4275..f489884177 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -303,39 +303,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     this._executeHandlerFb = callback;
   }
 
-  private _removeHandler(array: any[], callback: any): void {
-    if (array) {
-      for (let i = array.length; --i >= 0; ) {
-        if (array[i] == callback) {
-          array.splice(i, 1);
-            return;
-        }
-      }
-    }
-  }
-
-  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
-    let index = flag.charCodeAt(0);
-    let array = this._csiHandlers[index];
-    if (! array) { this._csiHandlers[index] = array = new Array(); }
-    array.push(callback);
-  }
-
-  removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
-    let index = flag.charCodeAt(0);
-    let array = this._csiHandlers[index];
-    this._removeHandler(array, callback);
-    if (array && array.length == 0)
-      delete this._csiHandlers[index];
-  }
-  /* deprecated */
   setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
-    this.clearCsiHandler(flag);
-    this.addCsiHandler(flag, (params: number[], collect: string): boolean => {
-        callback(params, collect); return true;
-    });
+    this._csiHandlers[flag.charCodeAt(0)] = callback;
   }
-  /* deprecated */
   clearCsiHandler(flag: string): void {
     if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
   }
@@ -353,25 +323,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     this._escHandlerFb = callback;
   }
 
-  addOscHandler(ident: number, callback: (data: string) => boolean): void {
-    let array = this._oscHandlers[ident];
-    if (! array) { this._oscHandlers[ident] = array = new Array(); }
-    array.push(callback);
-  }
-  removeOscHandler(ident: number, callback: (data: string) => boolean): void {
-    let array = this._oscHandlers[ident];
-    this._removeHandler(array, callback);
-    if (array && array.length == 0)
-      delete this._oscHandlers[ident];
-  }
-  /* deprecated */
   setOscHandler(ident: number, callback: (data: string) => void): void {
-    this.clearOscHandler(ident);
-    this.addOscHandler(ident, (data: string): boolean => {
-      callback(data); return true;
-    });
+    this._oscHandlers[ident] = callback;
   }
-  /* deprecated */
   clearOscHandler(ident: number): void {
     if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
   }
@@ -507,15 +461,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
           }
           break;
         case ParserAction.CSI_DISPATCH:
-          let cHandler = this._csiHandlers[code];
-          if (cHandler) {
-            for (let i = cHandler.length; ;) {
-              if (--i < 0) { cHandler = null; break; }
-              if ((cHandler[i])(params, collect))
-                break;
-            }
-          }
-          if (! cHandler) this._csiHandlerFb(collect, params, code);
+          callback = this._csiHandlers[code];
+          if (callback) callback(params, collect);
+          else this._csiHandlerFb(collect, params, code);
           break;
         case ParserAction.PARAM:
           if (code === 0x3b) params.push(0);
@@ -590,15 +538,9 @@ 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);
-              let oHandler = this._oscHandlers[identifier];
-              if (oHandler) {
-                for (let i = oHandler.length; ;) {
-                  if (--i < 0) { oHandler = null; break; }
-                  if ((oHandler[i])(content))
-                    break;
-                }
-              }
-              if (! oHandler) this._oscHandlerFb(identifier, content);
+              callback = this._oscHandlers[identifier];
+              if (callback) callback(content);
+              else this._oscHandlerFb(identifier, content);
             }
           }
           if (code === 0x1b) transition |= ParserState.ESCAPE;
diff --git a/src/InputHandler.ts b/src/InputHandler.ts
index 2c931a2bb0..7604b01f69 100644
--- a/src/InputHandler.ts
+++ b/src/InputHandler.ts
@@ -4,7 +4,7 @@
  * @license MIT
  */
 
-import { IVtInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
+import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
 import { C0, C1 } from './common/data/EscapeSequences';
 import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
 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 {
  * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand
  * each function's header comment.
  */
-export class InputHandler extends Disposable implements IVtInputHandler {
+export class InputHandler extends Disposable implements IInputHandler {
   private _surrogateFirst: string;
 
   constructor(
@@ -465,19 +465,6 @@ export class InputHandler extends Disposable implements IVtInputHandler {
     this._terminal.updateRange(buffer.y);
   }
 
-  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
-    this._parser.addCsiHandler(flag, callback);
-  }
-  removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void {
-    this._parser.removeCsiHandler(flag, callback);
-  }
-  addOscHandler(ident: number, callback: (data: string) => boolean): void {
-    this._parser.setOscHandler(ident, callback);
-  }
-  removeOscHandler(ident: number, callback: (data: string) => boolean): void {
-    this._parser.removeOscHandler(ident, callback);
-  }
-
   /**
    * BEL
    * Bell (Ctrl-G).
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 8f66ebe905..bc8fb103a2 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -21,7 +21,7 @@
  *   http://linux.die.net/man/7/urxvt
  */
 
-import { IInputHandlingTerminal, IInputHandler, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
+import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
 import { IMouseZoneManager } from './ui/Types';
 import { IRenderer } from './renderer/Types';
 import { BufferSet } from './BufferSet';
@@ -1287,10 +1287,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
     this.refresh(0, this.rows - 1);
   }
 
-  public get inputHandler(): IInputHandler {
-    return this._inputHandler;
-  }
-
   /**
    * Scroll the display of the terminal by a number of pages.
    * @param pageCount The number of pages to scroll (negative scrolls up).
diff --git a/src/Types.ts b/src/Types.ts
index 22eff1c853..a5aa8add70 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -182,16 +182,6 @@ export interface IInputHandler {
       ESC ~ */ setgLevel(level: number): void;
 }
 
-/*
- * An InputHandler for VT-style terminals
- */
-export interface IVtInputHandler extends IInputHandler {
-  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void;
-  removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void;
-  addOscHandler(ident: number, callback: (data: string) => boolean): void;
-  removeOscHandler(ident: number, callback: (data: string) => boolean): void;
-}
-
 export interface ILinkMatcher {
   id: number;
   regex: RegExp;
@@ -502,10 +492,6 @@ 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): void;
-  removeCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): void;
-  addOscHandler(ident: number, callback: (data: string) => boolean): void;
-  removeOscHandler(ident: number, callback: (data: string) => boolean): void;
 
   setEscHandler(collectAndFlag: string, callback: () => void): void;
   clearEscHandler(collectAndFlag: string): void;
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index de15fad08c..8ff7cf2b3b 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -4,7 +4,7 @@
  */
 
 import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm';
-import { ITerminal, IInputHandler } from '../Types';
+import { ITerminal } from '../Types';
 import { Terminal as TerminalCore } from '../Terminal';
 import * as Strings from '../Strings';
 
@@ -15,9 +15,6 @@ export class Terminal implements ITerminalApi {
     this._core = new TerminalCore(options);
   }
 
-  public get inputHandler(): IInputHandler {
-    return (this._core as TerminalCore).inputHandler;
-  }
   public get element(): HTMLElement { return this._core.element; }
   public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
   public get rows(): number { return this._core.rows; }

From 8ceea112f7a4d11532b03d39a06d319c08c13f18 Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Tue, 11 Dec 2018 17:18:10 -0800
Subject: [PATCH 07/14] hooks for custom control sequences

This re-implements addCsiHandler/addOscHandler to return an IDisposable.
---
 src/EscapeSequenceParser.ts | 53 ++++++++++++++++++++++++++++++++++++-
 src/InputHandler.ts         | 12 +++++++--
 src/Terminal.ts             |  6 ++++-
 src/Types.ts                | 10 +++++++
 src/public/Terminal.ts      |  5 +++-
 5 files changed, 81 insertions(+), 5 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index f489884177..285629391e 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -4,6 +4,7 @@
  */
 
 import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';
+import { IDisposable } from 'xterm';
 import { Disposable } from './common/Lifecycle';
 
 /**
@@ -41,7 +42,7 @@ export class TransitionTable {
    * @param action parser action to be done
    * @param next next parser state
    */
-  add(code: number, state: number, action: number | null, next: number | null): void {
+  add(code: number, state: number, action: number | null, next: number | null):  void {
     this.table[state << 8 | code] = ((action | 0) << 4) | ((next === undefined) ? state : next);
   }
 
@@ -303,6 +304,32 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     this._executeHandlerFb = callback;
   }
 
+  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
+    const index = flag.charCodeAt(0);
+    const oldHead = this._csiHandlers[index];
+    const newHead = Object.assign(
+      (params: number[], collect: string): void => {
+        if (callback(params, collect)) { }
+        else if (newHead.nextHandler) { newHead.nextHandler(params, collect); }
+        else { this._csiHandlerFb(collect, params, index); }
+      },
+      { nextHandler: oldHead,
+        dispose(): void {
+          let previous = null; let cur = this._csiHandlers[index];
+          for (; cur && cur.nextHandler;
+                 previous = cur, cur = cur.nextHandler) {
+            if (cur === newHead) {
+              if (previous) { previous.nextHandler = cur.nextHandler; }
+              else { this._csiHandlers[index] = cur.nextHandler; }
+              break;
+            }
+          }
+        }
+      });
+    this._csiHandlers[index] = newHead;
+    return newHead;
+  }
+
   setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
     this._csiHandlers[flag.charCodeAt(0)] = callback;
   }
@@ -323,6 +350,30 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     this._escHandlerFb = callback;
   }
 
+  addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
+    const oldHead = this._oscHandlers[ident];
+    const newHead = Object.assign(
+      (data: string): void => {
+        if (callback(data)) { }
+        else if (newHead.nextHandler) { newHead.nextHandler(data); }
+        else { this._oscHandlerFb(ident, data); }
+      },
+      { nextHandler: oldHead,
+        dispose(): void {
+          let previous = null; let cur = this._oscHandlers[ident];
+          for (; cur && cur.nextHandler;
+                 previous = cur, cur = cur.nextHandler) {
+            if (cur === newHead) {
+              if (previous) { previous.nextHandler = cur.nextHandler; }
+              else { this._oscHandlers[ident] = cur.nextHandler; }
+              break;
+            }
+          }
+        }
+      });
+    this._oscHandlers[ident] = newHead;
+    return newHead;
+  }
   setOscHandler(ident: number, callback: (data: string) => void): void {
     this._oscHandlers[ident] = callback;
   }
diff --git a/src/InputHandler.ts b/src/InputHandler.ts
index 7604b01f69..2e016296f7 100644
--- a/src/InputHandler.ts
+++ b/src/InputHandler.ts
@@ -4,7 +4,7 @@
  * @license MIT
  */
 
-import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
+import { IVtInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
 import { C0, C1 } from './common/data/EscapeSequences';
 import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
 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';
@@ -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';
 
 /**
@@ -112,7 +113,7 @@ class DECRQSS implements IDcsHandler {
  * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand
  * each function's header comment.
  */
-export class InputHandler extends Disposable implements IInputHandler {
+export class InputHandler extends Disposable implements IVtInputHandler {
   private _surrogateFirst: string;
 
   constructor(
@@ -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..8f66ebe905 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -21,7 +21,7 @@
  *   http://linux.die.net/man/7/urxvt
  */
 
-import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
+import { IInputHandlingTerminal, IInputHandler, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
 import { IMouseZoneManager } from './ui/Types';
 import { IRenderer } from './renderer/Types';
 import { BufferSet } from './BufferSet';
@@ -1287,6 +1287,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
     this.refresh(0, this.rows - 1);
   }
 
+  public get inputHandler(): IInputHandler {
+    return this._inputHandler;
+  }
+
   /**
    * Scroll the display of the terminal by a number of pages.
    * @param pageCount The number of pages to scroll (negative scrolls up).
diff --git a/src/Types.ts b/src/Types.ts
index a5aa8add70..e150b90b55 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -182,6 +182,14 @@ export interface IInputHandler {
       ESC ~ */ setgLevel(level: number): void;
 }
 
+/*
+ * An InputHandler for VT-style terminals
+ */
+export interface IVtInputHandler extends IInputHandler {
+  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
+  addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
+}
+
 export interface ILinkMatcher {
   id: number;
   regex: RegExp;
@@ -492,6 +500,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..de15fad08c 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -4,7 +4,7 @@
  */
 
 import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm';
-import { ITerminal } from '../Types';
+import { ITerminal, IInputHandler } from '../Types';
 import { Terminal as TerminalCore } from '../Terminal';
 import * as Strings from '../Strings';
 
@@ -15,6 +15,9 @@ export class Terminal implements ITerminalApi {
     this._core = new TerminalCore(options);
   }
 
+  public get inputHandler(): IInputHandler {
+    return (this._core as TerminalCore).inputHandler;
+  }
   public get element(): HTMLElement { return this._core.element; }
   public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
   public get rows(): number { return this._core.rows; }

From 5af4626ec7d44f0536024a7a9af66b463ee5d7d9 Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Thu, 13 Dec 2018 17:33:33 -0800
Subject: [PATCH 08/14] Change addCsiHandler/addOscHandler to not use
 Object.assign.

---
 src/EscapeSequenceParser.ts | 47 ++++++++++++++++++++-----------------
 1 file changed, 25 insertions(+), 22 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index 285629391e..a104d58362 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -307,25 +307,26 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
   addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
     const index = flag.charCodeAt(0);
     const oldHead = this._csiHandlers[index];
-    const newHead = Object.assign(
+    const parser = this;
+    const newHead =
       (params: number[], collect: string): void => {
-        if (callback(params, collect)) { }
-        else if (newHead.nextHandler) { newHead.nextHandler(params, collect); }
-        else { this._csiHandlerFb(collect, params, index); }
-      },
-      { nextHandler: oldHead,
-        dispose(): void {
-          let previous = null; let cur = this._csiHandlers[index];
+        if (! callback(params, collect)) {
+          if (newHead.nextHandler) { newHead.nextHandler(params, collect); }
+          else { this._csiHandlerFb(collect, params, index); }
+        }
+      };
+    newHead.nextHandler = oldHead;
+    newHead.dispose = function (): void {
+          let previous = null; let cur = parser._csiHandlers[index];
           for (; cur && cur.nextHandler;
                  previous = cur, cur = cur.nextHandler) {
             if (cur === newHead) {
               if (previous) { previous.nextHandler = cur.nextHandler; }
-              else { this._csiHandlers[index] = cur.nextHandler; }
+              else { parser._csiHandlers[index] = cur.nextHandler; }
               break;
             }
           }
-        }
-      });
+      };
     this._csiHandlers[index] = newHead;
     return newHead;
   }
@@ -352,25 +353,27 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
 
   addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
     const oldHead = this._oscHandlers[ident];
-    const newHead = Object.assign(
+    const parser = this;
+    const newHead =
       (data: string): void => {
-        if (callback(data)) { }
-        else if (newHead.nextHandler) { newHead.nextHandler(data); }
-        else { this._oscHandlerFb(ident, data); }
-      },
-      { nextHandler: oldHead,
-        dispose(): void {
-          let previous = null; let cur = this._oscHandlers[ident];
+        if (! callback(data)) {
+          if (newHead.nextHandler) { newHead.nextHandler(data); }
+          else { this._oscHandlerFb(ident, data); }
+        }
+      };
+    newHead.nextHandler = oldHead;
+    newHead.dispose =
+        function (): void {
+          let previous = null; let cur = parser._oscHandlers[ident];
           for (; cur && cur.nextHandler;
                  previous = cur, cur = cur.nextHandler) {
             if (cur === newHead) {
               if (previous) { previous.nextHandler = cur.nextHandler; }
-              else { this._oscHandlers[ident] = cur.nextHandler; }
+              else { parser._oscHandlers[ident] = cur.nextHandler; }
               break;
             }
           }
-        }
-      });
+      };
     this._oscHandlers[ident] = newHead;
     return newHead;
   }

From 8a5a03238fedded95e4cedfc9e80e3e0aec2ecc4 Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Sat, 15 Dec 2018 09:42:19 -0800
Subject: [PATCH 09/14] New method _linkHandler used by both addCsiHandler and
 addOscHandler.

---
 src/EscapeSequenceParser.ts | 60 +++++++++++++++++--------------------
 1 file changed, 27 insertions(+), 33 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index a104d58362..c85f670d4c 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -7,6 +7,10 @@ import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceP
 import { IDisposable } from 'xterm';
 import { Disposable } from './common/Lifecycle';
 
+interface IHandlerLink extends IDisposable {
+  nextHandler: IHandlerLink | null;
+}
+
 /**
  * Returns an array filled with numbers between the low and high parameters (right exclusive).
  * @param low The low number.
@@ -304,33 +308,38 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     this._executeHandlerFb = callback;
   }
 
-  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
-    const index = flag.charCodeAt(0);
-    const oldHead = this._csiHandlers[index];
-    const parser = this;
-    const newHead =
-      (params: number[], collect: string): void => {
-        if (! callback(params, collect)) {
-          if (newHead.nextHandler) { newHead.nextHandler(params, collect); }
-          else { this._csiHandlerFb(collect, params, index); }
-        }
-      };
-    newHead.nextHandler = oldHead;
+  private _linkHandler(handlers: object[], index: number, newCallback: object): IDisposable {
+    const newHead: any = newCallback;
+    newHead.nextHandler = handlers[index] as IHandlerLink;
     newHead.dispose = function (): void {
-          let previous = null; let cur = parser._csiHandlers[index];
+          let previous = null;
+          let cur = handlers[index] as IHandlerLink;
           for (; cur && cur.nextHandler;
                  previous = cur, cur = cur.nextHandler) {
             if (cur === newHead) {
               if (previous) { previous.nextHandler = cur.nextHandler; }
-              else { parser._csiHandlers[index] = cur.nextHandler; }
+              else { handlers[index] = cur.nextHandler; }
               break;
             }
           }
       };
-    this._csiHandlers[index] = newHead;
+    handlers[index] = newHead;
     return newHead;
   }
 
+  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
+    const index = flag.charCodeAt(0);
+    const newHead =
+      (params: number[], collect: string): void => {
+        if (! callback(params, collect)) {
+          const next = (newHead as unknown as IHandlerLink).nextHandler;
+          if (next) { (next as any)(params, collect); }
+          else { this._csiHandlerFb(collect, params, index); }
+        }
+      };
+    return this._linkHandler(this._csiHandlers, index, newHead);
+  }
+
   setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
     this._csiHandlers[flag.charCodeAt(0)] = callback;
   }
@@ -352,30 +361,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
   }
 
   addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
-    const oldHead = this._oscHandlers[ident];
-    const parser = this;
     const newHead =
       (data: string): void => {
         if (! callback(data)) {
-          if (newHead.nextHandler) { newHead.nextHandler(data); }
+          const next = (newHead as unknown as IHandlerLink).nextHandler;
+          if (next) { (next as any)(data); }
           else { this._oscHandlerFb(ident, data); }
         }
       };
-    newHead.nextHandler = oldHead;
-    newHead.dispose =
-        function (): void {
-          let previous = null; let cur = parser._oscHandlers[ident];
-          for (; cur && cur.nextHandler;
-                 previous = cur, cur = cur.nextHandler) {
-            if (cur === newHead) {
-              if (previous) { previous.nextHandler = cur.nextHandler; }
-              else { parser._oscHandlers[ident] = cur.nextHandler; }
-              break;
-            }
-          }
-      };
-    this._oscHandlers[ident] = newHead;
-    return newHead;
+    return this._linkHandler(this._oscHandlers, ident, newHead);
   }
   setOscHandler(ident: number, callback: (data: string) => void): void {
     this._oscHandlers[ident] = callback;

From 6b65ebd4aace41ddf0f7d663e5ff6c92a4a37948 Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Sat, 15 Dec 2018 11:34:15 -0800
Subject: [PATCH 10/14] Various typing and API fixes, doc comments, typing test
 etc.

---
 fixtures/typings-test/typings-test.ts |  8 +++++++-
 src/InputHandler.ts                   |  4 ++--
 src/Terminal.ts                       | 15 ++++++++++-----
 src/Types.ts                          |  8 --------
 src/public/Terminal.ts                | 11 +++++++----
 src/ui/TestUtils.test.ts              |  6 ++++++
 typings/xterm.d.ts                    | 25 +++++++++++++++++++++++++
 7 files changed, 57 insertions(+), 20 deletions(-)

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 @@
 
 /// <reference path="../../typings/xterm.d.ts" />
 
-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/InputHandler.ts b/src/InputHandler.ts
index 2e016296f7..eb1ca1050b 100644
--- a/src/InputHandler.ts
+++ b/src/InputHandler.ts
@@ -4,7 +4,7 @@
  * @license MIT
  */
 
-import { IVtInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
+import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
 import { C0, C1 } from './common/data/EscapeSequences';
 import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
 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';
@@ -113,7 +113,7 @@ class DECRQSS implements IDcsHandler {
  * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand
  * each function's header comment.
  */
-export class InputHandler extends Disposable implements IVtInputHandler {
+export class InputHandler extends Disposable implements IInputHandler {
   private _surrogateFirst: string;
 
   constructor(
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 8f66ebe905..bed45e46ec 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -21,7 +21,7 @@
  *   http://linux.die.net/man/7/urxvt
  */
 
-import { IInputHandlingTerminal, IInputHandler, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
+import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
 import { IMouseZoneManager } from './ui/Types';
 import { IRenderer } from './renderer/Types';
 import { BufferSet } from './BufferSet';
@@ -1287,10 +1287,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
     this.refresh(0, this.rows - 1);
   }
 
-  public get inputHandler(): IInputHandler {
-    return this._inputHandler;
-  }
-
   /**
    * Scroll the display of the terminal by a number of pages.
    * @param pageCount The number of pages to scroll (negative scrolls up).
@@ -1417,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 e150b90b55..93ba01f64b 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -182,14 +182,6 @@ export interface IInputHandler {
       ESC ~ */ setgLevel(level: number): void;
 }
 
-/*
- * An InputHandler for VT-style terminals
- */
-export interface IVtInputHandler extends IInputHandler {
-  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
-  addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
-}
-
 export interface ILinkMatcher {
   id: number;
   regex: RegExp;
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index de15fad08c..87fcfaef99 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -4,7 +4,7 @@
  */
 
 import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm';
-import { ITerminal, IInputHandler } from '../Types';
+import { ITerminal } from '../Types';
 import { Terminal as TerminalCore } from '../Terminal';
 import * as Strings from '../Strings';
 
@@ -15,9 +15,6 @@ export class Terminal implements ITerminalApi {
     this._core = new TerminalCore(options);
   }
 
-  public get inputHandler(): IInputHandler {
-    return (this._core as TerminalCore).inputHandler;
-  }
   public get element(): HTMLElement { return this._core.element; }
   public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
   public get rows(): number { return this._core.rows; }
@@ -62,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/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..cf489a610c 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -481,6 +481,31 @@ 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.

From 717357720df457e237ced3f6d68aad42f6ca4189 Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Thu, 20 Dec 2018 17:55:57 -0800
Subject: [PATCH 11/14] Revert "Various typing and API fixes, doc comments,
 typing test etc."

This reverts commit 6b65ebd4aace41ddf0f7d663e5ff6c92a4a37948.
---
 fixtures/typings-test/typings-test.ts |  8 +-------
 src/InputHandler.ts                   |  4 ++--
 src/Terminal.ts                       | 15 +++++----------
 src/Types.ts                          |  8 ++++++++
 src/public/Terminal.ts                | 11 ++++-------
 src/ui/TestUtils.test.ts              |  6 ------
 typings/xterm.d.ts                    | 25 -------------------------
 7 files changed, 20 insertions(+), 57 deletions(-)

diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts
index 87d911c2a4..13da69616b 100644
--- a/fixtures/typings-test/typings-test.ts
+++ b/fixtures/typings-test/typings-test.ts
@@ -4,7 +4,7 @@
 
 /// <reference path="../../typings/xterm.d.ts" />
 
-import { Terminal, IDisposable } from 'xterm';
+import { Terminal } from 'xterm';
 
 namespace constructor {
   {
@@ -119,12 +119,6 @@ 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/InputHandler.ts b/src/InputHandler.ts
index eb1ca1050b..2e016296f7 100644
--- a/src/InputHandler.ts
+++ b/src/InputHandler.ts
@@ -4,7 +4,7 @@
  * @license MIT
  */
 
-import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
+import { IVtInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
 import { C0, C1 } from './common/data/EscapeSequences';
 import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
 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';
@@ -113,7 +113,7 @@ class DECRQSS implements IDcsHandler {
  * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand
  * each function's header comment.
  */
-export class InputHandler extends Disposable implements IInputHandler {
+export class InputHandler extends Disposable implements IVtInputHandler {
   private _surrogateFirst: string;
 
   constructor(
diff --git a/src/Terminal.ts b/src/Terminal.ts
index bed45e46ec..8f66ebe905 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -21,7 +21,7 @@
  *   http://linux.die.net/man/7/urxvt
  */
 
-import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
+import { IInputHandlingTerminal, IInputHandler, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
 import { IMouseZoneManager } from './ui/Types';
 import { IRenderer } from './renderer/Types';
 import { BufferSet } from './BufferSet';
@@ -1287,6 +1287,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
     this.refresh(0, this.rows - 1);
   }
 
+  public get inputHandler(): IInputHandler {
+    return this._inputHandler;
+  }
+
   /**
    * Scroll the display of the terminal by a number of pages.
    * @param pageCount The number of pages to scroll (negative scrolls up).
@@ -1413,15 +1417,6 @@ 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 93ba01f64b..e150b90b55 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -182,6 +182,14 @@ export interface IInputHandler {
       ESC ~ */ setgLevel(level: number): void;
 }
 
+/*
+ * An InputHandler for VT-style terminals
+ */
+export interface IVtInputHandler extends IInputHandler {
+  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
+  addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
+}
+
 export interface ILinkMatcher {
   id: number;
   regex: RegExp;
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 87fcfaef99..de15fad08c 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -4,7 +4,7 @@
  */
 
 import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm';
-import { ITerminal } from '../Types';
+import { ITerminal, IInputHandler } from '../Types';
 import { Terminal as TerminalCore } from '../Terminal';
 import * as Strings from '../Strings';
 
@@ -15,6 +15,9 @@ export class Terminal implements ITerminalApi {
     this._core = new TerminalCore(options);
   }
 
+  public get inputHandler(): IInputHandler {
+    return (this._core as TerminalCore).inputHandler;
+  }
   public get element(): HTMLElement { return this._core.element; }
   public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
   public get rows(): number { return this._core.rows; }
@@ -59,12 +62,6 @@ 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/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts
index e6e4aaa32f..10033a3355 100644
--- a/src/ui/TestUtils.test.ts
+++ b/src/ui/TestUtils.test.ts
@@ -54,12 +54,6 @@ 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 cf489a610c..7528bb5540 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -481,31 +481,6 @@ 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.

From 9f603a2b12d26c0570002257ef27686d3649aa80 Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Thu, 20 Dec 2018 17:56:06 -0800
Subject: [PATCH 12/14] Revert "New method _linkHandler used by both
 addCsiHandler and addOscHandler."

This reverts commit 8a5a03238fedded95e4cedfc9e80e3e0aec2ecc4.
---
 src/EscapeSequenceParser.ts | 60 ++++++++++++++++++++-----------------
 1 file changed, 33 insertions(+), 27 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index c85f670d4c..a104d58362 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -7,10 +7,6 @@ import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceP
 import { IDisposable } from 'xterm';
 import { Disposable } from './common/Lifecycle';
 
-interface IHandlerLink extends IDisposable {
-  nextHandler: IHandlerLink | null;
-}
-
 /**
  * Returns an array filled with numbers between the low and high parameters (right exclusive).
  * @param low The low number.
@@ -308,38 +304,33 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     this._executeHandlerFb = callback;
   }
 
-  private _linkHandler(handlers: object[], index: number, newCallback: object): IDisposable {
-    const newHead: any = newCallback;
-    newHead.nextHandler = handlers[index] as IHandlerLink;
+  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
+    const index = flag.charCodeAt(0);
+    const oldHead = this._csiHandlers[index];
+    const parser = this;
+    const newHead =
+      (params: number[], collect: string): void => {
+        if (! callback(params, collect)) {
+          if (newHead.nextHandler) { newHead.nextHandler(params, collect); }
+          else { this._csiHandlerFb(collect, params, index); }
+        }
+      };
+    newHead.nextHandler = oldHead;
     newHead.dispose = function (): void {
-          let previous = null;
-          let cur = handlers[index] as IHandlerLink;
+          let previous = null; let cur = parser._csiHandlers[index];
           for (; cur && cur.nextHandler;
                  previous = cur, cur = cur.nextHandler) {
             if (cur === newHead) {
               if (previous) { previous.nextHandler = cur.nextHandler; }
-              else { handlers[index] = cur.nextHandler; }
+              else { parser._csiHandlers[index] = cur.nextHandler; }
               break;
             }
           }
       };
-    handlers[index] = newHead;
+    this._csiHandlers[index] = newHead;
     return newHead;
   }
 
-  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
-    const index = flag.charCodeAt(0);
-    const newHead =
-      (params: number[], collect: string): void => {
-        if (! callback(params, collect)) {
-          const next = (newHead as unknown as IHandlerLink).nextHandler;
-          if (next) { (next as any)(params, collect); }
-          else { this._csiHandlerFb(collect, params, index); }
-        }
-      };
-    return this._linkHandler(this._csiHandlers, index, newHead);
-  }
-
   setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
     this._csiHandlers[flag.charCodeAt(0)] = callback;
   }
@@ -361,15 +352,30 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
   }
 
   addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
+    const oldHead = this._oscHandlers[ident];
+    const parser = this;
     const newHead =
       (data: string): void => {
         if (! callback(data)) {
-          const next = (newHead as unknown as IHandlerLink).nextHandler;
-          if (next) { (next as any)(data); }
+          if (newHead.nextHandler) { newHead.nextHandler(data); }
           else { this._oscHandlerFb(ident, data); }
         }
       };
-    return this._linkHandler(this._oscHandlers, ident, newHead);
+    newHead.nextHandler = oldHead;
+    newHead.dispose =
+        function (): void {
+          let previous = null; let cur = parser._oscHandlers[ident];
+          for (; cur && cur.nextHandler;
+                 previous = cur, cur = cur.nextHandler) {
+            if (cur === newHead) {
+              if (previous) { previous.nextHandler = cur.nextHandler; }
+              else { parser._oscHandlers[ident] = cur.nextHandler; }
+              break;
+            }
+          }
+      };
+    this._oscHandlers[ident] = newHead;
+    return newHead;
   }
   setOscHandler(ident: number, callback: (data: string) => void): void {
     this._oscHandlers[ident] = callback;

From a4cc87d5e00d15327319d8ef8b63b833079e2f41 Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Thu, 20 Dec 2018 17:56:13 -0800
Subject: [PATCH 13/14] Revert "Change addCsiHandler/addOscHandler to not use
 Object.assign."

This reverts commit 5af4626ec7d44f0536024a7a9af66b463ee5d7d9.
---
 src/EscapeSequenceParser.ts | 47 +++++++++++++++++--------------------
 1 file changed, 22 insertions(+), 25 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index a104d58362..285629391e 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -307,26 +307,25 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
   addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
     const index = flag.charCodeAt(0);
     const oldHead = this._csiHandlers[index];
-    const parser = this;
-    const newHead =
+    const newHead = Object.assign(
       (params: number[], collect: string): void => {
-        if (! callback(params, collect)) {
-          if (newHead.nextHandler) { newHead.nextHandler(params, collect); }
-          else { this._csiHandlerFb(collect, params, index); }
-        }
-      };
-    newHead.nextHandler = oldHead;
-    newHead.dispose = function (): void {
-          let previous = null; let cur = parser._csiHandlers[index];
+        if (callback(params, collect)) { }
+        else if (newHead.nextHandler) { newHead.nextHandler(params, collect); }
+        else { this._csiHandlerFb(collect, params, index); }
+      },
+      { nextHandler: oldHead,
+        dispose(): void {
+          let previous = null; let cur = this._csiHandlers[index];
           for (; cur && cur.nextHandler;
                  previous = cur, cur = cur.nextHandler) {
             if (cur === newHead) {
               if (previous) { previous.nextHandler = cur.nextHandler; }
-              else { parser._csiHandlers[index] = cur.nextHandler; }
+              else { this._csiHandlers[index] = cur.nextHandler; }
               break;
             }
           }
-      };
+        }
+      });
     this._csiHandlers[index] = newHead;
     return newHead;
   }
@@ -353,27 +352,25 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
 
   addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
     const oldHead = this._oscHandlers[ident];
-    const parser = this;
-    const newHead =
+    const newHead = Object.assign(
       (data: string): void => {
-        if (! callback(data)) {
-          if (newHead.nextHandler) { newHead.nextHandler(data); }
-          else { this._oscHandlerFb(ident, data); }
-        }
-      };
-    newHead.nextHandler = oldHead;
-    newHead.dispose =
-        function (): void {
-          let previous = null; let cur = parser._oscHandlers[ident];
+        if (callback(data)) { }
+        else if (newHead.nextHandler) { newHead.nextHandler(data); }
+        else { this._oscHandlerFb(ident, data); }
+      },
+      { nextHandler: oldHead,
+        dispose(): void {
+          let previous = null; let cur = this._oscHandlers[ident];
           for (; cur && cur.nextHandler;
                  previous = cur, cur = cur.nextHandler) {
             if (cur === newHead) {
               if (previous) { previous.nextHandler = cur.nextHandler; }
-              else { parser._oscHandlers[ident] = cur.nextHandler; }
+              else { this._oscHandlers[ident] = cur.nextHandler; }
               break;
             }
           }
-      };
+        }
+      });
     this._oscHandlers[ident] = newHead;
     return newHead;
   }

From 2551a537a5a0e22e5ae204bd4e98ed33bbee22cf Mon Sep 17 00:00:00 2001
From: Per Bothner <per@bothner.com>
Date: Thu, 20 Dec 2018 17:56:16 -0800
Subject: [PATCH 14/14] Revert "hooks for custom control sequences"

This reverts commit 8ceea112f7a4d11532b03d39a06d319c08c13f18.
---
 src/EscapeSequenceParser.ts | 53 +------------------------------------
 src/InputHandler.ts         | 12 ++-------
 src/Terminal.ts             |  6 +----
 src/Types.ts                | 10 -------
 src/public/Terminal.ts      |  5 +---
 5 files changed, 5 insertions(+), 81 deletions(-)

diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts
index 285629391e..f489884177 100644
--- a/src/EscapeSequenceParser.ts
+++ b/src/EscapeSequenceParser.ts
@@ -4,7 +4,6 @@
  */
 
 import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';
-import { IDisposable } from 'xterm';
 import { Disposable } from './common/Lifecycle';
 
 /**
@@ -42,7 +41,7 @@ export class TransitionTable {
    * @param action parser action to be done
    * @param next next parser state
    */
-  add(code: number, state: number, action: number | null, next: number | null):  void {
+  add(code: number, state: number, action: number | null, next: number | null): void {
     this.table[state << 8 | code] = ((action | 0) << 4) | ((next === undefined) ? state : next);
   }
 
@@ -304,32 +303,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     this._executeHandlerFb = callback;
   }
 
-  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
-    const index = flag.charCodeAt(0);
-    const oldHead = this._csiHandlers[index];
-    const newHead = Object.assign(
-      (params: number[], collect: string): void => {
-        if (callback(params, collect)) { }
-        else if (newHead.nextHandler) { newHead.nextHandler(params, collect); }
-        else { this._csiHandlerFb(collect, params, index); }
-      },
-      { nextHandler: oldHead,
-        dispose(): void {
-          let previous = null; let cur = this._csiHandlers[index];
-          for (; cur && cur.nextHandler;
-                 previous = cur, cur = cur.nextHandler) {
-            if (cur === newHead) {
-              if (previous) { previous.nextHandler = cur.nextHandler; }
-              else { this._csiHandlers[index] = cur.nextHandler; }
-              break;
-            }
-          }
-        }
-      });
-    this._csiHandlers[index] = newHead;
-    return newHead;
-  }
-
   setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
     this._csiHandlers[flag.charCodeAt(0)] = callback;
   }
@@ -350,30 +323,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
     this._escHandlerFb = callback;
   }
 
-  addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
-    const oldHead = this._oscHandlers[ident];
-    const newHead = Object.assign(
-      (data: string): void => {
-        if (callback(data)) { }
-        else if (newHead.nextHandler) { newHead.nextHandler(data); }
-        else { this._oscHandlerFb(ident, data); }
-      },
-      { nextHandler: oldHead,
-        dispose(): void {
-          let previous = null; let cur = this._oscHandlers[ident];
-          for (; cur && cur.nextHandler;
-                 previous = cur, cur = cur.nextHandler) {
-            if (cur === newHead) {
-              if (previous) { previous.nextHandler = cur.nextHandler; }
-              else { this._oscHandlers[ident] = cur.nextHandler; }
-              break;
-            }
-          }
-        }
-      });
-    this._oscHandlers[ident] = newHead;
-    return newHead;
-  }
   setOscHandler(ident: number, callback: (data: string) => void): void {
     this._oscHandlers[ident] = callback;
   }
diff --git a/src/InputHandler.ts b/src/InputHandler.ts
index 2e016296f7..7604b01f69 100644
--- a/src/InputHandler.ts
+++ b/src/InputHandler.ts
@@ -4,7 +4,7 @@
  * @license MIT
  */
 
-import { IVtInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
+import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
 import { C0, C1 } from './common/data/EscapeSequences';
 import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
 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';
@@ -12,7 +12,6 @@ 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';
 
 /**
@@ -113,7 +112,7 @@ class DECRQSS implements IDcsHandler {
  * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand
  * each function's header comment.
  */
-export class InputHandler extends Disposable implements IVtInputHandler {
+export class InputHandler extends Disposable implements IInputHandler {
   private _surrogateFirst: string;
 
   constructor(
@@ -466,13 +465,6 @@ export class InputHandler extends Disposable implements IVtInputHandler {
     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 8f66ebe905..bc8fb103a2 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -21,7 +21,7 @@
  *   http://linux.die.net/man/7/urxvt
  */
 
-import { IInputHandlingTerminal, IInputHandler, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
+import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
 import { IMouseZoneManager } from './ui/Types';
 import { IRenderer } from './renderer/Types';
 import { BufferSet } from './BufferSet';
@@ -1287,10 +1287,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
     this.refresh(0, this.rows - 1);
   }
 
-  public get inputHandler(): IInputHandler {
-    return this._inputHandler;
-  }
-
   /**
    * Scroll the display of the terminal by a number of pages.
    * @param pageCount The number of pages to scroll (negative scrolls up).
diff --git a/src/Types.ts b/src/Types.ts
index e150b90b55..a5aa8add70 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -182,14 +182,6 @@ export interface IInputHandler {
       ESC ~ */ setgLevel(level: number): void;
 }
 
-/*
- * An InputHandler for VT-style terminals
- */
-export interface IVtInputHandler extends IInputHandler {
-  addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
-  addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
-}
-
 export interface ILinkMatcher {
   id: number;
   regex: RegExp;
@@ -500,8 +492,6 @@ 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 de15fad08c..8ff7cf2b3b 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -4,7 +4,7 @@
  */
 
 import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm';
-import { ITerminal, IInputHandler } from '../Types';
+import { ITerminal } from '../Types';
 import { Terminal as TerminalCore } from '../Terminal';
 import * as Strings from '../Strings';
 
@@ -15,9 +15,6 @@ export class Terminal implements ITerminalApi {
     this._core = new TerminalCore(options);
   }
 
-  public get inputHandler(): IInputHandler {
-    return (this._core as TerminalCore).inputHandler;
-  }
   public get element(): HTMLElement { return this._core.element; }
   public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
   public get rows(): number { return this._core.rows; }