From b6481f120bc31bb35edf5dbc68ccd9775fcb9037 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Jun 2018 11:03:37 +0200 Subject: [PATCH 01/13] Introduce IKeyEvent --- src/Terminal.ts | 39 +++++++++++++++++++++++++++------------ src/base/Types.ts | 14 ++++++++++++++ 2 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 src/base/Types.ts diff --git a/src/Terminal.ts b/src/Terminal.ts index 8a71df96..db4ceee3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -50,6 +50,7 @@ import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; import { ITheme, ILocalizableStrings, IMarker, IDisposable } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; +import { IKeyEvent } from './base/Types'; // reg + shift key mappings for digits and special chars const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = { @@ -1430,25 +1431,39 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } } + private _convertDomKeyEvent(event: KeyboardEvent): IKeyEvent { + return { + altKey: event.altKey, + ctrlKey: event.ctrlKey, + shiftKey: event.shiftKey, + metaKey: event.metaKey, + keyCode: event.keyCode, + key: event.key, + type: event.type + }; + } + /** * Handle a keydown event * Key Resources: * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent * @param {KeyboardEvent} ev The keydown event to be handled. */ - protected _keyDown(ev: KeyboardEvent): boolean { - if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) { + protected _keyDown(domEvent: KeyboardEvent): boolean { + if (this._customKeyEventHandler && this._customKeyEventHandler(domEvent) === false) { return false; } - if (!this._compositionHelper.keydown(ev)) { + if (!this._compositionHelper.keydown(domEvent)) { if (this.buffer.ybase !== this.buffer.ydisp) { this.scrollToBottom(); } return false; } - const result = this._evaluateKeyEscapeSequence(ev); + const event = this._convertDomKeyEvent(domEvent); + + const result = this._evaluateKeyEscapeSequence(event); // if (result.key === C0.DC3) { // XOFF // this._writeStopped = true; @@ -1458,31 +1473,31 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (result.scrollLines) { this.scrollLines(result.scrollLines); - return this.cancel(ev, true); + return this.cancel(domEvent, true); } - if (this._isThirdLevelShift(this.browser, ev)) { + if (this._isThirdLevelShift(this.browser, event)) { return true; } if (result.cancel) { // The event is canceled at the end already, is this necessary? - this.cancel(ev, true); + this.cancel(domEvent, true); } if (!result.key) { return true; } - this.emit('keydown', ev); - this.emit('key', result.key, ev); + this.emit('keydown', domEvent); + this.emit('key', result.key, domEvent); this.showCursor(); this.handler(result.key); - return this.cancel(ev, true); + return this.cancel(domEvent, true); } - private _isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean { + private _isThirdLevelShift(browser: IBrowser, ev: IKeyEvent): boolean { const thirdLevelKey = (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) || (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); @@ -1502,7 +1517,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html * @param ev The keyboard event to be translated to key escape sequence. */ - protected _evaluateKeyEscapeSequence(ev: KeyboardEvent): {cancel: boolean, key: string, scrollLines: number} { + protected _evaluateKeyEscapeSequence(ev: IKeyEvent): {cancel: boolean, key: string, scrollLines: number} { const result: {cancel: boolean, key: string, scrollLines: number} = { // Whether to cancel event propogation (NOTE: this may not be needed since the event is // canceled at the end of keyDown diff --git a/src/base/Types.ts b/src/base/Types.ts new file mode 100644 index 00000000..162aa4cd --- /dev/null +++ b/src/base/Types.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export interface IKeyEvent { + altKey: boolean; + ctrlKey: boolean; + shiftKey: boolean; + metaKey: boolean; + keyCode: number; + key: string; + type: string; +} From e167686b6da8422194d0ac0d36b86e2fb4e9a649 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Jun 2018 11:45:50 +0200 Subject: [PATCH 02/13] Remove conversion step, KeyboardEvent implements IKeyboardEvent --- src/Terminal.ts | 36 +++++++++++------------------------- src/base/Types.ts | 6 +++++- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index db4ceee3..9f396af0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -50,7 +50,7 @@ import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; import { ITheme, ILocalizableStrings, IMarker, IDisposable } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; -import { IKeyEvent } from './base/Types'; +import { IKeyboardEvent } from './base/Types'; // reg + shift key mappings for digits and special chars const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = { @@ -1431,38 +1431,24 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } } - private _convertDomKeyEvent(event: KeyboardEvent): IKeyEvent { - return { - altKey: event.altKey, - ctrlKey: event.ctrlKey, - shiftKey: event.shiftKey, - metaKey: event.metaKey, - keyCode: event.keyCode, - key: event.key, - type: event.type - }; - } - /** * Handle a keydown event * Key Resources: * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent * @param {KeyboardEvent} ev The keydown event to be handled. */ - protected _keyDown(domEvent: KeyboardEvent): boolean { - if (this._customKeyEventHandler && this._customKeyEventHandler(domEvent) === false) { + protected _keyDown(event: KeyboardEvent): boolean { + if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) { return false; } - if (!this._compositionHelper.keydown(domEvent)) { + if (!this._compositionHelper.keydown(event)) { if (this.buffer.ybase !== this.buffer.ydisp) { this.scrollToBottom(); } return false; } - const event = this._convertDomKeyEvent(domEvent); - const result = this._evaluateKeyEscapeSequence(event); // if (result.key === C0.DC3) { // XOFF @@ -1473,7 +1459,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (result.scrollLines) { this.scrollLines(result.scrollLines); - return this.cancel(domEvent, true); + return this.cancel(event, true); } if (this._isThirdLevelShift(this.browser, event)) { @@ -1482,22 +1468,22 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (result.cancel) { // The event is canceled at the end already, is this necessary? - this.cancel(domEvent, true); + this.cancel(event, true); } if (!result.key) { return true; } - this.emit('keydown', domEvent); - this.emit('key', result.key, domEvent); + this.emit('keydown', event); + this.emit('key', result.key, event); this.showCursor(); this.handler(result.key); - return this.cancel(domEvent, true); + return this.cancel(event, true); } - private _isThirdLevelShift(browser: IBrowser, ev: IKeyEvent): boolean { + private _isThirdLevelShift(browser: IBrowser, ev: IKeyboardEvent): boolean { const thirdLevelKey = (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) || (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); @@ -1517,7 +1503,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html * @param ev The keyboard event to be translated to key escape sequence. */ - protected _evaluateKeyEscapeSequence(ev: IKeyEvent): {cancel: boolean, key: string, scrollLines: number} { + protected _evaluateKeyEscapeSequence(ev: IKeyboardEvent): {cancel: boolean, key: string, scrollLines: number} { const result: {cancel: boolean, key: string, scrollLines: number} = { // Whether to cancel event propogation (NOTE: this may not be needed since the event is // canceled at the end of keyDown diff --git a/src/base/Types.ts b/src/base/Types.ts index 162aa4cd..98cb296d 100644 --- a/src/base/Types.ts +++ b/src/base/Types.ts @@ -3,7 +3,11 @@ * @license MIT */ -export interface IKeyEvent { +/** + * A keyboard event interface which does not depend on the DOM, KeyboardEvent implicitly extends + * this event. + */ +export interface IKeyboardEvent { altKey: boolean; ctrlKey: boolean; shiftKey: boolean; From b4ccbe042bdd2f8046fe346f99e114b8a533ef2b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Jun 2018 11:52:25 +0200 Subject: [PATCH 03/13] Pull keyboard event translation into core --- src/Terminal.ts | 354 +------------------------------------ src/core/Types.ts | 10 ++ src/core/input/Keyboard.ts | 353 ++++++++++++++++++++++++++++++++++++ 3 files changed, 365 insertions(+), 352 deletions(-) create mode 100644 src/core/Types.ts create mode 100644 src/core/input/Keyboard.ts diff --git a/src/Terminal.ts b/src/Terminal.ts index 9f396af0..1b7a0c1a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -51,34 +51,7 @@ import { ITheme, ILocalizableStrings, IMarker, IDisposable } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './base/Types'; - -// reg + shift key mappings for digits and special chars -const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = { - // digits 0-9 - 48: ['0', ')'], - 49: ['1', '!'], - 50: ['2', '@'], - 51: ['3', '#'], - 52: ['4', '$'], - 53: ['5', '%'], - 54: ['6', '^'], - 55: ['7', '&'], - 56: ['8', '*'], - 57: ['9', '('], - - // special chars - 186: [';', ':'], - 187: ['=', '+'], - 188: [',', '<'], - 189: ['-', '_'], - 190: ['.', '>'], - 191: ['/', '?'], - 192: ['`', '~'], - 219: ['[', '{'], - 220: ['\\', '|'], - 221: [']', '}'], - 222: ['\'', '"'] -}; +import { evaluateKeyboardEvent } from './core/input/Keyboard'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -1449,7 +1422,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return false; } - const result = this._evaluateKeyEscapeSequence(event); + const result = evaluateKeyboardEvent(event); // if (result.key === C0.DC3) { // XOFF // this._writeStopped = true; @@ -1496,329 +1469,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); } - /** - * Returns an object that determines how a KeyboardEvent should be handled. The key of the - * returned value is the new key code to pass to the PTY. - * - * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * @param ev The keyboard event to be translated to key escape sequence. - */ - protected _evaluateKeyEscapeSequence(ev: IKeyboardEvent): {cancel: boolean, key: string, scrollLines: number} { - const result: {cancel: boolean, key: string, scrollLines: number} = { - // Whether to cancel event propogation (NOTE: this may not be needed since the event is - // canceled at the end of keyDown - cancel: false, - // The new key even to emit - key: undefined, - // The number of characters to scroll, if this is defined it will cancel the event - scrollLines: undefined - }; - const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0); - switch (ev.keyCode) { - case 0: - if (ev.key === 'UIKeyInputUpArrow') { - if (this.applicationCursor) { - result.key = C0.ESC + 'OA'; - } else { - result.key = C0.ESC + '[A'; - } - } - else if (ev.key === 'UIKeyInputLeftArrow') { - if (this.applicationCursor) { - result.key = C0.ESC + 'OD'; - } else { - result.key = C0.ESC + '[D'; - } - } - else if (ev.key === 'UIKeyInputRightArrow') { - if (this.applicationCursor) { - result.key = C0.ESC + 'OC'; - } else { - result.key = C0.ESC + '[C'; - } - } - else if (ev.key === 'UIKeyInputDownArrow') { - if (this.applicationCursor) { - result.key = C0.ESC + 'OB'; - } else { - result.key = C0.ESC + '[B'; - } - } - break; - case 8: - // backspace - if (ev.shiftKey) { - result.key = C0.BS; // ^H - break; - } else if (ev.altKey) { - result.key = C0.ESC + C0.DEL; // \e ^? - break; - } - result.key = C0.DEL; // ^? - break; - case 9: - // tab - if (ev.shiftKey) { - result.key = C0.ESC + '[Z'; - break; - } - result.key = C0.HT; - result.cancel = true; - break; - case 13: - // return/enter - result.key = C0.CR; - result.cancel = true; - break; - case 27: - // escape - result.key = C0.ESC; - result.cancel = true; - break; - case 37: - // left-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D'; - // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards - // http://unix.stackexchange.com/a/108106 - // macOS uses different escape sequences than linux - if (result.key === C0.ESC + '[1;3D') { - result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OD'; - } else { - result.key = C0.ESC + '[D'; - } - break; - case 39: - // right-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C'; - // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward - // http://unix.stackexchange.com/a/108106 - // macOS uses different escape sequences than linux - if (result.key === C0.ESC + '[1;3C') { - result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OC'; - } else { - result.key = C0.ESC + '[C'; - } - break; - case 38: - // up-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A'; - // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow - // http://unix.stackexchange.com/a/108106 - if (result.key === C0.ESC + '[1;3A') { - result.key = C0.ESC + '[1;5A'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OA'; - } else { - result.key = C0.ESC + '[A'; - } - break; - case 40: - // down-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B'; - // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow - // http://unix.stackexchange.com/a/108106 - if (result.key === C0.ESC + '[1;3B') { - result.key = C0.ESC + '[1;5B'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OB'; - } else { - result.key = C0.ESC + '[B'; - } - break; - case 45: - // insert - if (!ev.shiftKey && !ev.ctrlKey) { - // or + are used to - // copy-paste on some systems. - result.key = C0.ESC + '[2~'; - } - break; - case 46: - // delete - if (modifiers) { - result.key = C0.ESC + '[3;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[3~'; - } - break; - case 36: - // home - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H'; - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OH'; - } else { - result.key = C0.ESC + '[H'; - } - break; - case 35: - // end - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F'; - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OF'; - } else { - result.key = C0.ESC + '[F'; - } - break; - case 33: - // page up - if (ev.shiftKey) { - result.scrollLines = -(this.rows - 1); - } else { - result.key = C0.ESC + '[5~'; - } - break; - case 34: - // page down - if (ev.shiftKey) { - result.scrollLines = this.rows - 1; - } else { - result.key = C0.ESC + '[6~'; - } - break; - case 112: - // F1-F12 - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P'; - } else { - result.key = C0.ESC + 'OP'; - } - break; - case 113: - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q'; - } else { - result.key = C0.ESC + 'OQ'; - } - break; - case 114: - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R'; - } else { - result.key = C0.ESC + 'OR'; - } - break; - case 115: - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S'; - } else { - result.key = C0.ESC + 'OS'; - } - break; - case 116: - if (modifiers) { - result.key = C0.ESC + '[15;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[15~'; - } - break; - case 117: - if (modifiers) { - result.key = C0.ESC + '[17;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[17~'; - } - break; - case 118: - if (modifiers) { - result.key = C0.ESC + '[18;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[18~'; - } - break; - case 119: - if (modifiers) { - result.key = C0.ESC + '[19;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[19~'; - } - break; - case 120: - if (modifiers) { - result.key = C0.ESC + '[20;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[20~'; - } - break; - case 121: - if (modifiers) { - result.key = C0.ESC + '[21;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[21~'; - } - break; - case 122: - if (modifiers) { - result.key = C0.ESC + '[23;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[23~'; - } - break; - case 123: - if (modifiers) { - result.key = C0.ESC + '[24;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[24~'; - } - break; - default: - // a-z and space - if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { - if (ev.keyCode >= 65 && ev.keyCode <= 90) { - result.key = String.fromCharCode(ev.keyCode - 64); - } else if (ev.keyCode === 32) { - // NUL - result.key = String.fromCharCode(0); - } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { - // escape, file sep, group sep, record sep, unit sep - result.key = String.fromCharCode(ev.keyCode - 51 + 27); - } else if (ev.keyCode === 56) { - // delete - result.key = String.fromCharCode(127); - } else if (ev.keyCode === 219) { - // ^[ - Control Sequence Introducer (CSI) - result.key = String.fromCharCode(27); - } else if (ev.keyCode === 220) { - // ^\ - String Terminator (ST) - result.key = String.fromCharCode(28); - } else if (ev.keyCode === 221) { - // ^] - Operating System Command (OSC) - result.key = String.fromCharCode(29); - } - } else if ((!this.browser.isMac || this.options.macOptionIsMeta) && ev.altKey && !ev.metaKey) { - // On macOS this is a third level shift when !macOptionIsMeta. Use instead. - const keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode]; - const key = keyMapping && keyMapping[!ev.shiftKey ? 0 : 1]; - if (key) { - result.key = C0.ESC + key; - } else if (ev.keyCode >= 65 && ev.keyCode <= 90) { - const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32; - result.key = C0.ESC + String.fromCharCode(keyCode); - } - } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { - if (ev.keyCode === 65) { // cmd + a - this.selectAll(); - } - } - break; - } - - return result; - } - /** * Set the G level of the terminal * @param g diff --git a/src/core/Types.ts b/src/core/Types.ts new file mode 100644 index 00000000..374b8f7d --- /dev/null +++ b/src/core/Types.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export interface IKeyboardResult { + cancel: boolean; + key: string; + scrollLines: number; +} diff --git a/src/core/input/Keyboard.ts b/src/core/input/Keyboard.ts new file mode 100644 index 00000000..caaa9189 --- /dev/null +++ b/src/core/input/Keyboard.ts @@ -0,0 +1,353 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * @license MIT + */ + +import { IKeyboardEvent } from '../../base/Types'; +import { IKeyboardResult } from '../Types'; +import { C0 } from '../../EscapeSequences'; + +// reg + shift key mappings for digits and special chars +const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = { + // digits 0-9 + 48: ['0', ')'], + 49: ['1', '!'], + 50: ['2', '@'], + 51: ['3', '#'], + 52: ['4', '$'], + 53: ['5', '%'], + 54: ['6', '^'], + 55: ['7', '&'], + 56: ['8', '*'], + 57: ['9', '('], + + // special chars + 186: [';', ':'], + 187: ['=', '+'], + 188: [',', '<'], + 189: ['-', '_'], + 190: ['.', '>'], + 191: ['/', '?'], + 192: ['`', '~'], + 219: ['[', '{'], + 220: ['\\', '|'], + 221: [']', '}'], + 222: ['\'', '"'] +}; + +export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { + const result: IKeyboardResult = { + // Whether to cancel event propogation (NOTE: this may not be needed since the event is + // canceled at the end of keyDown + cancel: false, + // The new key even to emit + key: undefined, + // The number of characters to scroll, if this is defined it will cancel the event + scrollLines: undefined + }; + const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0); + switch (ev.keyCode) { + case 0: + if (ev.key === 'UIKeyInputUpArrow') { + if (this.applicationCursor) { + result.key = C0.ESC + 'OA'; + } else { + result.key = C0.ESC + '[A'; + } + } + else if (ev.key === 'UIKeyInputLeftArrow') { + if (this.applicationCursor) { + result.key = C0.ESC + 'OD'; + } else { + result.key = C0.ESC + '[D'; + } + } + else if (ev.key === 'UIKeyInputRightArrow') { + if (this.applicationCursor) { + result.key = C0.ESC + 'OC'; + } else { + result.key = C0.ESC + '[C'; + } + } + else if (ev.key === 'UIKeyInputDownArrow') { + if (this.applicationCursor) { + result.key = C0.ESC + 'OB'; + } else { + result.key = C0.ESC + '[B'; + } + } + break; + case 8: + // backspace + if (ev.shiftKey) { + result.key = C0.BS; // ^H + break; + } else if (ev.altKey) { + result.key = C0.ESC + C0.DEL; // \e ^? + break; + } + result.key = C0.DEL; // ^? + break; + case 9: + // tab + if (ev.shiftKey) { + result.key = C0.ESC + '[Z'; + break; + } + result.key = C0.HT; + result.cancel = true; + break; + case 13: + // return/enter + result.key = C0.CR; + result.cancel = true; + break; + case 27: + // escape + result.key = C0.ESC; + result.cancel = true; + break; + case 37: + // left-arrow + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D'; + // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards + // http://unix.stackexchange.com/a/108106 + // macOS uses different escape sequences than linux + if (result.key === C0.ESC + '[1;3D') { + result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D'; + } + } else if (this.applicationCursor) { + result.key = C0.ESC + 'OD'; + } else { + result.key = C0.ESC + '[D'; + } + break; + case 39: + // right-arrow + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C'; + // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward + // http://unix.stackexchange.com/a/108106 + // macOS uses different escape sequences than linux + if (result.key === C0.ESC + '[1;3C') { + result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C'; + } + } else if (this.applicationCursor) { + result.key = C0.ESC + 'OC'; + } else { + result.key = C0.ESC + '[C'; + } + break; + case 38: + // up-arrow + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A'; + // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow + // http://unix.stackexchange.com/a/108106 + if (result.key === C0.ESC + '[1;3A') { + result.key = C0.ESC + '[1;5A'; + } + } else if (this.applicationCursor) { + result.key = C0.ESC + 'OA'; + } else { + result.key = C0.ESC + '[A'; + } + break; + case 40: + // down-arrow + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B'; + // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow + // http://unix.stackexchange.com/a/108106 + if (result.key === C0.ESC + '[1;3B') { + result.key = C0.ESC + '[1;5B'; + } + } else if (this.applicationCursor) { + result.key = C0.ESC + 'OB'; + } else { + result.key = C0.ESC + '[B'; + } + break; + case 45: + // insert + if (!ev.shiftKey && !ev.ctrlKey) { + // or + are used to + // copy-paste on some systems. + result.key = C0.ESC + '[2~'; + } + break; + case 46: + // delete + if (modifiers) { + result.key = C0.ESC + '[3;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[3~'; + } + break; + case 36: + // home + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H'; + } else if (this.applicationCursor) { + result.key = C0.ESC + 'OH'; + } else { + result.key = C0.ESC + '[H'; + } + break; + case 35: + // end + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F'; + } else if (this.applicationCursor) { + result.key = C0.ESC + 'OF'; + } else { + result.key = C0.ESC + '[F'; + } + break; + case 33: + // page up + if (ev.shiftKey) { + result.scrollLines = -(this.rows - 1); + } else { + result.key = C0.ESC + '[5~'; + } + break; + case 34: + // page down + if (ev.shiftKey) { + result.scrollLines = this.rows - 1; + } else { + result.key = C0.ESC + '[6~'; + } + break; + case 112: + // F1-F12 + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P'; + } else { + result.key = C0.ESC + 'OP'; + } + break; + case 113: + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q'; + } else { + result.key = C0.ESC + 'OQ'; + } + break; + case 114: + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R'; + } else { + result.key = C0.ESC + 'OR'; + } + break; + case 115: + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S'; + } else { + result.key = C0.ESC + 'OS'; + } + break; + case 116: + if (modifiers) { + result.key = C0.ESC + '[15;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[15~'; + } + break; + case 117: + if (modifiers) { + result.key = C0.ESC + '[17;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[17~'; + } + break; + case 118: + if (modifiers) { + result.key = C0.ESC + '[18;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[18~'; + } + break; + case 119: + if (modifiers) { + result.key = C0.ESC + '[19;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[19~'; + } + break; + case 120: + if (modifiers) { + result.key = C0.ESC + '[20;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[20~'; + } + break; + case 121: + if (modifiers) { + result.key = C0.ESC + '[21;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[21~'; + } + break; + case 122: + if (modifiers) { + result.key = C0.ESC + '[23;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[23~'; + } + break; + case 123: + if (modifiers) { + result.key = C0.ESC + '[24;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[24~'; + } + break; + default: + // a-z and space + if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { + if (ev.keyCode >= 65 && ev.keyCode <= 90) { + result.key = String.fromCharCode(ev.keyCode - 64); + } else if (ev.keyCode === 32) { + // NUL + result.key = String.fromCharCode(0); + } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { + // escape, file sep, group sep, record sep, unit sep + result.key = String.fromCharCode(ev.keyCode - 51 + 27); + } else if (ev.keyCode === 56) { + // delete + result.key = String.fromCharCode(127); + } else if (ev.keyCode === 219) { + // ^[ - Control Sequence Introducer (CSI) + result.key = String.fromCharCode(27); + } else if (ev.keyCode === 220) { + // ^\ - String Terminator (ST) + result.key = String.fromCharCode(28); + } else if (ev.keyCode === 221) { + // ^] - Operating System Command (OSC) + result.key = String.fromCharCode(29); + } + } else if ((!this.browser.isMac || this.options.macOptionIsMeta) && ev.altKey && !ev.metaKey) { + // On macOS this is a third level shift when !macOptionIsMeta. Use instead. + const keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode]; + const key = keyMapping && keyMapping[!ev.shiftKey ? 0 : 1]; + if (key) { + result.key = C0.ESC + key; + } else if (ev.keyCode >= 65 && ev.keyCode <= 90) { + const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32; + result.key = C0.ESC + String.fromCharCode(keyCode); + } + } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { + if (ev.keyCode === 65) { // cmd + a + this.selectAll(); + } + } + break; + } + + return result; +} From 95217ca931397bbdcf8254ae4465583a87dcf8ab Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Jun 2018 12:21:06 +0200 Subject: [PATCH 04/13] Remove core/input/Keyboard's dep on Terminal --- src/Terminal.test.ts | 257 +----------------------------- src/Terminal.ts | 13 +- src/core/Types.ts | 11 +- src/core/input/Keyboard.test.ts | 273 ++++++++++++++++++++++++++++++++ src/core/input/Keyboard.ts | 49 +++--- 5 files changed, 324 insertions(+), 279 deletions(-) create mode 100644 src/core/input/Keyboard.test.ts diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index f6cbad52..8974b784 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -13,7 +13,6 @@ const INIT_COLS = 80; const INIT_ROWS = 24; class TestTerminal extends Terminal { - public evaluateKeyEscapeSequence(ev: any): {cancel: boolean, key: string, scrollLines: number} { return this._evaluateKeyEscapeSequence(ev); } public keyDown(ev: any): boolean { return this._keyDown(ev); } public keyPress(ev: any): boolean { return this._keyPress(ev); } } @@ -296,22 +295,19 @@ describe('term.js addons', () => { }); }); - describe('keyDown', () => { + describe('keyPress', () => { it('should scroll down, when a key is pressed and terminal is scrolled up', () => { - // Override _evaluateKeyEscapeSequence to return cancel code - (term)._evaluateKeyEscapeSequence = () => { - return { key: 'a' }; - }; const event = { type: 'keydown', - keyCode: 0, + key: 'a', + keyCode: 65, preventDefault: () => {}, stopPropagation: () => {} }; term.buffer.ydisp = 0; term.buffer.ybase = 40; - term.keyDown(event); + term.keyPress(event); // Ensure that now the terminal is scrolled to bottom assert.equal(term.buffer.ydisp, term.buffer.ybase); @@ -330,7 +326,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.ydisp, startYDisp); term.scrollLines(-1); assert.equal(term.buffer.ydisp, startYDisp - 1); - term.keyDown({ keyCode: 0 }); + term.keyPress({ keyCode: 0 }); assert.equal(term.buffer.ydisp, startYDisp - 1); }); }); @@ -468,249 +464,6 @@ describe('term.js addons', () => { }); }); - describe('evaluateKeyEscapeSequence', () => { - it('should return the correct escape sequence for unmodified keys', () => { - // Backspace - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 8 }).key, '\x7f'); // ^? - // Tab - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 9 }).key, '\t'); - // Return/enter - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 13 }).key, '\r'); // CR - // Escape - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 27 }).key, '\x1b'); - // Page up, page down - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~ - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~ - // End, Home - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1b[F'); // SS3 F - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1b[H'); // SS3 H - // Left, up, right, down arrows - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 37 }).key, '\x1b[D'); // CSI D - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 38 }).key, '\x1b[A'); // CSI A - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 39 }).key, '\x1b[C'); // CSI C - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 40 }).key, '\x1b[B'); // CSI B - // Insert - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~ - // Delete - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~ - // F1-F12 - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); // SS3 P - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); // SS3 R - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); // SS3 S - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~ - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~ - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~ - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~ - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~ - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~ - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~ - assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~ - }); - it('should return \\x1b[3;5~ for ctrl+delete', () => { - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 46 }).key, '\x1b[3;5~'); - }); - it('should return \\x1b[3;2~ for shift+delete', () => { - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~'); - }); - it('should return \\x1b[3;3~ for alt+delete', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~'); - }); - it('should return \\x1b[5D for ctrl+left', () => { - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D - }); - it('should return \\x1b[5C for ctrl+right', () => { - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C - }); - it('should return \\x1b[5A for ctrl+up', () => { - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A - }); - it('should return \\x1b[5B for ctrl+down', () => { - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B - }); - - describe('On non-macOS platforms', () => { - beforeEach(() => { - term.browser.isMac = false; - }); - // Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100 - // http://unix.stackexchange.com/a/108106 - it('should return \\x1b[5D for alt+left', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D - }); - it('should return \\x1b[5C for alt+right', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C - }); - it('should return \\x1ba for alt+a', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, '\x1ba'); - }); - }); - - describe('On macOS platforms', () => { - beforeEach(() => { - term.browser.isMac = true; - }); - it('should return \\x1bb for alt+left', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1bb'); // CSI 5 D - }); - it('should return \\x1bf for alt+right', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1bf'); // CSI 5 C - }); - it('should return undefined for alt+a', () => { - assert.strictEqual(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, undefined); - }); - }); - - describe('with macOptionIsMeta', () => { - beforeEach(() => { - term.browser.isMac = true; - term.setOption('macOptionIsMeta', true); - }); - it('should return \\x1ba for alt+a', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, '\x1ba'); - }); - }); - - it('should return \\x1b[5A for alt+up', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A - }); - it('should return \\x1b[5B for alt+down', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B - }); - it('should return the correct escape sequence for modified F1-F12 keys', () => { - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 113 }).key, '\x1b[1;2Q'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 114 }).key, '\x1b[1;2R'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 115 }).key, '\x1b[1;2S'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 116 }).key, '\x1b[15;2~'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 117 }).key, '\x1b[17;2~'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 118 }).key, '\x1b[18;2~'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 119 }).key, '\x1b[19;2~'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 120 }).key, '\x1b[20;2~'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 121 }).key, '\x1b[21;2~'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 122 }).key, '\x1b[23;2~'); - assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 123 }).key, '\x1b[24;2~'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 112 }).key, '\x1b[1;3P'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 113 }).key, '\x1b[1;3Q'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 114 }).key, '\x1b[1;3R'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 115 }).key, '\x1b[1;3S'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 116 }).key, '\x1b[15;3~'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 117 }).key, '\x1b[17;3~'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 118 }).key, '\x1b[18;3~'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 119 }).key, '\x1b[19;3~'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 120 }).key, '\x1b[20;3~'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 121 }).key, '\x1b[21;3~'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 122 }).key, '\x1b[23;3~'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 123 }).key, '\x1b[24;3~'); - - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 112 }).key, '\x1b[1;5P'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 113 }).key, '\x1b[1;5Q'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 114 }).key, '\x1b[1;5R'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 115 }).key, '\x1b[1;5S'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 116 }).key, '\x1b[15;5~'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 117 }).key, '\x1b[17;5~'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 118 }).key, '\x1b[18;5~'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 119 }).key, '\x1b[19;5~'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 120 }).key, '\x1b[20;5~'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 121 }).key, '\x1b[21;5~'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~'); - assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~'); - }); - - // Characters using ctrl+alt sequences - it('should return proper sequence for ctrl+alt+a', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, ctrlKey: true, keyCode: 65 }).key, '\x1b\x01'); - }); - - // Characters using alt sequences (numbers) - it('should return proper sequences for alt+0', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 48 }).key, '\x1b0'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 48 }).key, '\x1b)'); - }); - it('should return proper sequences for alt+1', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 49 }).key, '\x1b1'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 49 }).key, '\x1b!'); - }); - it('should return proper sequences for alt+2', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 50 }).key, '\x1b2'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 50 }).key, '\x1b@'); - }); - it('should return proper sequences for alt+3', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 51 }).key, '\x1b3'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 51 }).key, '\x1b#'); - }); - it('should return proper sequences for alt+4', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 52 }).key, '\x1b4'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 52 }).key, '\x1b$'); - }); - it('should return proper sequences for alt+5', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 53 }).key, '\x1b5'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 53 }).key, '\x1b%'); - }); - it('should return proper sequences for alt+6', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 54 }).key, '\x1b6'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 54 }).key, '\x1b^'); - }); - it('should return proper sequences for alt+7', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 55 }).key, '\x1b7'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 55 }).key, '\x1b&'); - }); - it('should return proper sequences for alt+8', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 56 }).key, '\x1b8'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 56 }).key, '\x1b*'); - }); - it('should return proper sequences for alt+9', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 57 }).key, '\x1b9'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 57 }).key, '\x1b('); - }); - - // Characters using alt sequences (special chars) - it('should return proper sequences for alt+;', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 186 }).key, '\x1b;'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 186 }).key, '\x1b:'); - }); - it('should return proper sequences for alt+=', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 187 }).key, '\x1b='); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 187 }).key, '\x1b+'); - }); - it('should return proper sequences for alt+,', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 188 }).key, '\x1b,'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 188 }).key, '\x1b<'); - }); - it('should return proper sequences for alt+-', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 189 }).key, '\x1b-'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 189 }).key, '\x1b_'); - }); - it('should return proper sequences for alt+.', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 190 }).key, '\x1b.'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 190 }).key, '\x1b>'); - }); - it('should return proper sequences for alt+/', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 191 }).key, '\x1b/'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 191 }).key, '\x1b?'); - }); - it('should return proper sequences for alt+~', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 192 }).key, '\x1b`'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 192 }).key, '\x1b~'); - }); - it('should return proper sequences for alt+[', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 219 }).key, '\x1b['); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 219 }).key, '\x1b{'); - }); - it('should return proper sequences for alt+\\', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 220 }).key, '\x1b\\'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 220 }).key, '\x1b|'); - }); - it('should return proper sequences for alt+]', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 221 }).key, '\x1b]'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 221 }).key, '\x1b}'); - }); - it('should return proper sequences for alt+\'', () => { - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 222 }).key, '\x1b\''); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 222 }).key, '\x1b"'); - }); - }); - describe('Third level shift', () => { let evKeyDown: any; let evKeyPress: any; diff --git a/src/Terminal.ts b/src/Terminal.ts index 1b7a0c1a..a1918353 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,6 +52,7 @@ import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './base/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; +import { KeyboardResultType } from './core/Types'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -1422,7 +1423,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return false; } - const result = evaluateKeyboardEvent(event); + const result = evaluateKeyboardEvent(event, this.applicationCursor, this.browser.isMac, this.options.macOptionIsMeta); // if (result.key === C0.DC3) { // XOFF // this._writeStopped = true; @@ -1430,11 +1431,17 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // this._writeStopped = false; // } - if (result.scrollLines) { - this.scrollLines(result.scrollLines); + if (result.type === KeyboardResultType.PAGE_DOWN || result.type === KeyboardResultType.PAGE_UP) { + const scrollCount = this.rows - 1; + this.scrollLines(result.type === KeyboardResultType.PAGE_UP ? -scrollCount : scrollCount); return this.cancel(event, true); } + if (result.type === KeyboardResultType.SELECT_ALL) { + this.selectAll(); + // TODO: Verify cancel behavior is the same as before + } + if (this._isThirdLevelShift(this.browser, event)) { return true; } diff --git a/src/core/Types.ts b/src/core/Types.ts index 374b8f7d..cc5c0d1b 100644 --- a/src/core/Types.ts +++ b/src/core/Types.ts @@ -3,8 +3,15 @@ * @license MIT */ +export const enum KeyboardResultType { + SEND_KEY, + SELECT_ALL, + PAGE_UP, + PAGE_DOWN +} + export interface IKeyboardResult { + type: KeyboardResultType; cancel: boolean; - key: string; - scrollLines: number; + key: string | undefined; } diff --git a/src/core/input/Keyboard.test.ts b/src/core/input/Keyboard.test.ts new file mode 100644 index 00000000..97a12c88 --- /dev/null +++ b/src/core/input/Keyboard.test.ts @@ -0,0 +1,273 @@ + +import { assert } from 'chai'; +import { evaluateKeyboardEvent } from './Keyboard'; +import { IKeyboardResult } from '../Types'; + +/** + * A helper function for testing which allows passing in a partial event and defaults will be filled + * in on it. + */ +function testEvaluateKeyboardEvent(partialEvent: { + altKey?: boolean; + ctrlKey?: boolean; + shiftKey?: boolean; + metaKey?: boolean; + keyCode?: number; + key?: string; + type?: string; +}, partialOptions: { + applicationCursorMode?: boolean; + isMac?: boolean; + macOptionIsMeta?: boolean; +} = {}): IKeyboardResult { + const event = { + altKey: partialEvent.altKey || false, + ctrlKey: partialEvent.ctrlKey || false, + shiftKey: partialEvent.shiftKey || false, + metaKey: partialEvent.metaKey || false, + keyCode: partialEvent.keyCode || undefined, + key: partialEvent.key || '', + type: partialEvent.type || '' + }; + const options = { + applicationCursorMode: partialOptions.applicationCursorMode || false, + isMac: partialOptions.isMac || false, + macOptionIsMeta: partialOptions.macOptionIsMeta || false + }; + return evaluateKeyboardEvent(event, options.applicationCursorMode, options.isMac, options.macOptionIsMeta); +} + +describe('Keyboard', () => { + describe('evaluateKeyEscapeSequence', () => { + it('should return the correct escape sequence for unmodified keys', () => { + // Backspace + assert.equal(testEvaluateKeyboardEvent({ keyCode: 8 }).key, '\x7f'); // ^? + // Tab + assert.equal(testEvaluateKeyboardEvent({ keyCode: 9 }).key, '\t'); + // Return/enter + assert.equal(testEvaluateKeyboardEvent({ keyCode: 13 }).key, '\r'); // CR + // Escape + assert.equal(testEvaluateKeyboardEvent({ keyCode: 27 }).key, '\x1b'); + // Page up, page down + assert.equal(testEvaluateKeyboardEvent({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~ + assert.equal(testEvaluateKeyboardEvent({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~ + // End, Home + assert.equal(testEvaluateKeyboardEvent({ keyCode: 35 }).key, '\x1b[F'); // SS3 F + assert.equal(testEvaluateKeyboardEvent({ keyCode: 36 }).key, '\x1b[H'); // SS3 H + // Left, up, right, down arrows + assert.equal(testEvaluateKeyboardEvent({ keyCode: 37 }).key, '\x1b[D'); // CSI D + assert.equal(testEvaluateKeyboardEvent({ keyCode: 38 }).key, '\x1b[A'); // CSI A + assert.equal(testEvaluateKeyboardEvent({ keyCode: 39 }).key, '\x1b[C'); // CSI C + assert.equal(testEvaluateKeyboardEvent({ keyCode: 40 }).key, '\x1b[B'); // CSI B + // Insert + assert.equal(testEvaluateKeyboardEvent({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~ + // Delete + assert.equal(testEvaluateKeyboardEvent({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~ + // F1-F12 + assert.equal(testEvaluateKeyboardEvent({ keyCode: 112 }).key, '\x1bOP'); // SS3 P + assert.equal(testEvaluateKeyboardEvent({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q + assert.equal(testEvaluateKeyboardEvent({ keyCode: 114 }).key, '\x1bOR'); // SS3 R + assert.equal(testEvaluateKeyboardEvent({ keyCode: 115 }).key, '\x1bOS'); // SS3 S + assert.equal(testEvaluateKeyboardEvent({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~ + assert.equal(testEvaluateKeyboardEvent({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~ + assert.equal(testEvaluateKeyboardEvent({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~ + assert.equal(testEvaluateKeyboardEvent({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~ + assert.equal(testEvaluateKeyboardEvent({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~ + assert.equal(testEvaluateKeyboardEvent({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~ + assert.equal(testEvaluateKeyboardEvent({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~ + assert.equal(testEvaluateKeyboardEvent({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~ + }); + it('should return \\x1b[3;5~ for ctrl+delete', () => { + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 46 }).key, '\x1b[3;5~'); + }); + it('should return \\x1b[3;2~ for shift+delete', () => { + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~'); + }); + it('should return \\x1b[3;3~ for alt+delete', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~'); + }); + it('should return \\x1b[5D for ctrl+left', () => { + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D + }); + it('should return \\x1b[5C for ctrl+right', () => { + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C + }); + it('should return \\x1b[5A for ctrl+up', () => { + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A + }); + it('should return \\x1b[5B for ctrl+down', () => { + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B + }); + + describe('On non-macOS platforms', () => { + // Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100 + // http://unix.stackexchange.com/a/108106 + it('should return \\x1b[5D for alt+left', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: false }).key, '\x1b[1;5D'); // CSI 5 D + }); + it('should return \\x1b[5C for alt+right', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: false }).key, '\x1b[1;5C'); // CSI 5 C + }); + it('should return \\x1ba for alt+a', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: false }).key, '\x1ba'); + }); + }); + + describe('On macOS platforms', () => { + it('should return \\x1bb for alt+left', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: true }).key, '\x1bb'); // CSI 5 D + }); + it('should return \\x1bf for alt+right', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: true }).key, '\x1bf'); // CSI 5 C + }); + it('should return undefined for alt+a', () => { + assert.strictEqual(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true }).key, undefined), { isMac: true }; + }); + }); + + describe('with macOptionIsMeta', () => { + it('should return \\x1ba for alt+a', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true, macOptionIsMeta: true }).key, '\x1ba'); + }); + }); + + it('should return \\x1b[5A for alt+up', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A + }); + it('should return \\x1b[5B for alt+down', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B + }); + it('should return the correct escape sequence for modified F1-F12 keys', () => { + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 113 }).key, '\x1b[1;2Q'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 114 }).key, '\x1b[1;2R'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 115 }).key, '\x1b[1;2S'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 116 }).key, '\x1b[15;2~'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 117 }).key, '\x1b[17;2~'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 118 }).key, '\x1b[18;2~'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 119 }).key, '\x1b[19;2~'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 120 }).key, '\x1b[20;2~'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 121 }).key, '\x1b[21;2~'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 122 }).key, '\x1b[23;2~'); + assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 123 }).key, '\x1b[24;2~'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 112 }).key, '\x1b[1;3P'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 113 }).key, '\x1b[1;3Q'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 114 }).key, '\x1b[1;3R'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 115 }).key, '\x1b[1;3S'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 116 }).key, '\x1b[15;3~'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 117 }).key, '\x1b[17;3~'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 118 }).key, '\x1b[18;3~'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 119 }).key, '\x1b[19;3~'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 120 }).key, '\x1b[20;3~'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 121 }).key, '\x1b[21;3~'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 122 }).key, '\x1b[23;3~'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 123 }).key, '\x1b[24;3~'); + + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 112 }).key, '\x1b[1;5P'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 113 }).key, '\x1b[1;5Q'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 114 }).key, '\x1b[1;5R'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 115 }).key, '\x1b[1;5S'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 116 }).key, '\x1b[15;5~'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 117 }).key, '\x1b[17;5~'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 118 }).key, '\x1b[18;5~'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 119 }).key, '\x1b[19;5~'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 120 }).key, '\x1b[20;5~'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 121 }).key, '\x1b[21;5~'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~'); + assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~'); + }); + + // Characters using ctrl+alt sequences + it('should return proper sequence for ctrl+alt+a', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, ctrlKey: true, keyCode: 65 }).key, '\x1b\x01'); + }); + + // Characters using alt sequences (numbers) + it('should return proper sequences for alt+0', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 48 }).key, '\x1b0'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 48 }).key, '\x1b)'); + }); + it('should return proper sequences for alt+1', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 49 }).key, '\x1b1'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 49 }).key, '\x1b!'); + }); + it('should return proper sequences for alt+2', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 50 }).key, '\x1b2'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 50 }).key, '\x1b@'); + }); + it('should return proper sequences for alt+3', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 51 }).key, '\x1b3'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 51 }).key, '\x1b#'); + }); + it('should return proper sequences for alt+4', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 52 }).key, '\x1b4'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 52 }).key, '\x1b$'); + }); + it('should return proper sequences for alt+5', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 53 }).key, '\x1b5'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 53 }).key, '\x1b%'); + }); + it('should return proper sequences for alt+6', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 54 }).key, '\x1b6'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 54 }).key, '\x1b^'); + }); + it('should return proper sequences for alt+7', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 55 }).key, '\x1b7'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 55 }).key, '\x1b&'); + }); + it('should return proper sequences for alt+8', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 56 }).key, '\x1b8'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 56 }).key, '\x1b*'); + }); + it('should return proper sequences for alt+9', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 57 }).key, '\x1b9'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 57 }).key, '\x1b('); + }); + + // Characters using alt sequences (special chars) + it('should return proper sequences for alt+;', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 186 }).key, '\x1b;'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 186 }).key, '\x1b:'); + }); + it('should return proper sequences for alt+=', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 187 }).key, '\x1b='); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 187 }).key, '\x1b+'); + }); + it('should return proper sequences for alt+,', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 188 }).key, '\x1b,'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 188 }).key, '\x1b<'); + }); + it('should return proper sequences for alt+-', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 189 }).key, '\x1b-'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 189 }).key, '\x1b_'); + }); + it('should return proper sequences for alt+.', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 190 }).key, '\x1b.'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 190 }).key, '\x1b>'); + }); + it('should return proper sequences for alt+/', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 191 }).key, '\x1b/'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 191 }).key, '\x1b?'); + }); + it('should return proper sequences for alt+~', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 192 }).key, '\x1b`'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 192 }).key, '\x1b~'); + }); + it('should return proper sequences for alt+[', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 219 }).key, '\x1b['); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 219 }).key, '\x1b{'); + }); + it('should return proper sequences for alt+\\', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 220 }).key, '\x1b\\'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 220 }).key, '\x1b|'); + }); + it('should return proper sequences for alt+]', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 221 }).key, '\x1b]'); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 221 }).key, '\x1b}'); + }); + it('should return proper sequences for alt+\'', () => { + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 222 }).key, '\x1b\''); + assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 222 }).key, '\x1b"'); + }); + }); +}); diff --git a/src/core/input/Keyboard.ts b/src/core/input/Keyboard.ts index caaa9189..f4393276 100644 --- a/src/core/input/Keyboard.ts +++ b/src/core/input/Keyboard.ts @@ -5,7 +5,7 @@ */ import { IKeyboardEvent } from '../../base/Types'; -import { IKeyboardResult } from '../Types'; +import { IKeyboardResult, KeyboardResultType } from '../Types'; import { C0 } from '../../EscapeSequences'; // reg + shift key mappings for digits and special chars @@ -36,42 +36,46 @@ const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = { 222: ['\'', '"'] }; -export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { +export function evaluateKeyboardEvent( + ev: IKeyboardEvent, + applicationCursorMode: boolean, + isMac: boolean, + macOptionIsMeta: boolean +): IKeyboardResult { const result: IKeyboardResult = { + type: KeyboardResultType.SEND_KEY, // Whether to cancel event propogation (NOTE: this may not be needed since the event is // canceled at the end of keyDown cancel: false, // The new key even to emit - key: undefined, - // The number of characters to scroll, if this is defined it will cancel the event - scrollLines: undefined + key: undefined }; const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0); switch (ev.keyCode) { case 0: if (ev.key === 'UIKeyInputUpArrow') { - if (this.applicationCursor) { + if (applicationCursorMode) { result.key = C0.ESC + 'OA'; } else { result.key = C0.ESC + '[A'; } } else if (ev.key === 'UIKeyInputLeftArrow') { - if (this.applicationCursor) { + if (applicationCursorMode) { result.key = C0.ESC + 'OD'; } else { result.key = C0.ESC + '[D'; } } else if (ev.key === 'UIKeyInputRightArrow') { - if (this.applicationCursor) { + if (applicationCursorMode) { result.key = C0.ESC + 'OC'; } else { result.key = C0.ESC + '[C'; } } else if (ev.key === 'UIKeyInputDownArrow') { - if (this.applicationCursor) { + if (applicationCursorMode) { result.key = C0.ESC + 'OB'; } else { result.key = C0.ESC + '[B'; @@ -116,9 +120,9 @@ export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { // http://unix.stackexchange.com/a/108106 // macOS uses different escape sequences than linux if (result.key === C0.ESC + '[1;3D') { - result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D'; + result.key = isMac ? C0.ESC + 'b' : C0.ESC + '[1;5D'; } - } else if (this.applicationCursor) { + } else if (applicationCursorMode) { result.key = C0.ESC + 'OD'; } else { result.key = C0.ESC + '[D'; @@ -132,9 +136,9 @@ export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { // http://unix.stackexchange.com/a/108106 // macOS uses different escape sequences than linux if (result.key === C0.ESC + '[1;3C') { - result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C'; + result.key = isMac ? C0.ESC + 'f' : C0.ESC + '[1;5C'; } - } else if (this.applicationCursor) { + } else if (applicationCursorMode) { result.key = C0.ESC + 'OC'; } else { result.key = C0.ESC + '[C'; @@ -149,7 +153,7 @@ export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { if (result.key === C0.ESC + '[1;3A') { result.key = C0.ESC + '[1;5A'; } - } else if (this.applicationCursor) { + } else if (applicationCursorMode) { result.key = C0.ESC + 'OA'; } else { result.key = C0.ESC + '[A'; @@ -164,7 +168,7 @@ export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { if (result.key === C0.ESC + '[1;3B') { result.key = C0.ESC + '[1;5B'; } - } else if (this.applicationCursor) { + } else if (applicationCursorMode) { result.key = C0.ESC + 'OB'; } else { result.key = C0.ESC + '[B'; @@ -190,7 +194,7 @@ export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { // home if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H'; - } else if (this.applicationCursor) { + } else if (applicationCursorMode) { result.key = C0.ESC + 'OH'; } else { result.key = C0.ESC + '[H'; @@ -200,7 +204,7 @@ export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { // end if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F'; - } else if (this.applicationCursor) { + } else if (applicationCursorMode) { result.key = C0.ESC + 'OF'; } else { result.key = C0.ESC + '[F'; @@ -209,7 +213,7 @@ export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { case 33: // page up if (ev.shiftKey) { - result.scrollLines = -(this.rows - 1); + result.type = KeyboardResultType.PAGE_UP; } else { result.key = C0.ESC + '[5~'; } @@ -217,7 +221,7 @@ export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { case 34: // page down if (ev.shiftKey) { - result.scrollLines = this.rows - 1; + result.type = KeyboardResultType.PAGE_DOWN; } else { result.key = C0.ESC + '[6~'; } @@ -331,7 +335,7 @@ export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { // ^] - Operating System Command (OSC) result.key = String.fromCharCode(29); } - } else if ((!this.browser.isMac || this.options.macOptionIsMeta) && ev.altKey && !ev.metaKey) { + } else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) { // On macOS this is a third level shift when !macOptionIsMeta. Use instead. const keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode]; const key = keyMapping && keyMapping[!ev.shiftKey ? 0 : 1]; @@ -341,9 +345,10 @@ export function evaluateKeyboardEvent(ev: IKeyboardEvent): IKeyboardResult { const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32; result.key = C0.ESC + String.fromCharCode(keyCode); } - } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { + } else if (isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { if (ev.keyCode === 65) { // cmd + a - this.selectAll(); + result.type = KeyboardResultType.SELECT_ALL; + // TODO: Select all in terminal side } } break; From 20307ef637e9382ce7133726de949c8de1931f3a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Jun 2018 12:43:22 +0200 Subject: [PATCH 05/13] Add tests for mobile key events --- src/core/input/Keyboard.test.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/core/input/Keyboard.test.ts b/src/core/input/Keyboard.test.ts index 97a12c88..1104fc61 100644 --- a/src/core/input/Keyboard.test.ts +++ b/src/core/input/Keyboard.test.ts @@ -25,7 +25,7 @@ function testEvaluateKeyboardEvent(partialEvent: { ctrlKey: partialEvent.ctrlKey || false, shiftKey: partialEvent.shiftKey || false, metaKey: partialEvent.metaKey || false, - keyCode: partialEvent.keyCode || undefined, + keyCode: partialEvent.keyCode !== undefined ? partialEvent.keyCode : undefined, key: partialEvent.key || '', type: partialEvent.type || '' }; @@ -269,5 +269,16 @@ describe('Keyboard', () => { assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 222 }).key, '\x1b\''); assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 222 }).key, '\x1b"'); }); + + it('should handle mobile arrow events', () => { + assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputUpArrow' }).key, '\x1b[A'); + assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputUpArrow' }, { applicationCursorMode: true }).key, '\x1bOA'); + assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputLeftArrow' }).key, '\x1b[D'); + assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputLeftArrow' }, { applicationCursorMode: true }).key, '\x1bOD'); + assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputRightArrow' }).key, '\x1b[C'); + assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputRightArrow' }, { applicationCursorMode: true }).key, '\x1bOC'); + assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }).key, '\x1b[B'); + assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }, { applicationCursorMode: true }).key, '\x1bOB'); + }); }); }); From d29a527724f13767a3a80a4f37fee2f2cea28aff Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Jun 2018 16:40:36 +0100 Subject: [PATCH 06/13] Introduce the public module --- demo/main.js | 6 +- package.json | 2 +- src/Terminal.ts | 6 +- src/addons/fit/fit.ts | 8 +- src/addons/search/Interfaces.ts | 11 +- src/addons/search/SearchHelper.ts | 24 ++--- src/addons/winptyCompat/Interfaces.ts | 6 +- src/public/Terminal.ts | 139 ++++++++++++++++++++++++++ src/xterm.ts | 2 +- 9 files changed, 175 insertions(+), 29 deletions(-) create mode 100644 src/public/Terminal.ts diff --git a/demo/main.js b/demo/main.js index 13fa8e96..1ed6a180 100644 --- a/demo/main.js +++ b/demo/main.js @@ -170,7 +170,7 @@ function initOptions(term) { fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'] }; - var options = Object.keys(term.options); + var options = Object.keys(term._core.options); var booleanOptions = []; var numberOptions = []; options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => { @@ -241,8 +241,8 @@ function initOptions(term) { function updateTerminalSize() { var cols = parseInt(document.getElementById(`opt-cols`).value, 10); var rows = parseInt(document.getElementById(`opt-rows`).value, 10); - var width = (cols * term.renderer.dimensions.actualCellWidth + term.viewport.scrollBarWidth).toString() + 'px'; - var height = (rows * term.renderer.dimensions.actualCellHeight).toString() + 'px'; + var width = (cols * term._core.renderer.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px'; + var height = (rows * term._core.renderer.dimensions.actualCellHeight).toString() + 'px'; terminalContainer.style.width = width; terminalContainer.style.height = height; term.fit(); diff --git a/package.json b/package.json index c7f6f81c..4aaac342 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "xterm", "description": "Full xterm terminal, in your browser", "version": "3.4.0", - "main": "lib/Terminal.js", + "main": "lib/public/Terminal.js", "types": "typings/xterm.d.ts", "repository": "https://github.com/xtermjs/xterm.js", "license": "MIT", diff --git a/src/Terminal.ts b/src/Terminal.ts index a1918353..02e7c3ed 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -751,9 +751,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * Apply the provided addon on the `Terminal` class. * @param addon The addon to apply. */ - public static applyAddon(addon: any): void { - addon.apply(Terminal); - } + // public static applyAddon(addon: any): void { + // addon.apply(Terminal); + // } /** * XTerm mouse events diff --git a/src/addons/fit/fit.ts b/src/addons/fit/fit.ts index 68e4cfd5..ac978807 100644 --- a/src/addons/fit/fit.ts +++ b/src/addons/fit/fit.ts @@ -37,10 +37,10 @@ export function proposeGeometry(term: Terminal): IGeometry { const elementPaddingVer = elementPadding.top + elementPadding.bottom; const elementPaddingHor = elementPadding.right + elementPadding.left; const availableHeight = parentElementHeight - elementPaddingVer; - const availableWidth = parentElementWidth - elementPaddingHor - (term).viewport.scrollBarWidth; + const availableWidth = parentElementWidth - elementPaddingHor - (term)._core.viewport.scrollBarWidth; const geometry = { - cols: Math.floor(availableWidth / (term).renderer.dimensions.actualCellWidth), - rows: Math.floor(availableHeight / (term).renderer.dimensions.actualCellHeight) + cols: Math.floor(availableWidth / (term)._core.renderer.dimensions.actualCellWidth), + rows: Math.floor(availableHeight / (term)._core.renderer.dimensions.actualCellHeight) }; return geometry; } @@ -50,7 +50,7 @@ export function fit(term: Terminal): void { if (geometry) { // Force a full render if (term.rows !== geometry.rows || term.cols !== geometry.cols) { - (term).renderer.clear(); + (term)._core.renderer.clear(); term.resize(geometry.cols, geometry.rows); } } diff --git a/src/addons/search/Interfaces.ts b/src/addons/search/Interfaces.ts index 6faa03e2..926e3f36 100644 --- a/src/addons/search/Interfaces.ts +++ b/src/addons/search/Interfaces.ts @@ -5,14 +5,17 @@ import { Terminal } from 'xterm'; -export interface ISearchAddonTerminal extends Terminal { - __searchHelper?: ISearchHelper; - - // TODO: Reuse ITerminal from core +// TODO: Don't rely on this private API +export interface ITerminalCore { buffer: any; selectionManager: any; } +export interface ISearchAddonTerminal extends Terminal { + __searchHelper?: ISearchHelper; + _core: ITerminalCore; +} + export interface ISearchHelper { findNext(term: string): boolean; findPrevious(term: string): boolean; diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 2abc3b38..5ba81aa0 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -35,14 +35,14 @@ export class SearchHelper implements ISearchHelper { let result: ISearchResult; - let startRow = this._terminal.buffer.ydisp; - if (this._terminal.selectionManager.selectionEnd) { + let startRow = this._terminal._core.buffer.ydisp; + if (this._terminal._core.selectionManager.selectionEnd) { // Start from the selection end if there is a selection - startRow = this._terminal.selectionManager.selectionEnd[1]; + startRow = this._terminal._core.selectionManager.selectionEnd[1]; } // Search from ydisp + 1 to end - for (let y = startRow + 1; y < this._terminal.buffer.ybase + this._terminal.rows; y++) { + for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) { result = this._findInLine(term, y); if (result) { break; @@ -76,10 +76,10 @@ export class SearchHelper implements ISearchHelper { let result: ISearchResult; - let startRow = this._terminal.buffer.ydisp; - if (this._terminal.selectionManager.selectionStart) { + let startRow = this._terminal._core.buffer.ydisp; + if (this._terminal._core.selectionManager.selectionStart) { // Start from the selection end if there is a selection - startRow = this._terminal.selectionManager.selectionStart[1]; + startRow = this._terminal._core.selectionManager.selectionStart[1]; } // Search from ydisp + 1 to end @@ -92,7 +92,7 @@ export class SearchHelper implements ISearchHelper { // Search from the top to the current ydisp if (!result) { - for (let y = this._terminal.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) { + for (let y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) { result = this._findInLine(term, y); if (result) { break; @@ -111,11 +111,11 @@ export class SearchHelper implements ISearchHelper { * @return The search result if it was found. */ private _findInLine(term: string, y: number): ISearchResult { - const lowerStringLine = this._terminal.buffer.translateBufferLineToString(y, true).toLowerCase(); + const lowerStringLine = this._terminal._core.buffer.translateBufferLineToString(y, true).toLowerCase(); const lowerTerm = term.toLowerCase(); let searchIndex = lowerStringLine.indexOf(lowerTerm); if (searchIndex >= 0) { - const line = this._terminal.buffer.lines.get(y); + const line = this._terminal._core.buffer.lines.get(y); for (let i = 0; i < searchIndex; i++) { const charData = line[i]; // Adjust the searchIndex to normalize emoji into single chars @@ -147,8 +147,8 @@ export class SearchHelper implements ISearchHelper { if (!result) { return false; } - this._terminal.selectionManager.setSelection(result.col, result.row, result.term.length); - this._terminal.scrollLines(result.row - this._terminal.buffer.ydisp); + this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length); + this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp); return true; } } diff --git a/src/addons/winptyCompat/Interfaces.ts b/src/addons/winptyCompat/Interfaces.ts index 8e02c64b..6217c860 100644 --- a/src/addons/winptyCompat/Interfaces.ts +++ b/src/addons/winptyCompat/Interfaces.ts @@ -5,6 +5,10 @@ import { Terminal } from 'xterm'; -export interface IWinptyCompatAddonTerminal extends Terminal { +export interface ITerminalCore { buffer: any; } + +export interface IWinptyCompatAddonTerminal extends Terminal { + _core: ITerminalCore; +} diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts new file mode 100644 index 00000000..0d491a69 --- /dev/null +++ b/src/public/Terminal.ts @@ -0,0 +1,139 @@ +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme } from 'xterm'; +import { ITerminal } from '../Types'; +import { Terminal as TerminalCore } from '../Terminal'; + +export class Terminal implements ITerminalApi { + private _core: ITerminal; + + constructor(options?: ITerminalOptions) { + this._core = new TerminalCore(options); + } + + public get element(): HTMLElement { return this._core.element; } + public get textarea(): HTMLTextAreaElement { return this._core.textarea; } + public get rows(): number { return this._core.rows; } + public get cols(): number { return this._core.cols; } + public get markers(): IMarker[] { return this._core.markers; } + public blur(): void { + this._core.blur(); + } + public focus(): void { + this._core.focus(); + } + public on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void; + public on(type: 'data', listener: (...args: any[]) => void): void; + public on(type: 'key', listener: (key?: string, event?: KeyboardEvent) => void): void; + public on(type: 'keypress' | 'keydown', listener: (event?: KeyboardEvent) => void): void; + public on(type: 'refresh', listener: (data?: { start: number; end: number; }) => void): void; + public on(type: 'resize', listener: (data?: { cols: number; rows: number; }) => void): void; + public on(type: 'scroll', listener: (ydisp?: number) => void): void; + public on(type: 'title', listener: (title?: string) => void): void; + public on(type: string, listener: (...args: any[]) => void): void; + public on(type: any, listener: any): void { + this._core.on(type, listener); + } + public off(type: string, listener: (...args: any[]) => void): void { + this._core.off(type, listener); + } + public emit(type: string, data?: any): void { + this._core.emit(type, data); + } + public addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable { + return this.addDisposableListener(type, handler); + } + public resize(columns: number, rows: number): void { + this._core.resize(columns, rows); + } + public writeln(data: string): void { + this._core.writeln(data); + } + public open(parent: HTMLElement): void { + this._core.open(parent); + } + public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { + this._core.attachCustomKeyEventHandler(customKeyEventHandler); + } + public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number { + return this._core.registerLinkMatcher(regex, handler, options); + } + public deregisterLinkMatcher(matcherId: number): void { + this._core.deregisterLinkMatcher(matcherId); + } + public addMarker(cursorYOffset: number): IMarker { + return this._core.addMarker(cursorYOffset); + } + public hasSelection(): boolean { + return this._core.hasSelection(); + } + public getSelection(): string { + return this._core.getSelection(); + } + public clearSelection(): void { + this._core.clearSelection(); + } + public selectAll(): void { + this._core.selectAll(); + } + public selectLines(start: number, end: number): void { + this._core.selectLines(start, end); + } + public dispose(): void { + this._core.dispose(); + } + public destroy(): void { + this._core.destroy(); + } + public scrollLines(amount: number): void { + this._core.scrollLines(amount); + } + public scrollPages(pageCount: number): void { + this._core.scrollPages(pageCount); + } + public scrollToTop(): void { + this._core.scrollToTop(); + } + public scrollToBottom(): void { + this._core.scrollToBottom(); + } + public scrollToLine(line: number): void { + this._core.scrollToLine(line); + } + public clear(): void { + this._core.clear(); + } + public write(data: string): void { + this._core.write(data); + } + public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'rendererType' | 'termName'): string; + public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; + public getOption(key: 'colors'): string[]; + public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; + public getOption(key: 'handler'): (data: string) => void; + public getOption(key: string): any; + public getOption(key: any): any { + return this._core.getOption(key); + } + public setOption(key: 'bellSound' | 'fontFamily' | 'termName', value: string): void; + public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void; + public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void; + public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void; + public setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; + public setOption(key: 'colors', value: string[]): void; + public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; + public setOption(key: 'handler', value: (data: string) => void): void; + public setOption(key: 'theme', value: ITheme): void; + public setOption(key: 'cols' | 'rows', value: number): void; + public setOption(key: string, value: any): void; + public setOption(key: any, value: any): void { + this._core.setOption(key, value); + } + public refresh(start: number, end: number): void { + this._core.refresh(start, end); + } + public reset(): void { + this._core.reset(); + } + public static applyAddon(addon: any): void { + addon.apply(Terminal); + } +} diff --git a/src/xterm.ts b/src/xterm.ts index 6df3a4c0..81b95805 100644 --- a/src/xterm.ts +++ b/src/xterm.ts @@ -5,6 +5,6 @@ * This file is the entry point for browserify. */ -import { Terminal } from './Terminal'; +import { Terminal } from './public/Terminal'; module.exports = Terminal; From 410d506f34e520a9b884b60663c8ac4f1e91a0b9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Jun 2018 16:44:50 +0100 Subject: [PATCH 07/13] Fix applyAddon test --- src/Terminal.test.ts | 8 -------- src/public/Terminal.test.ts | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 src/public/Terminal.test.ts diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 8974b784..0745ddda 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -5,7 +5,6 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; -import * as attach from './addons/attach/attach'; import { MockViewport, MockCompositionHelper, MockRenderer } from './utils/TestUtils.test'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; @@ -52,13 +51,6 @@ describe('term.js addons', () => { }); }); - it('should apply addons with Terminal.applyAddon', () => { - Terminal.applyAddon(attach); - // Test that addon was applied successfully, adding attach to Terminal's - // prototype. - assert.equal(typeof (Terminal).prototype.attach, 'function'); - }); - describe('getOption', () => { it('should retrieve the option correctly', () => { // In the `options` namespace. diff --git a/src/public/Terminal.test.ts b/src/public/Terminal.test.ts new file mode 100644 index 00000000..06c8f1d5 --- /dev/null +++ b/src/public/Terminal.test.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { Terminal } from './Terminal'; +import * as attach from '../addons/attach/attach'; + +describe('Terminal', () => { + it('should apply addons with Terminal.applyAddon', () => { + Terminal.applyAddon(attach); + // Test that addon was applied successfully, adding attach to Terminal's + // prototype. + assert.equal(typeof (Terminal).prototype.attach, 'function'); + }); +}); From 12e7f80037d66cebe7bb02cc38aa1e5a8cca74c2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Jun 2018 16:53:28 +0100 Subject: [PATCH 08/13] Move Charsets to core --- src/InputHandler.ts | 4 ++-- src/Parser.ts | 2 +- src/Types.ts | 5 +---- src/core/Types.ts | 4 ++++ src/{ => core/data}/Charsets.ts | 2 +- src/public/Terminal.ts | 5 +++++ 6 files changed, 14 insertions(+), 8 deletions(-) rename src/{ => core/data}/Charsets.ts (99%) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index dbf6dfb2..84276b5b 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -4,9 +4,9 @@ * @license MIT */ -import { CharData, IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, ICharset } from './Types'; +import { CharData, IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer } from './Types'; import { C0, C1 } from './EscapeSequences'; -import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; +import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR } from './Buffer'; import { FLAGS } from './renderer/Types'; import { wcwidth } from './CharWidth'; diff --git a/src/Parser.ts b/src/Parser.ts index 519b9609..3675e1f0 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -6,7 +6,7 @@ import { C0 } from './EscapeSequences'; import { IInputHandler, IInputHandlingTerminal } from './Types'; -import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; +import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {}; normalStateHandler[C0.BEL] = (parser, handler) => handler.bell(); diff --git a/src/Types.ts b/src/Types.ts index 96eec3c4..1de223f7 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -6,6 +6,7 @@ import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter } from 'xterm'; import { IColorSet, IRenderer } from './renderer/Types'; import { IMouseZoneManager } from './input/Types'; +import { ICharset } from './core/Types'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; @@ -191,10 +192,6 @@ export interface ILinkMatcher { willLinkActivate?: (event: MouseEvent, uri: string) => boolean; } -export interface ICharset { - [key: string]: string; -} - export interface ILinkHoverEvent { x1: number; y1: number; diff --git a/src/core/Types.ts b/src/core/Types.ts index cc5c0d1b..4001e448 100644 --- a/src/core/Types.ts +++ b/src/core/Types.ts @@ -15,3 +15,7 @@ export interface IKeyboardResult { cancel: boolean; key: string | undefined; } + +export interface ICharset { + [key: string]: string; +} diff --git a/src/Charsets.ts b/src/core/data/Charsets.ts similarity index 99% rename from src/Charsets.ts rename to src/core/data/Charsets.ts index fe0112ec..b49ee77f 100644 --- a/src/Charsets.ts +++ b/src/core/data/Charsets.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ICharset } from './Types'; +import { ICharset } from '../Types'; /** * The character sets supported by the terminal. These enable several languages diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 0d491a69..f137131b 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme } from 'xterm'; import { ITerminal } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; From 6a3ed43ad2cac03d0cc515dd87d931a375a9a39a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Jun 2018 16:56:29 +0100 Subject: [PATCH 09/13] Move EscapeSequences into base --- src/InputHandler.ts | 3 ++- src/Parser.ts | 2 +- src/Terminal.ts | 6 +++--- src/{ => base/data}/EscapeSequences.ts | 0 src/core/input/Keyboard.ts | 2 +- src/handlers/AltClickHandler.ts | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) rename src/{ => base/data}/EscapeSequences.ts (100%) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 84276b5b..39ad6d06 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -5,12 +5,13 @@ */ import { CharData, IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer } from './Types'; -import { C0, C1 } from './EscapeSequences'; +import { C0, C1 } from './base/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 } from './Buffer'; import { FLAGS } from './renderer/Types'; import { wcwidth } from './CharWidth'; import { EscapeSequenceParser } from './EscapeSequenceParser'; +import { ICharset } from './core/Types'; /** * Map collect to glevel. Used in `selectCharset`. diff --git a/src/Parser.ts b/src/Parser.ts index 3675e1f0..f0ce93dc 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { C0 } from './EscapeSequences'; +import { C0 } from './base/data/EscapeSequences'; import { IInputHandler, IInputHandlingTerminal } from './Types'; import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; diff --git a/src/Terminal.ts b/src/Terminal.ts index 02e7c3ed..bbbdad7f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData } from './Types'; import { IMouseZoneManager } from './input/Types'; import { IRenderer } from './renderer/Types'; import { BufferSet } from './BufferSet'; @@ -30,7 +30,7 @@ import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './EventEmitter'; import { Viewport } from './Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard'; -import { C0 } from './EscapeSequences'; +import { C0 } from './base/data/EscapeSequences'; import { InputHandler } from './InputHandler'; // import { Parser } from './Parser'; import { Renderer } from './renderer/Renderer'; @@ -52,7 +52,7 @@ import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './base/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; -import { KeyboardResultType } from './core/Types'; +import { KeyboardResultType, ICharset } from './core/Types'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; diff --git a/src/EscapeSequences.ts b/src/base/data/EscapeSequences.ts similarity index 100% rename from src/EscapeSequences.ts rename to src/base/data/EscapeSequences.ts diff --git a/src/core/input/Keyboard.ts b/src/core/input/Keyboard.ts index f4393276..a6482311 100644 --- a/src/core/input/Keyboard.ts +++ b/src/core/input/Keyboard.ts @@ -6,7 +6,7 @@ import { IKeyboardEvent } from '../../base/Types'; import { IKeyboardResult, KeyboardResultType } from '../Types'; -import { C0 } from '../../EscapeSequences'; +import { C0 } from '../../base/data/EscapeSequences'; // reg + shift key mappings for digits and special chars const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = { diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 8f1cf2df..b6cb0da1 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -4,7 +4,7 @@ */ import { ITerminal, ICircularList, LineData } from '../Types'; -import { C0 } from '../EscapeSequences'; +import { C0 } from '../base/data/EscapeSequences'; const enum Direction { UP = 'A', From 8c3376911f3915865056de3cf8fac7c2552e824d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Jun 2018 17:05:13 +0200 Subject: [PATCH 10/13] Fix winptyCompat compile --- src/addons/winptyCompat/winptyCompat.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index 9cde62e0..84b21590 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -26,11 +26,11 @@ export function winptyCompatInit(terminal: Terminal): void { // Windows when text reaches the end of the terminal it's likely going to be // wrapped. addonTerminal.on('linefeed', () => { - const line = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y - 1); + const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1); const lastChar = line[addonTerminal.cols - 1]; if (lastChar[3] !== 32 /* ' ' */) { - const nextLine = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y); + const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y); (nextLine).isWrapped = true; } }); From bb4db719580fc621d7070e61b562fcac6f003b44 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Jun 2018 17:07:43 +0200 Subject: [PATCH 11/13] Rename base/ to common/ --- src/InputHandler.ts | 2 +- src/Parser.ts | 2 +- src/Terminal.ts | 4 ++-- src/{base => common}/Types.ts | 0 src/{base => common}/data/EscapeSequences.ts | 0 src/core/input/Keyboard.ts | 4 ++-- src/handlers/AltClickHandler.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) rename src/{base => common}/Types.ts (100%) rename src/{base => common}/data/EscapeSequences.ts (100%) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 39ad6d06..9df24c7d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -5,7 +5,7 @@ */ import { CharData, IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer } from './Types'; -import { C0, C1 } from './base/data/EscapeSequences'; +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 } from './Buffer'; import { FLAGS } from './renderer/Types'; diff --git a/src/Parser.ts b/src/Parser.ts index f0ce93dc..4086de19 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { C0 } from './base/data/EscapeSequences'; +import { C0 } from './common/data/EscapeSequences'; import { IInputHandler, IInputHandlingTerminal } from './Types'; import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; diff --git a/src/Terminal.ts b/src/Terminal.ts index bbbdad7f..cd3f886d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -30,7 +30,7 @@ import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './EventEmitter'; import { Viewport } from './Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard'; -import { C0 } from './base/data/EscapeSequences'; +import { C0 } from './common/data/EscapeSequences'; import { InputHandler } from './InputHandler'; // import { Parser } from './Parser'; import { Renderer } from './renderer/Renderer'; @@ -50,7 +50,7 @@ import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; import { ITheme, ILocalizableStrings, IMarker, IDisposable } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; -import { IKeyboardEvent } from './base/Types'; +import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; diff --git a/src/base/Types.ts b/src/common/Types.ts similarity index 100% rename from src/base/Types.ts rename to src/common/Types.ts diff --git a/src/base/data/EscapeSequences.ts b/src/common/data/EscapeSequences.ts similarity index 100% rename from src/base/data/EscapeSequences.ts rename to src/common/data/EscapeSequences.ts diff --git a/src/core/input/Keyboard.ts b/src/core/input/Keyboard.ts index a6482311..cf290275 100644 --- a/src/core/input/Keyboard.ts +++ b/src/core/input/Keyboard.ts @@ -4,9 +4,9 @@ * @license MIT */ -import { IKeyboardEvent } from '../../base/Types'; +import { IKeyboardEvent } from '../../common/Types'; import { IKeyboardResult, KeyboardResultType } from '../Types'; -import { C0 } from '../../base/data/EscapeSequences'; +import { C0 } from '../../common/data/EscapeSequences'; // reg + shift key mappings for digits and special chars const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = { diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index b6cb0da1..344a5543 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -4,7 +4,7 @@ */ import { ITerminal, ICircularList, LineData } from '../Types'; -import { C0 } from '../base/data/EscapeSequences'; +import { C0 } from '../common/data/EscapeSequences'; const enum Direction { UP = 'A', From 57959249d819eca8991eb356ab1942063241d8a3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Jun 2018 17:17:21 +0200 Subject: [PATCH 12/13] Resolve todos --- src/Terminal.ts | 9 --------- src/core/input/Keyboard.ts | 1 - 2 files changed, 10 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index cd3f886d..f57d3c11 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -747,14 +747,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } } - /** - * Apply the provided addon on the `Terminal` class. - * @param addon The addon to apply. - */ - // public static applyAddon(addon: any): void { - // addon.apply(Terminal); - // } - /** * XTerm mouse events * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking @@ -1439,7 +1431,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (result.type === KeyboardResultType.SELECT_ALL) { this.selectAll(); - // TODO: Verify cancel behavior is the same as before } if (this._isThirdLevelShift(this.browser, event)) { diff --git a/src/core/input/Keyboard.ts b/src/core/input/Keyboard.ts index cf290275..8c6f3c59 100644 --- a/src/core/input/Keyboard.ts +++ b/src/core/input/Keyboard.ts @@ -348,7 +348,6 @@ export function evaluateKeyboardEvent( } else if (isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { if (ev.keyCode === 65) { // cmd + a result.type = KeyboardResultType.SELECT_ALL; - // TODO: Select all in terminal side } } break; From 91cf9e1ba8817cf025e7d83e4e2098283857a294 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 20 Jun 2018 19:36:08 +1000 Subject: [PATCH 13/13] Move .strings to public/ --- src/Terminal.ts | 6 +----- src/public/Terminal.ts | 6 +++++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index f57d3c11..06f9defc 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -47,7 +47,7 @@ import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { MouseZoneManager } from './input/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; -import { ITheme, ILocalizableStrings, IMarker, IDisposable } from 'xterm'; +import { ITheme, IMarker, IDisposable } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; @@ -328,10 +328,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return this.buffers.active; } - public static get strings(): ILocalizableStrings { - return Strings; - } - /** * back_color_erase feature for xterm. */ diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index f137131b..76e7fba0 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,9 +3,10 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm'; import { ITerminal } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; +import * as Strings from '../Strings'; export class Terminal implements ITerminalApi { private _core: ITerminal; @@ -141,4 +142,7 @@ export class Terminal implements ITerminalApi { public static applyAddon(addon: any): void { addon.apply(Terminal); } + public static get strings(): ILocalizableStrings { + return Strings; + } }