From e2aaa8d362331efdc9939af366dcaaf2ab23ee96 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 Jun 2016 18:01:12 -0700 Subject: [PATCH 1/4] Jump over words with ctrl+left/right Escape codes used: 5=ctrl, C=right, D=left Fixes #65 --- src/xterm.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index b17437cc..d8bdd7ac 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2337,7 +2337,12 @@ this.cancel(ev, true); key = '\x1bb' // Jump a word back break; - } else if (this.applicationCursor) { + } + if (ev.ctrlKey) { + key = '\x1b[5D'; // Jump a word back + break; + } + if (this.applicationCursor) { key = '\x1bOD'; // SS3 as ^[O for 7-bit break; } @@ -2349,7 +2354,12 @@ this.cancel(ev, true); key = '\x1bf' // Jump a word forward break; - } else if (this.applicationCursor) { + } + if (ev.ctrlKey) { + key = '\x1b[5C'; // Jump a word forward + break; + } + if (this.applicationCursor) { key = '\x1bOC'; break; } From 3a866cf202bbf5a797d927547cf27b391db607a9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 Jun 2016 18:41:54 -0700 Subject: [PATCH 2/4] Refactor escape sequence code and add tests --- src/xterm.js | 200 ++++++++++++++++++++++++--------------------------- test/test.js | 19 ++++- 2 files changed, 109 insertions(+), 110 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index d8bdd7ac..f4a1d3d0 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2301,229 +2301,213 @@ // 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); + }; + + 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 + result.key = '\x1bb' // Jump a word back + result.cancel = true; break; } if (ev.ctrlKey) { - key = '\x1b[5D'; // Jump a word back + result.key = '\x1b[5D'; // Jump a word back break; } if (this.applicationCursor) { - key = '\x1bOD'; // SS3 as ^[O for 7-bit + result.key = '\x1bOD'; // SS3 as ^[O for 7-bit break; } - key = '\x1b[D'; + result.key = '\x1b[D'; break; // right-arrow case 39: if (ev.altKey) { - this.cancel(ev, true); - key = '\x1bf' // Jump a word forward + result.key = '\x1bf' // Jump a word forward + result.cancel = true; break; } if (ev.ctrlKey) { - key = '\x1b[5C'; // Jump a word forward + result.key = '\x1b[5C'; // Jump a word forward break; } if (this.applicationCursor) { - key = '\x1bOC'; + result.key = '\x1bOC'; break; } - key = '\x1b[C'; + 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~'; + result.key = '\x1b[3~'; break; // insert case 45: - key = '\x1b[2~'; + result.key = '\x1b[2~'; 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..7297d8af 100644 --- a/test/test.js +++ b/test/test.js @@ -1,6 +1,21 @@ 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 \\x1b[5D when ctrl+left is passed', function() { + var event = { ctrlKey: true, keyCode: 37 }; + assert.equal(xterm.evaluateKeyEscapeSequence(event).key, '\x1b[5D'); + }); + it('should return \\x1b[5C when ctrl+right is passed', function() { + var event = { ctrlKey: true, keyCode: 39 }; + assert.equal(xterm.evaluateKeyEscapeSequence(event).key, '\x1b[5C'); + }); + }); }); From c86fd8781998c59b88cfcceec31fd9a457475248 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 Jun 2016 19:00:40 -0700 Subject: [PATCH 3/4] Add tests for F-keys --- test/test.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/test/test.js b/test/test.js index 7297d8af..12fba1d0 100644 --- a/test/test.js +++ b/test/test.js @@ -10,12 +10,24 @@ describe('xterm.js', function() { describe('evaluateKeyEscapeSequence', function() { it('should return \\x1b[5D when ctrl+left is passed', function() { - var event = { ctrlKey: true, keyCode: 37 }; - assert.equal(xterm.evaluateKeyEscapeSequence(event).key, '\x1b[5D'); + assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[5D'); }); it('should return \\x1b[5C when ctrl+right is passed', function() { - var event = { ctrlKey: true, keyCode: 39 }; - assert.equal(xterm.evaluateKeyEscapeSequence(event).key, '\x1b[5C'); + assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[5C'); + }); + it('should return the correct escape sequence for the F-keys', function() { + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); + assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); }); }); }); From 0535f9425bf96985d3b1e3ce45df965dc6d8cc6b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 Jun 2016 19:25:29 -0700 Subject: [PATCH 4/4] More escape sequence tests --- src/xterm.js | 16 ++++++++------- test/test.js | 58 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index f4a1d3d0..ac06960d 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2326,6 +2326,12 @@ 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 @@ -2422,14 +2428,10 @@ result.key = '\x1b[B'; } break; - // delete - case 46: - result.key = '\x1b[3~'; - break; // insert - case 45: - result.key = '\x1b[2~'; - break; + case 45: result.key = '\x1b[2~'; break; + // delete + case 46: result.key = '\x1b[3~'; break; // home case 36: if (this.applicationKeypad) { diff --git a/test/test.js b/test/test.js index 12fba1d0..e603d65f 100644 --- a/test/test.js +++ b/test/test.js @@ -9,25 +9,49 @@ describe('xterm.js', function() { }); describe('evaluateKeyEscapeSequence', function() { - it('should return \\x1b[5D when ctrl+left is passed', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[5D'); + 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[5C when ctrl+right is passed', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[5C'); + 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 the correct escape sequence for the F-keys', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); + it('should return \\x1b[5C for ctrl+right', function() { + assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[5C'); // CSI 5 C }); }); });