diff --git a/demo/index.html b/demo/index.html index 93399f9c..da348f5e 100644 --- a/demo/index.html +++ b/demo/index.html @@ -23,6 +23,9 @@
cursorBlink
+ macOptionIsMeta +
cursorStyle diff --git a/demo/main.js b/demo/main.js index 95b15a0d..1faf77d8 100644 --- a/demo/main.js +++ b/demo/main.js @@ -27,6 +27,7 @@ var terminalContainer = document.getElementById('terminal-container'), optionElements = { cursorBlink: document.querySelector('#option-cursor-blink'), cursorStyle: document.querySelector('#option-cursor-style'), + macOptionIsMeta: document.querySelector('#option-mac-option-is-meta'), scrollback: document.querySelector('#option-scrollback'), tabstopwidth: document.querySelector('#option-tabstopwidth'), bellStyle: document.querySelector('#option-bell-style') @@ -72,6 +73,9 @@ optionElements.cursorStyle.addEventListener('change', function () { optionElements.bellStyle.addEventListener('change', function () { term.setOption('bellStyle', optionElements.bellStyle.value); }); +optionElements.macOptionIsMeta.addEventListener('change', function () { + term.setOption('macOptionIsMeta', optionElements.macOptionIsMeta.checked); +}); optionElements.scrollback.addEventListener('change', function () { term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10)); }); @@ -87,6 +91,7 @@ function createTerminal() { terminalContainer.removeChild(terminalContainer.children[0]); } term = new Terminal({ + macOptionIsMeta: optionElements.macOptionIsMeta.enabled, cursorBlink: optionElements.cursorBlink.checked, scrollback: parseInt(optionElements.scrollback.value, 10), tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index 5a5f6c50..0a88bd37 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -144,6 +144,7 @@ namespace methods_core { const r20: string = t.getOption('bellStyle'); const r21: boolean = t.getOption('enableBold'); const r22: number = t.getOption('letterSpacing'); + const r23: boolean = t.getOption('macOptionIsMeta'); } { const t: Terminal = new Terminal(); @@ -177,6 +178,7 @@ namespace methods_core { t.setOption('lineHeight', 1); t.setOption('fontFamily', 'foo'); t.setOption('theme', {background: '#ff0000'}); + t.setOption('macOptionIsMeta', true); } } namespace scrolling { diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 780a4047..70b8509f 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -143,6 +143,7 @@ export interface ITerminalOptions { handler?: (data: string) => void; letterSpacing?: number; lineHeight?: number; + macOptionIsMeta?: boolean; rows?: number; screenKeys?: boolean; scrollback?: number; diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 9cb0b75c..946e9efb 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -503,6 +503,9 @@ describe('term.js addons', () => { 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', () => { @@ -515,6 +518,19 @@ describe('term.js addons', () => { 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', () => { @@ -597,6 +613,22 @@ describe('term.js addons', () => { }; }); + describe('with macOptionIsMeta', () => { + beforeEach(() => { + term.browser.isMac = true; + term.setOption('macOptionIsMeta', true); + }); + + it('should interfere with the alt key on keyDown', () => { + evKeyDown.altKey = true; + evKeyDown.keyCode = 81; + assert.equal(term.keyDown(evKeyDown), false); + evKeyDown.altKey = true; + evKeyDown.keyCode = 192; + assert.equal(term.keyDown(evKeyDown), false); + }); + }); + describe('On Mac OS', () => { beforeEach(() => { term.browser.isMac = true; diff --git a/src/Terminal.ts b/src/Terminal.ts index 31c9a250..b56caf83 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -80,6 +80,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { scrollback: 1000, screenKeys: false, debug: false, + macOptionIsMeta: false, cancelEvents: false, disableStdin: false, useFlowControl: false, @@ -1374,7 +1375,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return this.cancel(ev, true); } - if (isThirdLevelShift(this.browser, ev)) { + if (this._isThirdLevelShift(this.browser, ev)) { return true; } @@ -1395,6 +1396,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return this.cancel(ev, true); } + private _isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean { + const thirdLevelKey = + (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) || + (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); + + if (ev.type === 'keypress') { + return thirdLevelKey; + } + + // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events) + 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. @@ -1699,8 +1713,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // ^] - Operating System Command (OSC) result.key = String.fromCharCode(29); } - } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) { - // On Mac this is a third level shift. Use instead. + } else if ((!this.browser.isMac || this.options.macOptionIsMeta) && ev.altKey && !ev.ctrlKey && !ev.metaKey) { + // On macOS this is a third level shift when !macOptionIsMeta. Use instead. if (ev.keyCode >= 65 && ev.keyCode <= 90) { result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32); } else if (ev.keyCode === 192) { @@ -1766,7 +1780,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } if (!key || ( - (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this.browser, ev) + (ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev) )) { return false; } @@ -2158,19 +2172,6 @@ function off(el: any, type: string, handler: (event: Event) => any, capture: boo el.removeEventListener(type, handler, capture); } -function isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean { - const thirdLevelKey = - (browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || - (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); - - if (ev.type === 'keypress') { - return thirdLevelKey; - } - - // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events) - return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); -} - function wasMondifierKeyOnlyEvent(ev: KeyboardEvent): boolean { return ev.keyCode === 16 || // Shift ev.keyCode === 17 || // Ctrl diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 83199e08..43842bad 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -454,7 +454,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; + setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key.