xterm
[VSoRC/.git] / node_modules / xterm / src / common / services / CoreMouseService.ts
diff --git a/node_modules/xterm/src/common/services/CoreMouseService.ts b/node_modules/xterm/src/common/services/CoreMouseService.ts
new file mode 100644 (file)
index 0000000..500655b
--- /dev/null
@@ -0,0 +1,296 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
+
+/**
+ * Supported default protocols.
+ */
+const DEFAULT_PROTOCOLS: {[key: string]: ICoreMouseProtocol} = {
+  /**
+   * NONE
+   * Events: none
+   * Modifiers: none
+   */
+  NONE: {
+    events: CoreMouseEventType.NONE,
+    restrict: () => false
+  },
+  /**
+   * X10
+   * Events: mousedown
+   * Modifiers: none
+   */
+  X10: {
+    events: CoreMouseEventType.DOWN,
+    restrict: (e: ICoreMouseEvent) => {
+      // no wheel, no move, no up
+      if (e.button === CoreMouseButton.WHEEL || e.action !== CoreMouseAction.DOWN) {
+        return false;
+      }
+      // no modifiers
+      e.ctrl = false;
+      e.alt = false;
+      e.shift = false;
+      return true;
+    }
+  },
+  /**
+   * VT200
+   * Events: mousedown / mouseup / wheel
+   * Modifiers: all
+   */
+  VT200: {
+    events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL,
+    restrict: (e: ICoreMouseEvent) => {
+      // no move
+      if (e.action === CoreMouseAction.MOVE) {
+        return false;
+      }
+      return true;
+    }
+  },
+  /**
+   * DRAG
+   * Events: mousedown / mouseup / wheel / mousedrag
+   * Modifiers: all
+   */
+  DRAG: {
+    events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG,
+    restrict: (e: ICoreMouseEvent) => {
+      // no move without button
+      if (e.action === CoreMouseAction.MOVE && e.button === CoreMouseButton.NONE) {
+        return false;
+      }
+      return true;
+    }
+  },
+  /**
+   * ANY
+   * Events: all mouse related events
+   * Modifiers: all
+   */
+  ANY: {
+    events:
+      CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL
+      | CoreMouseEventType.DRAG | CoreMouseEventType.MOVE,
+    restrict: (e: ICoreMouseEvent) => true
+  }
+};
+
+const enum Modifiers {
+  SHIFT = 4,
+  ALT = 8,
+  CTRL = 16
+}
+
+// helper for default encoders to generate the event code.
+function eventCode(e: ICoreMouseEvent, isSGR: boolean): number {
+  let code = (e.ctrl ? Modifiers.CTRL : 0) | (e.shift ? Modifiers.SHIFT : 0) | (e.alt ? Modifiers.ALT : 0);
+  if (e.button === CoreMouseButton.WHEEL) {
+    code |= 64;
+    code |= e.action;
+  } else {
+    code |= e.button & 3;
+    if (e.button & 4) {
+      code |= 64;
+    }
+    if (e.button & 8) {
+      code |= 128;
+    }
+    if (e.action === CoreMouseAction.MOVE) {
+      code |= CoreMouseAction.MOVE;
+    } else if (e.action === CoreMouseAction.UP && !isSGR) {
+      // special case - only SGR can report button on release
+      // all others have to go with NONE
+      code |= CoreMouseButton.NONE;
+    }
+  }
+  return code;
+}
+
+const S = String.fromCharCode;
+
+/**
+ * Supported default encodings.
+ */
+const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
+  /**
+   * DEFAULT - CSI M Pb Px Py
+   * Single byte encoding for coords and event code.
+   * Can encode values up to 223. The Encoding of higher
+   * values is not UTF-8 compatible (and currently limited
+   * to 95 in xterm.js).
+   */
+  DEFAULT: (e: ICoreMouseEvent) => {
+    let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
+    // FIXME: we are currently limited to ASCII range
+    params = params.map(v => (v > 127) ? 127 : v);
+    // FIXED: params = params.map(v => (v > 255) ? 0 : value);
+    return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
+  },
+  /**
+   * SGR - CSI < Pb ; Px ; Py M|m
+   * No encoding limitation.
+   * Can report button on release and works with a well formed sequence.
+   */
+  SGR: (e: ICoreMouseEvent) => {
+    const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M';
+    return `\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`;
+  }
+};
+
+/**
+ * CoreMouseService
+ *
+ * Provides mouse tracking reports with different protocols and encodings.
+ *  - protocols: NONE (default), X10, VT200, DRAG, ANY
+ *  - encodings: DEFAULT, SGR (UTF8, URXVT removed in #2507)
+ *
+ * Custom protocols/encodings can be added by `addProtocol` / `addEncoding`.
+ * To activate a protocol/encoding, set `activeProtocol` / `activeEncoding`.
+ * Switching a protocol will send a notification event `onProtocolChange`
+ * with a list of needed events to track.
+ *
+ * The service handles the mouse tracking state and decides whether to send
+ * a tracking report to the backend based on protocol and encoding limitations.
+ * To send a mouse event call `triggerMouseEvent`.
+ */
+export class CoreMouseService implements ICoreMouseService {
+  private _protocols: {[name: string]: ICoreMouseProtocol} = {};
+  private _encodings: {[name: string]: CoreMouseEncoding} = {};
+  private _activeProtocol: string = '';
+  private _activeEncoding: string = '';
+  private _onProtocolChange = new EventEmitter<CoreMouseEventType>();
+  private _lastEvent: ICoreMouseEvent | null = null;
+
+  constructor(
+    @IBufferService private readonly _bufferService: IBufferService,
+    @ICoreService private readonly _coreService: ICoreService
+  ) {
+    // register default protocols and encodings
+    Object.keys(DEFAULT_PROTOCOLS).forEach(name => this.addProtocol(name, DEFAULT_PROTOCOLS[name]));
+    Object.keys(DEFAULT_ENCODINGS).forEach(name => this.addEncoding(name, DEFAULT_ENCODINGS[name]));
+    // call reset to set defaults
+    this.reset();
+  }
+
+  public addProtocol(name: string, protocol: ICoreMouseProtocol): void {
+    this._protocols[name] = protocol;
+  }
+
+  public addEncoding(name: string, encoding: CoreMouseEncoding): void {
+    this._encodings[name] = encoding;
+  }
+
+  public get activeProtocol(): string {
+    return this._activeProtocol;
+  }
+
+  public set activeProtocol(name: string) {
+    if (!this._protocols[name]) {
+      throw new Error(`unknown protocol "${name}"`);
+    }
+    this._activeProtocol = name;
+    this._onProtocolChange.fire(this._protocols[name].events);
+  }
+
+  public get activeEncoding(): string {
+    return this._activeEncoding;
+  }
+
+  public set activeEncoding(name: string) {
+    if (!this._encodings[name]) {
+      throw new Error(`unknown encoding "${name}"`);
+    }
+    this._activeEncoding = name;
+  }
+
+  public reset(): void {
+    this.activeProtocol = 'NONE';
+    this.activeEncoding = 'DEFAULT';
+    this._lastEvent = null;
+  }
+
+  /**
+   * Event to announce changes in mouse tracking.
+   */
+  public get onProtocolChange(): IEvent<CoreMouseEventType> {
+    return this._onProtocolChange.event;
+  }
+
+  /**
+   * Triggers a mouse event to be sent.
+   *
+   * Returns true if the event passed all protocol restrictions and a report
+   * was sent, otherwise false. The return value may be used to decide whether
+   * the default event action in the bowser component should be omitted.
+   *
+   * Note: The method will change values of the given event object
+   * to fullfill protocol and encoding restrictions.
+   */
+  public triggerMouseEvent(e: ICoreMouseEvent): boolean {
+    // range check for col/row
+    if (e.col < 0 || e.col >= this._bufferService.cols
+        || e.row < 0 || e.row >= this._bufferService.rows) {
+      return false;
+    }
+
+    // filter nonsense combinations of button + action
+    if (e.button === CoreMouseButton.WHEEL && e.action === CoreMouseAction.MOVE) {
+      return false;
+    }
+    if (e.button === CoreMouseButton.NONE && e.action !== CoreMouseAction.MOVE) {
+      return false;
+    }
+    if (e.button !== CoreMouseButton.WHEEL && (e.action === CoreMouseAction.LEFT || e.action === CoreMouseAction.RIGHT)) {
+      return false;
+    }
+
+    // report 1-based coords
+    e.col++;
+    e.row++;
+
+    // debounce move at grid level
+    if (e.action === CoreMouseAction.MOVE && this._lastEvent && this._compareEvents(this._lastEvent, e)) {
+      return false;
+    }
+
+    // apply protocol restrictions
+    if (!this._protocols[this._activeProtocol].restrict(e)) {
+      return false;
+    }
+
+    // encode report and send
+    const report = this._encodings[this._activeEncoding](e);
+    this._coreService.triggerDataEvent(report, true);
+
+    this._lastEvent = e;
+
+    return true;
+  }
+
+  public explainEvents(events: CoreMouseEventType): {[event: string]: boolean} {
+    return {
+      DOWN: !!(events & CoreMouseEventType.DOWN),
+      UP: !!(events & CoreMouseEventType.UP),
+      DRAG: !!(events & CoreMouseEventType.DRAG),
+      MOVE: !!(events & CoreMouseEventType.MOVE),
+      WHEEL: !!(events & CoreMouseEventType.WHEEL)
+    };
+  }
+
+  private _compareEvents(e1: ICoreMouseEvent, e2: ICoreMouseEvent): boolean {
+    if (e1.col !== e2.col) return false;
+    if (e1.row !== e2.row) return false;
+    if (e1.button !== e2.button) return false;
+    if (e1.action !== e2.action) return false;
+    if (e1.ctrl !== e2.ctrl) return false;
+    if (e1.alt !== e2.alt) return false;
+    if (e1.shift !== e2.shift) return false;
+    return true;
+  }
+}