diff --git a/src/xterm.js b/src/xterm.js index 4b3d7acd..a806a03d 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2305,223 +2305,221 @@ // Key Resources: // https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent Terminal.prototype.keyDown = function(ev) { - var self = this, key; + var self = this; + var result = this.evaluateKeyEscapeSequence(ev); + if (result.scrollDisp) { + this.scrollDisp(result.scrollDisp); + return this.cancel(ev); + } + + if (result.cancel) { + // The event is canceled at the end already, is this necessary? + this.cancel(ev, true); + } + + if (!result.key || (this.isMac && ev.metaKey)) { + return true; + } + + this.emit('keydown', ev); + this.emit('key', result.key, ev); + this.showCursor(); + this.handler(result.key); + + return this.cancel(ev, true); + }; + + /** + * 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 + */ + Terminal.prototype.evaluateKeyEscapeSequence = function(ev) { + var result = { + // 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 + scrollDisp: undefined + }; switch (ev.keyCode) { // backspace case 8: if (ev.shiftKey) { - key = '\x08'; // ^H + result.key = '\x08'; // ^H break; } - key = '\x7f'; // ^? + result.key = '\x7f'; // ^? break; // tab case 9: if (ev.shiftKey) { - key = '\x1b[Z'; + result.key = '\x1b[Z'; break; } - key = '\t'; - this.cancel(ev, true); + result.key = '\t'; + result.cancel = true; break; // return/enter case 13: - key = '\r'; - this.cancel(ev, true); + result.key = '\r'; + result.cancel = true; break; // escape case 27: - key = '\x1b'; - this.cancel(ev, true); + result.key = '\x1b'; + result.cancel = true; break; // left-arrow case 37: if (ev.altKey) { - this.cancel(ev, true); - key = '\x1bb' // Jump a word back - break; - } else if (this.applicationCursor) { - key = '\x1bOD'; // SS3 as ^[O for 7-bit + result.key = '\x1bb' // Jump a word back + result.cancel = true; break; } - key = '\x1b[D'; + if (ev.ctrlKey) { + result.key = '\x1b[5D'; // Jump a word back + break; + } + if (this.applicationCursor) { + result.key = '\x1bOD'; // SS3 as ^[O for 7-bit + break; + } + result.key = '\x1b[D'; break; // right-arrow case 39: if (ev.altKey) { - this.cancel(ev, true); - key = '\x1bf' // Jump a word forward - break; - } else if (this.applicationCursor) { - key = '\x1bOC'; + result.key = '\x1bf' // Jump a word forward + result.cancel = true; break; } - key = '\x1b[C'; + if (ev.ctrlKey) { + result.key = '\x1b[5C'; // Jump a word forward + break; + } + if (this.applicationCursor) { + result.key = '\x1bOC'; + break; + } + result.key = '\x1b[C'; break; // up-arrow case 38: if (this.applicationCursor) { - key = '\x1bOA'; + result.key = '\x1bOA'; break; } if (ev.ctrlKey) { - this.scrollDisp(-1); - return this.cancel(ev); + result.scrollDisp = -1; } else { - key = '\x1b[A'; + result.key = '\x1b[A'; } break; // down-arrow case 40: if (this.applicationCursor) { - key = '\x1bOB'; + result.key = '\x1bOB'; break; } if (ev.ctrlKey) { - this.scrollDisp(1); - return this.cancel(ev); + result.scrollDisp = 1; } else { - key = '\x1b[B'; + result.key = '\x1b[B'; } break; - // delete - case 46: - key = '\x1b[3~'; - break; // insert case 45: if (!ev.shiftKey && !ev.ctrlKey) { // or + are used to // copy-paste on some systems. - key = '\x1b[2~'; + result.key = '\x1b[2~'; } break; + // delete + case 46: result.key = '\x1b[3~'; break; // home case 36: if (this.applicationKeypad) { - key = '\x1bOH'; + result.key = '\x1bOH'; break; } - key = '\x1bOH'; + result.key = '\x1bOH'; break; // end case 35: if (this.applicationKeypad) { - key = '\x1bOF'; + result.key = '\x1bOF'; break; } - key = '\x1bOF'; + result.key = '\x1bOF'; break; // page up case 33: if (ev.shiftKey) { - this.scrollDisp(-(this.rows - 1)); - return this.cancel(ev); + result.scrollDisp = -(this.rows - 1); } else { - key = '\x1b[5~'; + result.key = '\x1b[5~'; } break; // page down case 34: if (ev.shiftKey) { - this.scrollDisp(this.rows - 1); - return this.cancel(ev); + result.scrollDisp = this.rows - 1; } else { - key = '\x1b[6~'; + result.key = '\x1b[6~'; } break; - // F1 - case 112: - key = '\x1bOP'; - break; - // F2 - case 113: - key = '\x1bOQ'; - break; - // F3 - case 114: - key = '\x1bOR'; - break; - // F4 - case 115: - key = '\x1bOS'; - break; - // F5 - case 116: - key = '\x1b[15~'; - break; - // F6 - case 117: - key = '\x1b[17~'; - break; - // F7 - case 118: - key = '\x1b[18~'; - break; - // F8 - case 119: - key = '\x1b[19~'; - break; - // F9 - case 120: - key = '\x1b[20~'; - break; - // F10 - case 121: - key = '\x1b[21~'; - break; - // F11 - case 122: - key = '\x1b[23~'; - break; - // F12 - case 123: - key = '\x1b[24~'; - break; + // F1-F12 + case 112: result.key = '\x1bOP'; break; + case 113: result.key = '\x1bOQ'; break; + case 114: result.key = '\x1bOR'; break; + case 115: result.key = '\x1bOS'; break; + case 116: result.key = '\x1b[15~'; break; + case 117: result.key = '\x1b[17~'; break; + case 118: result.key = '\x1b[18~'; break; + case 119: result.key = '\x1b[19~'; break; + case 120: result.key = '\x1b[20~'; break; + case 121: result.key = '\x1b[21~'; break; + case 122: result.key = '\x1b[23~'; break; + case 123: result.key = '\x1b[24~'; break; default: // a-z and space if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { if (ev.keyCode >= 65 && ev.keyCode <= 90) { - key = String.fromCharCode(ev.keyCode - 64); + result.key = String.fromCharCode(ev.keyCode - 64); } else if (ev.keyCode === 32) { // NUL - key = String.fromCharCode(0); + result.key = String.fromCharCode(0); } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { // escape, file sep, group sep, record sep, unit sep - key = String.fromCharCode(ev.keyCode - 51 + 27); + result.key = String.fromCharCode(ev.keyCode - 51 + 27); } else if (ev.keyCode === 56) { // delete - key = String.fromCharCode(127); + result.key = String.fromCharCode(127); } else if (ev.keyCode === 219) { // ^[ - escape - key = String.fromCharCode(27); + result.key = String.fromCharCode(27); } else if (ev.keyCode === 221) { // ^] - group sep - key = String.fromCharCode(29); + result.key = String.fromCharCode(29); } } else if ((!this.isMac && ev.altKey) || (this.isMac && ev.metaKey)) { if (ev.keyCode >= 65 && ev.keyCode <= 90) { - key = '\x1b' + String.fromCharCode(ev.keyCode + 32); + result.key = '\x1b' + String.fromCharCode(ev.keyCode + 32); } else if (ev.keyCode === 192) { - key = '\x1b`'; + result.key = '\x1b`'; } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { - key = '\x1b' + (ev.keyCode - 48); + result.key = '\x1b' + (ev.keyCode - 48); } } break; } - - if (!key || (this.isMac && ev.metaKey)) { - return true; - } - - this.emit('keydown', ev); - this.emit('key', key, ev); - this.showCursor(); - this.handler(key); - - return this.cancel(ev, true); + return result; }; Terminal.prototype.setgLevel = function(g) { diff --git a/test/test.js b/test/test.js index d746eb28..e603d65f 100644 --- a/test/test.js +++ b/test/test.js @@ -1,6 +1,57 @@ var assert = require('chai').assert; +var Terminal = require('../src/xterm'); +describe('xterm.js', function() { + var xterm; -describe('xterm', function() { - // Just a dummy first test + beforeEach(function () { + xterm = new Terminal(); + }); + + describe('evaluateKeyEscapeSequence', function() { + it('should return the correct escape sequence for unmodified keys', function() { + // Backspace + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 8 }).key, '\x7f'); // ^? + // Tab + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 9 }).key, '\t'); + // Return/enter + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 13 }).key, '\r'); // CR + // Escape + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 27 }).key, '\x1b'); + // Page up, page down + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~ + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~ + // End, Home + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1bOF'); // SS3 F + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1bOH'); // SS3 H + // Left, up, right, down arrows + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 37 }).key, '\x1b[D'); // CSI D + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 38 }).key, '\x1b[A'); // CSI A + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 39 }).key, '\x1b[C'); // CSI C + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 40 }).key, '\x1b[B'); // CSI B + // Insert + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~ + // Delete + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~ + // F1-F12 + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); // SS3 P + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); // SS3 R + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); // SS3 S + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~ + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~ + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~ + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~ + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~ + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~ + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~ + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~ + }); + it('should return \\x1b[5D for ctrl+left', function() { + assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[5D'); // CSI 5 D + }); + it('should return \\x1b[5C for ctrl+right', function() { + assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[5C'); // CSI 5 C + }); + }); });