diff --git a/README.md b/README.md
index b9b4c177..85c2ac18 100644
--- a/README.md
+++ b/README.md
@@ -123,6 +123,8 @@ computational environment for Jupyter, supporting interactive data science and s
- [**Azure Cloud Shell**](https://shell.azure.com): Azure Cloud Shell is a Microsoft-managed admin machine built on Azure, for Azure.
- [**atom-xterm**](https://atom.io/packages/atom-xterm): Atom plugin for providing terminals inside your Atom workspace.
- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
+- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS
+- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list.
diff --git a/demo/index.html b/demo/index.html
index e0c355d0..9c95d06c 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 df11a27f..651dbd0f 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'),
@@ -74,6 +75,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));
});
@@ -99,6 +103,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 2b4b7bdd..5a22a185 100644
--- a/src/Interfaces.ts
+++ b/src/Interfaces.ts
@@ -144,6 +144,7 @@ export interface ITerminalOptions {
handler?: (data: string) => void;
letterSpacing?: number;
lineHeight?: number;
+ macOptionIsMeta?: boolean;
rows?: number;
screenKeys?: boolean;
screenReaderMode?: boolean;
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 4afd4166..584565eb 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -83,6 +83,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
screenKeys: false,
screenReaderMode: false,
debug: false,
+ macOptionIsMeta: false,
cancelEvents: false,
disableStdin: false,
useFlowControl: false,
@@ -1434,7 +1435,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;
}
@@ -1455,6 +1456,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.
@@ -1759,8 +1773,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) {
@@ -1826,7 +1840,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;
}
@@ -2218,19 +2232,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 85663734..b9796328 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -467,7 +467,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.