From 6b905127980f904efeddd70b43539f32240b4a63 Mon Sep 17 00:00:00 2001 From: Exile Date: Sat, 7 Sep 2013 10:42:47 +0300 Subject: [PATCH 1/7] Locali(s)(z)ed keyboard support for 3rd level characters This patch includes support-beginnings for locali(s)(z)ation needed by countries where keyboard includes third-level characters. Characters like @|{[]}\ are currently often not usable because they are mapped as 3rd level characters on different international keyboard layouts. - kbLayout option introduced, which can be given during Terminal.call and changed even after, defaults to 'us'; - Placeholders for the following layouts: -> French (fr); -> German (de); -> UK (uk); -> South Slavic Latin countries - Croatia (hr),Slovenia (si), Bosnia and Herzegovina (ba), Serbia (rs); -> Belgium (be); -> Polish (pl); -> Turkish (tr); - Placeholder keyboard layouts currently output "Not Implemented" to browser console; - Estonian keyboard layout implementation for Windows (AltGr) and Mac (Alt); - Should not break anything that was working before... --- src/term.js | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/src/term.js b/src/term.js index 68bc073..698b763 100644 --- a/src/term.js +++ b/src/term.js @@ -444,7 +444,8 @@ Terminal.defaults = { scrollback: 1000, screenKeys: false, debug: false, - useStyle: false + useStyle: false, + kbLayout: 'us', // programFeatures: false, // focusKeys: false, }; @@ -2810,7 +2811,84 @@ Terminal.prototype.keyDown = function(ev) { } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { key = '\x1b' + (ev.keyCode - 48); } - } + } if (this['kbLayout'] != 'us' && ((this.isMac && ev.altKey) || (!this.isMac && ev.altKey && ev.ctrlKey))) { + // This is to support keyboards with third-level characters + if (this['kbLayout'] == 'ee') { + // Estonian + if(!this.isMac) { + if (ev.keyCode === 50) { + // AltGr-2 => @ + key = String.fromCharCode(64); + } else if (ev.keyCode === 51) { + // AltGr-3 => £ + key = String.fromCharCode(163); + } else if (ev.keyCode === 52) { + // AltGr-4 => $ + key = String.fromCharCode(36); + } else if (ev.keyCode === 69) { + // AltGr-e => € + key = String.fromCharCode(8364); + } else if (ev.keyCode === 55) { + // AltGr-7 => { + key = String.fromCharCode(123); + } else if (ev.keyCode === 56) { + // AltGr-8 => [ + key = String.fromCharCode(91); + } else if (ev.keyCode === 57) { + // AltGr-9 => ] + key = String.fromCharCode(93); + } else if (ev.keyCode === 48) { + // AltGr-0 => } + key = String.fromCharCode(125); + } else if (ev.keyCode === 189) { + // AltGr-+ => \ + key = String.fromCharCode(92); + } else if (ev.keyCode === 226) { + // AltGr-< => | + key = String.fromCharCode(124); + } + } else if (this.isMac) { + if (ev.keyCode === 50) { + // Alt-2 => @ + key = String.fromCharCode(64); + } else if (ev.keyCode === 51) { + // Alt-3 => £ + key = String.fromCharCode(163); + } else if (ev.keyCode === 52) { + // Alt-4 => $ + key = String.fromCharCode(36); + } else if (ev.keyCode === 219) { + if (ev.keyIdentifier === 'U+0037') { + // Alt-7 => { + key = String.fromCharCode(123); + } else if (ev.keyIdentifier === 'U+0038') { + // Alt-8 => [ + key = String.fromCharCode(91); + } + } else if (ev.keyCode === 221) { + if (ev.keyIdentifier === 'U+0039') { + // Alt-9 => ] + key = String.fromCharCode(93); + } else if (ev.keyIdentifier === 'U+0030') { + // Alt-0 => } + key = String.fromCharCode(125); + } + } else if (ev.keyCode === 220) { + if (ev.keyIdentifier === 'U+002B') { + // Alt-+ => \ + key = String.fromCharCode(92); + } else if (ev.keyIdentifier === 'U+003C') { + // Alt-< => | + key = String.fromCharCode(124); + } + } + } + } else if (['fr','de','uk','hr','si','ba','rs','be','pl','tr'].indexOf(this['kbLayout'] > -1)) { + console.log('Not implemented'); + } else { + console.log('Unknown keyboard layout: %s', this['kbLayout']); + } + } break; } From f6161fa508dd9696a8847223fa140addf8eb03de Mon Sep 17 00:00:00 2001 From: Exile Date: Sat, 24 Sep 2016 13:50:33 +0300 Subject: [PATCH 2/7] Revert "Locali(s)(z)ed keyboard support for 3rd level characters" This reverts commit 6b905127980f904efeddd70b43539f32240b4a63. --- src/term.js | 82 ++--------------------------------------------------- 1 file changed, 2 insertions(+), 80 deletions(-) diff --git a/src/term.js b/src/term.js index 698b763..68bc073 100644 --- a/src/term.js +++ b/src/term.js @@ -444,8 +444,7 @@ Terminal.defaults = { scrollback: 1000, screenKeys: false, debug: false, - useStyle: false, - kbLayout: 'us', + useStyle: false // programFeatures: false, // focusKeys: false, }; @@ -2811,84 +2810,7 @@ Terminal.prototype.keyDown = function(ev) { } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { key = '\x1b' + (ev.keyCode - 48); } - } if (this['kbLayout'] != 'us' && ((this.isMac && ev.altKey) || (!this.isMac && ev.altKey && ev.ctrlKey))) { - // This is to support keyboards with third-level characters - if (this['kbLayout'] == 'ee') { - // Estonian - if(!this.isMac) { - if (ev.keyCode === 50) { - // AltGr-2 => @ - key = String.fromCharCode(64); - } else if (ev.keyCode === 51) { - // AltGr-3 => £ - key = String.fromCharCode(163); - } else if (ev.keyCode === 52) { - // AltGr-4 => $ - key = String.fromCharCode(36); - } else if (ev.keyCode === 69) { - // AltGr-e => € - key = String.fromCharCode(8364); - } else if (ev.keyCode === 55) { - // AltGr-7 => { - key = String.fromCharCode(123); - } else if (ev.keyCode === 56) { - // AltGr-8 => [ - key = String.fromCharCode(91); - } else if (ev.keyCode === 57) { - // AltGr-9 => ] - key = String.fromCharCode(93); - } else if (ev.keyCode === 48) { - // AltGr-0 => } - key = String.fromCharCode(125); - } else if (ev.keyCode === 189) { - // AltGr-+ => \ - key = String.fromCharCode(92); - } else if (ev.keyCode === 226) { - // AltGr-< => | - key = String.fromCharCode(124); - } - } else if (this.isMac) { - if (ev.keyCode === 50) { - // Alt-2 => @ - key = String.fromCharCode(64); - } else if (ev.keyCode === 51) { - // Alt-3 => £ - key = String.fromCharCode(163); - } else if (ev.keyCode === 52) { - // Alt-4 => $ - key = String.fromCharCode(36); - } else if (ev.keyCode === 219) { - if (ev.keyIdentifier === 'U+0037') { - // Alt-7 => { - key = String.fromCharCode(123); - } else if (ev.keyIdentifier === 'U+0038') { - // Alt-8 => [ - key = String.fromCharCode(91); - } - } else if (ev.keyCode === 221) { - if (ev.keyIdentifier === 'U+0039') { - // Alt-9 => ] - key = String.fromCharCode(93); - } else if (ev.keyIdentifier === 'U+0030') { - // Alt-0 => } - key = String.fromCharCode(125); - } - } else if (ev.keyCode === 220) { - if (ev.keyIdentifier === 'U+002B') { - // Alt-+ => \ - key = String.fromCharCode(92); - } else if (ev.keyIdentifier === 'U+003C') { - // Alt-< => | - key = String.fromCharCode(124); - } - } - } - } else if (['fr','de','uk','hr','si','ba','rs','be','pl','tr'].indexOf(this['kbLayout'] > -1)) { - console.log('Not implemented'); - } else { - console.log('Unknown keyboard layout: %s', this['kbLayout']); - } - } + } break; } From 91d319d013f6e250cd1bdac173737a6737011942 Mon Sep 17 00:00:00 2001 From: Exile Date: Sun, 25 Sep 2016 00:17:14 +0300 Subject: [PATCH 3/7] Compatibility and important fixes - 3rd level characters now work; - Special character input via composition events work; - Different set of fixes and improvements from xterm.js --- README.md | 82 +---- src/term.js | 953 +++++++++++++++++++++++++++++++++------------------- 2 files changed, 607 insertions(+), 428 deletions(-) diff --git a/README.md b/README.md index 4b6fbc7..f05b267 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,15 @@ # term.js -A full xterm clone written in javascript. Used by -[**tty.js**](https://github.com/chjj/tty.js). +A full xterm clone written in javascript. Used by [**Cloud 9v2**](https://github.com/exsilium/cloud9). -**⚠️ This project is no longer maintained ⚠️. For a maintained fork take a look at [sourcelair/xterm.js](https://github.com/sourcelair/xterm.js).** +Maintenance fork to keep compatibility with existing implementation. Backporting crucial fixes from [sourcelair/xterm.js](https://github.com/sourcelair/xterm.js) -## Example - -Server: - -``` js -var term = require('term.js'); -app.use(term.middleware()); -... -``` - -Client: - -``` js -window.addEventListener('load', function() { - var socket = io.connect(); - socket.on('connect', function() { - var term = new Terminal({ - cols: 80, - rows: 24, - screenKeys: true - }); - - term.on('data', function(data) { - socket.emit('data', data); - }); - - term.on('title', function(title) { - document.title = title; - }); - - term.open(document.body); - - term.write('\x1b[31mWelcome to term.js!\x1b[m\r\n'); - - socket.on('data', function(data) { - term.write(data); - }); - - socket.on('disconnect', function() { - term.destroy(); - }); - }); -}, false); -``` - -## Tmux-like - -While term.js has always supported copy/paste using the mouse, it now also -supports several keyboard based solutions for copy/paste. - -term.js includes a tmux-like selection mode (enabled with the `screenKeys` -option) which makes copy and paste very simple. `Ctrl-A` enters `prefix` mode, -from here you can type `Ctrl-V` to paste. Press `[` in prefix mode to enter -selection mode. To select text press `v` (or `space`) to enter visual mode, use -`hjkl` to navigate and create a selection, and press `Ctrl-C` to copy. - -`Ctrl-C` (in visual mode) and `Ctrl-V` (in prefix mode) should work in any OS -for copy and paste. `y` (in visual mode) will work for copying only on X11 -systems. It will copy to the primary selection. - -Note: `Ctrl-C` will also work in prefix mode for the regular OS/browser -selection. If you want to select text with your mouse and copy it to the -clipboard, simply select the text and type `Ctrl-A + Ctrl-C`, and -`Ctrl-A + Ctrl-V` to paste it. - -For mac users: consider `Ctrl` to be `Command/Apple` above. - -## Contribution and License Agreement - -If you contribute code to this project, you are implicitly allowing your code -to be distributed under the MIT license. You are also implicitly verifying that -all code is your original work. `` +* Fix cross platform input problems [sourcelair/xterm.js#60](https://github.com/sourcelair/xterm.js/pull/60); +* Textarea used for DOM event capturing (`compositionstart`, `-update`, `-end`); +* Enhancements made in `evaluateKeyEscapeSequence`; ## License +Copyright (c) 2014-2016, Sten Feldman (MIT License) +Copyright (c) 2014-2016, SourceLair, Private Company (www.sourcelair.com) (MIT License) Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) diff --git a/src/term.js b/src/term.js index 68bc073..8b50fcd 100644 --- a/src/term.js +++ b/src/term.js @@ -186,6 +186,8 @@ function Terminal(options) { return new Terminal(arguments[0], arguments[1], arguments[2]); } + self.cancel = Terminal.cancel; + Stream.call(this); if (typeof options === 'number') { @@ -520,15 +522,23 @@ Terminal.prototype.initGlobal = function() { Terminal.bindCopy(document); - if (this.isMobile) { - this.fixMobile(document); - } - if (this.useStyle) { Terminal.insertStyle(document, this.colors[256], this.colors[257]); } }; +/** + * Clears all selected text, inside the terminal. + */ + +Terminal.prototype.clearSelection = function () { + var selectionBaseNode = window.getSelection().baseNode; + + if (selectionBaseNode && (this.element.contains(selectionBaseNode.parentElement))) { + window.getSelection().removeAllRanges(); + } +}; + /** * Bind to paste event */ @@ -586,6 +596,48 @@ Terminal.bindKeys = function(document) { } }, true); + on(document, 'compositionstart', function(ev) { + if (!Terminal.focus) return; + var target = ev.target || ev.srcElement; + if (!target) return; + if (target === Terminal.focus.element + || target === Terminal.focus.context + || target === Terminal.focus.document + || target === Terminal.focus.body + || target === Terminal._textarea + || target === Terminal.focus.parent) { + return Terminal.focus.compositionStart(ev); + } + }, true); + + on(document, 'compositionupdate', function(ev) { + if (!Terminal.focus) return; + var target = ev.target || ev.srcElement; + if (!target) return; + if (target === Terminal.focus.element + || target === Terminal.focus.context + || target === Terminal.focus.document + || target === Terminal.focus.body + || target === Terminal._textarea + || target === Terminal.focus.parent) { + return Terminal.focus.compositionUpdate(ev); + } + }, true); + + on(document, 'compositionend', function(ev) { + if (!Terminal.focus) return; + var target = ev.target || ev.srcElement; + if (!target) return; + if (target === Terminal.focus.element + || target === Terminal.focus.context + || target === Terminal.focus.document + || target === Terminal.focus.body + || target === Terminal._textarea + || target === Terminal.focus.parent) { + return Terminal.focus.compositionEnd(ev); + } + }, true); + // If we click somewhere other than a // terminal, unfocus the terminal. on(document, 'mousedown', function(ev) { @@ -647,44 +699,6 @@ Terminal.bindCopy = function(document) { }); }; -/** - * Fix Mobile - */ - -Terminal.prototype.fixMobile = function(document) { - var self = this; - - var textarea = document.createElement('textarea'); - textarea.style.position = 'absolute'; - textarea.style.left = '-32000px'; - textarea.style.top = '-32000px'; - textarea.style.width = '0px'; - textarea.style.height = '0px'; - textarea.style.opacity = '0'; - textarea.style.backgroundColor = 'transparent'; - textarea.style.borderStyle = 'none'; - textarea.style.outlineStyle = 'none'; - textarea.autocapitalize = 'none'; - textarea.autocorrect = 'off'; - - document.getElementsByTagName('body')[0].appendChild(textarea); - - Terminal._textarea = textarea; - - setTimeout(function() { - textarea.focus(); - }, 1000); - - if (this.isAndroid) { - on(textarea, 'change', function() { - var value = textarea.textContent || textarea.value; - textarea.value = ''; - textarea.textContent = ''; - self.send(value + '\r'); - }); - } -}; - /** * Insert a default style */ @@ -753,14 +767,30 @@ Terminal.prototype.open = function(parent) { // Parse user-agent strings. if (this.context.navigator && this.context.navigator.userAgent) { - this.isMac = !!~this.context.navigator.userAgent.indexOf('Mac'); - this.isIpad = !!~this.context.navigator.userAgent.indexOf('iPad'); - this.isIphone = !!~this.context.navigator.userAgent.indexOf('iPhone'); this.isAndroid = !!~this.context.navigator.userAgent.indexOf('Android'); - this.isMobile = this.isIpad || this.isIphone || this.isAndroid; this.isMSIE = !!~this.context.navigator.userAgent.indexOf('MSIE'); } + /* + * Find the users platform. We use this to interpret the meta key + * and ISO third level shifts. + * http://stackoverflow.com/questions/19877924/what-is-the-list-of-possible-values-for-navigator-platform-as-of-today + */ + if (this.context.navigator && this.context.navigator.platform) { + this.isMac = contains( + this.context.navigator.platform, + ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'] + ); + this.isIpad = this.context.navigator.platform === 'iPad'; + this.isIphone = this.context.navigator.platform === 'iPhone'; + this.isMSWindows = contains( + this.context.navigator.platform, + ['Windows', 'Win16', 'Win32', 'WinCE'] + ); + } + + this.isMobile = this.isIpad || this.isIphone || this.isAndroid; + // Create our main terminal element. this.element = this.document.createElement('div'); this.element.className = 'terminal'; @@ -779,6 +809,36 @@ Terminal.prototype.open = function(parent) { } this.parent.appendChild(this.element); + var textarea = document.createElement('textarea'); + textarea.style.position = 'absolute'; + textarea.style.left = '-32000px'; + textarea.style.top = '-32000px'; + textarea.style.width = '0px'; + textarea.style.height = '0px'; + textarea.style.opacity = '0'; + textarea.style.backgroundColor = 'transparent'; + textarea.style.borderStyle = 'none'; + textarea.style.outlineStyle = 'none'; + textarea.autocapitalize = 'none'; + textarea.autocorrect = 'off'; + + document.getElementsByTagName('body')[0].appendChild(textarea); + + Terminal._textarea = textarea; + + setTimeout(function() { + textarea.focus(); + }, 1000); + + if (this.isAndroid) { + on(textarea, 'change', function() { + var value = textarea.textContent || textarea.value; + textarea.value = ''; + textarea.textContent = ''; + self.send(value + '\r'); + }); + } + // Draw the screen. this.refresh(0, this.rows - 1); @@ -1499,34 +1559,53 @@ Terminal.prototype.scrollDisp = function(disp) { this.refresh(0, this.rows - 1); }; -Terminal.prototype.write = function(data) { - var l = data.length - , i = 0 - , j - , cs - , ch; +/** + * Writes text to the terminal. + * @param {string} text The text to write to the terminal. + */ +Terminal.prototype.write = function (data) { + var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; this.refreshStart = this.y; this.refreshEnd = this.y; if (this.ybase !== this.ydisp) { this.ydisp = this.ybase; + this.emit('scroll', this.ydisp); this.maxRange(); } - // this.log(JSON.stringify(data.replace(/\x1b/g, '^['))); + // apply leftover surrogate high from last write + if (this.surrogate_high) { + data = this.surrogate_high + data; + this.surrogate_high = ''; + } - for (; i < l; i++, this.lch = ch) { + for (; i < l; i++) { ch = data[i]; + + // FIXME: higher chars than 0xa0 are not allowed in escape sequences + // --> maybe move to default + code = data.charCodeAt(i); + if (0xD800 <= code && code <= 0xDBFF) { + // we got a surrogate high + // get surrogate low (next 2 bytes) + low = data.charCodeAt(i + 1); + if (isNaN(low)) { + // end of data stream, save surrogate high + this.surrogate_high = ch; + continue; + } + code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; + ch += data.charAt(i + 1); + } + // surrogate low - already handled above + if (0xDC00 <= code && code <= 0xDFFF) + continue; + switch (this.state) { case normal: switch (ch) { - // '\0' - // case '\0': - // case '\200': - // break; - - // '\a' case '\x07': this.bell(); break; @@ -1538,9 +1617,6 @@ Terminal.prototype.write = function(data) { if (this.convertEol) { this.x = 0; } - // TODO: Implement eat_newline_glitch. - // if (this.realX >= this.cols) break; - // this.realX = 0; this.y++; if (this.y > this.scrollBottom) { this.y--; @@ -1582,31 +1658,80 @@ Terminal.prototype.write = function(data) { default: // ' ' + // calculate print space + // expensive call, therefore we save width in line buffer + ch_width = wcwidth(code); + if (ch >= ' ') { if (this.charset && this.charset[ch]) { ch = this.charset[ch]; } - if (this.x >= this.cols) { - this.x = 0; - this.y++; - if (this.y > this.scrollBottom) { - this.y--; - this.scroll(); + row = this.y + this.ybase; + + // insert combining char in last cell + // FIXME: needs handling after cursor jumps + if (!ch_width && this.x) { + + // dont overflow left + if (this.lines[row][this.x - 1]) { + if (!this.lines[row][this.x - 1][2]) { + + // found empty cell after fullwidth, need to go 2 cells back + if (this.lines[row][this.x - 2]) + this.lines[row][this.x - 2][1] += ch; + + } else { + this.lines[row][this.x - 1][1] += ch; + } + this.updateRange(this.y); + } + break; + } + + // goto next line if ch would overflow + // TODO: needs a global min terminal width of 2 + if (this.x + ch_width - 1 >= this.cols) { + // autowrap - DECAWM + if (this.wraparoundMode) { + this.x = 0; + this.y++; + if (this.y > this.scrollBottom) { + this.y--; + this.scroll(); + } + } else { + this.x = this.cols - 1; + if (ch_width === 2) // FIXME: check for xterm behavior + continue; + } + } + row = this.y + this.ybase; + + // insert mode: move characters to right + if (this.insertMode) { + // do this twice for a fullwidth char + for (var moves = 0; moves < ch_width; ++moves) { + // remove last cell, if it's width is 0 + // we have to adjust the second last cell as well + var removed = this.lines[this.y + this.ybase].pop(); + if (removed[2] === 0 + && this.lines[row][this.cols - 2] + && this.lines[row][this.cols - 2][2] === 2) + this.lines[row][this.cols - 2] = [this.curAttr, ' ', 1]; + + // insert empty cell at cursor + this.lines[row].splice(this.x, 0, [this.curAttr, ' ', 1]); } } - this.lines[this.y + this.ybase][this.x] = [this.curAttr, ch]; + this.lines[row][this.x] = [this.curAttr, ch, ch_width]; this.x++; this.updateRange(this.y); - if (isWide(ch)) { - j = this.y + this.ybase; - if (this.cols < 2 || this.x >= this.cols) { - this.lines[j][this.x - 1] = [this.curAttr, ' ']; - break; - } - this.lines[j][this.x] = [this.curAttr, ' ']; + // fullwidth char - set next cell width to zero and advance cursor + if (ch_width === 2) { + this.lines[row][this.x] = [this.curAttr, '', 0]; this.x++; } } @@ -1632,8 +1757,7 @@ Terminal.prototype.write = function(data) { // ESC P Device Control String ( DCS is 0x90). case 'P': this.params = []; - this.prefix = ''; - this.currentParam = ''; + this.currentParam = 0; this.state = dcs; break; @@ -1774,14 +1898,14 @@ Terminal.prototype.write = function(data) { this.tabSet(); break; - // ESC = Application Keypad (DECPAM). + // ESC = Application Keypad (DECKPAM). case '=': this.log('Serial port requested application keypad.'); this.applicationKeypad = true; this.state = normal; break; - // ESC > Normal Keypad (DECPNM). + // ESC > Normal Keypad (DECKPNM). case '>': this.log('Switching back to normal keypad.'); this.applicationKeypad = false; @@ -1856,14 +1980,8 @@ Terminal.prototype.write = function(data) { // OSC Ps ; Pt ST // OSC Ps ; Pt BEL // Set Text Parameters. - if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') { - if (this.lch === '\x1b') { - if (typeof this.currentParam === 'string') { - this.currentParam = this.currentParam.slice(0, -1); - } else if (typeof this.currentParam == 'number') { - this.currentParam = (this.currentParam - ('\x1b'.charCodeAt(0) - 48)) / 10; - } - } + if (ch === '\x1b' || ch === '\x07') { + if (ch === '\x1b') i++; this.params.push(this.currentParam); @@ -2395,158 +2513,94 @@ Terminal.prototype.write = function(data) { break; case dcs: - if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') { - // Workarounds: - if (this.prefix === 'tmux;\x1b') { - // `DCS tmux; Pt ST` may contain a Pt with an ST - // XXX Does tmux work this way? - // if (this.lch === '\x1b' & data[i + 1] === '\x1b' && data[i + 2] === '\\') { - // this.currentParam += ch; - // continue; - // } - // Tmux only accepts ST, not BEL: - if (ch === '\x07') { - this.currentParam += ch; - continue; - } - } - - if (this.lch === '\x1b') { - if (typeof this.currentParam === 'string') { - this.currentParam = this.currentParam.slice(0, -1); - } else if (typeof this.currentParam == 'number') { - this.currentParam = (this.currentParam - ('\x1b'.charCodeAt(0) - 48)) / 10; - } - } - - this.params.push(this.currentParam); - - var pt = this.params[this.params.length - 1]; + if (ch === '\x1b' || ch === '\x07') { + if (ch === '\x1b') i++; switch (this.prefix) { // User-Defined Keys (DECUDK). - // DCS Ps; Ps| Pt ST - case UDK: - this.emit('udk', { - clearAll: this.params[0] === 0, - eraseBelow: this.params[0] === 1, - lockKeys: this.params[1] === 0, - dontLockKeys: this.params[1] === 1, - keyList: (this.params[2] + '').split(';').map(function(part) { - part = part.split('/'); - return { - keyCode: part[0], - hexKeyValue: part[1] - }; - }) - }); + case '': break; // Request Status String (DECRQSS). - // DCS $ q Pt ST // test: echo -e '\eP$q"p\e\\' case '$q': - var valid = 0; + var pt = this.currentParam + , valid = false; switch (pt) { // DECSCA - // CSI Ps " q case '"q': pt = '0"q'; - valid = 1; break; // DECSCL - // CSI Ps ; Ps " p case '"p': - pt = '61;0"p'; - valid = 1; + pt = '61"p'; break; // DECSTBM - // CSI Ps ; Ps r case 'r': pt = '' + (this.scrollTop + 1) + ';' + (this.scrollBottom + 1) + 'r'; - valid = 1; break; // SGR - // CSI Pm m case 'm': - // TODO: Parse this.curAttr here. - // pt = '0m'; - // valid = 1; - valid = 0; // Not implemented. + pt = '0m'; break; default: this.error('Unknown DCS Pt: %s.', pt); - valid = 0; // unimplemented + pt = ''; break; } - this.send('\x1bP' + valid + '$r' + pt + '\x1b\\'); + this.send('\x1bP' + +valid + '$r' + pt + '\x1b\\'); break; // Set Termcap/Terminfo Data (xterm, experimental). - // DCS + p Pt ST case '+p': - this.emit('set terminfo', { - name: this.params[0] - }); break; // Request Termcap/Terminfo String (xterm, experimental) // Regular xterm does not even respond to this sequence. // This can cause a small glitch in vim. - // DCS + q Pt ST // test: echo -ne '\eP+q6b64\e\\' case '+q': - var valid = false; - this.send('\x1bP' + +valid + '+r' + pt + '\x1b\\'); - break; + var pt = this.currentParam + , valid = false; - // Implement tmux sequence forwarding is - // someone uses term.js for a multiplexer. - // DCS tmux; ESC Pt ST - case 'tmux;\x1b': - this.emit('passthrough', pt); + this.send('\x1bP' + +valid + '+r' + pt + '\x1b\\'); break; default: - this.error('Unknown DCS prefix: %s.', pt); + this.error('Unknown DCS prefix: %s.', this.prefix); break; } this.currentParam = 0; this.prefix = ''; this.state = normal; + } else if (!this.currentParam) { + if (!this.prefix && ch !== '$' && ch !== '+') { + this.currentParam = ch; + } else if (this.prefix.length === 2) { + this.currentParam = ch; + } else { + this.prefix += ch; + } } else { this.currentParam += ch; - if (!this.prefix) { - if (/^\d*;\d*\|/.test(this.currentParam)) { - this.prefix = UDK; - this.params = this.currentParam.split(/[;|]/).map(function(n) { - if (!n.length) return 0; - return +n; - }).slice(0, -1); - this.currentParam = ''; - } else if (/^[$+][a-zA-Z]/.test(this.currentParam) - || /^\w+;\x1b/.test(this.currentParam)) { - this.prefix = this.currentParam; - this.currentParam = ''; - } - } } break; case ignore: // For PM and APC. - if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') { + if (ch === '\x1b' || ch === '\x07') { + if (ch === '\x1b') i++; this.state = normal; } break; @@ -2555,12 +2609,14 @@ Terminal.prototype.write = function(data) { this.updateRange(this.y); this.refresh(this.refreshStart, this.refreshEnd); - - return true; }; -Terminal.prototype.writeln = function(data) { - return this.write(data + '\r\n'); +/** + * Writes text to the terminal, followed by a break line character (\n). + * @param {string} text The text to write to the terminal. + */ +Terminal.prototype.writeln = function (data) { + this.write(data + '\r\n'); }; Terminal.prototype.end = function(data) { @@ -2582,257 +2638,311 @@ Terminal.prototype.pause = function() { // Key Resources: // https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent -Terminal.prototype.keyDown = function(ev) { - var self = this - , key; +Terminal.prototype.keyDown = function (ev) { + var self = this; + var result = this.evaluateKeyEscapeSequence(ev); + + if (result.scrollDisp) { + this.scrollDisp(result.scrollDisp); + return this.cancel(ev); + } + + if (isThirdLevelShift(this, ev)) { + return true; + } + + if (result.cancel) { + // The event is canceled at the end already, is this necessary? + this.cancel(ev); + } + if (!result.key) { + return true; + } + + this.emit('keydown', ev); + this.emit('key', result.key, ev); + this.showCursor(); + this.handler(result.key); + + return this.cancel(ev); +}; + +/** + * 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 {KeyboardEvent} ev The keyboard event to be translated to key escape sequence. + */ +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 + }; + var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3; switch (ev.keyCode) { - // backspace case 8: - if (ev.altKey) { - key = '\x17'; - break; - } + // backspace if (ev.shiftKey) { - key = '\x08'; // ^H + result.key = '\x08'; // ^H break; } - key = '\x7f'; // ^? + result.key = '\x7f'; // ^? break; - // tab case 9: + // tab if (ev.shiftKey) { - key = '\x1b[Z'; + result.key = '\x1b[Z'; break; } - key = '\t'; + result.key = '\t'; + result.cancel = true; break; - // return/enter case 13: - key = '\r'; + // return/enter + result.key = '\r'; + result.cancel = true; break; - // escape case 27: - key = '\x1b'; - break; - // space - case 32: - key = '\x20'; + // escape + result.key = '\x1b'; + result.cancel = true; break; - // left-arrow case 37: - if (this.applicationCursor) { - key = '\x1bOD'; // SS3 as ^[O for 7-bit - //key = '\x8fD'; // SS3 as 0x8f for 8-bit - break; - } - if (ev.ctrlKey) { - key = '\x1b[5D'; - break; + // left-arrow + if (modifiers) { + result.key = '\x1b[1;' + (modifiers + 1) + 'D'; + // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards + // http://unix.stackexchange.com/a/108106 + if (result.key == '\x1b[1;3D') { + result.key = '\x1b[1;5D'; + } + } else if (this.applicationCursor) { + result.key = '\x1bOD'; + } else { + result.key = '\x1b[D'; } - key = '\x1b[D'; break; - // right-arrow case 39: - if (this.applicationCursor) { - key = '\x1bOC'; - break; - } - if (ev.ctrlKey) { - key = '\x1b[5C'; - break; + // right-arrow + if (modifiers) { + result.key = '\x1b[1;' + (modifiers + 1) + 'C'; + // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward + // http://unix.stackexchange.com/a/108106 + if (result.key == '\x1b[1;3C') { + result.key = '\x1b[1;5C'; + } + } else if (this.applicationCursor) { + result.key = '\x1bOC'; + } else { + result.key = '\x1b[C'; } - key = '\x1b[C'; break; - // up-arrow case 38: - if (this.applicationCursor) { - key = '\x1bOA'; - break; - } - if (ev.ctrlKey) { - this.scrollDisp(-1); - return cancel(ev); + // up-arrow + if (modifiers) { + result.key = '\x1b[1;' + (modifiers + 1) + 'A'; + // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow + // http://unix.stackexchange.com/a/108106 + if (result.key == '\x1b[1;3A') { + result.key = '\x1b[1;5A'; + } + } else if (this.applicationCursor) { + result.key = '\x1bOA'; } else { - key = '\x1b[A'; + result.key = '\x1b[A'; } break; - // down-arrow case 40: - if (this.applicationCursor) { - key = '\x1bOB'; - break; - } - if (ev.ctrlKey) { - this.scrollDisp(1); - return cancel(ev); + // down-arrow + if (modifiers) { + result.key = '\x1b[1;' + (modifiers + 1) + 'B'; + // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow + // http://unix.stackexchange.com/a/108106 + if (result.key == '\x1b[1;3B') { + result.key = '\x1b[1;5B'; + } + } else if (this.applicationCursor) { + result.key = '\x1bOB'; } else { - key = '\x1b[B'; + result.key = '\x1b[B'; } break; - // delete - case 46: - key = '\x1b[3~'; - break; - // insert case 45: - key = '\x1b[2~'; + // insert + if (!ev.shiftKey && !ev.ctrlKey) { + // or + are used to + // copy-paste on some systems. + result.key = '\x1b[2~'; + } break; - // home - case 36: - if (this.applicationKeypad) { - key = '\x1bOH'; - break; + case 46: + // delete + if (modifiers) { + result.key = '\x1b[3;' + (modifiers + 1) + '~'; + } else { + result.key = '\x1b[3~'; } - key = '\x1bOH'; break; - // end + case 36: + // home + if (modifiers) + result.key = '\x1b[1;' + (modifiers + 1) + 'H'; + else if (this.applicationCursor) + result.key = '\x1bOH'; + else + result.key = '\x1b[H'; + break; case 35: - if (this.applicationKeypad) { - key = '\x1bOF'; - break; - } - key = '\x1bOF'; + // end + if (modifiers) + result.key = '\x1b[1;' + (modifiers + 1) + 'F'; + else if (this.applicationCursor) + result.key = '\x1bOF'; + else + result.key = '\x1b[F'; break; - // page up case 33: + // page up if (ev.shiftKey) { - this.scrollDisp(-(this.rows - 1)); - return cancel(ev); + result.scrollDisp = -(this.rows - 1); } else { - key = '\x1b[5~'; + result.key = '\x1b[5~'; } break; - // page down case 34: + // page down if (ev.shiftKey) { - this.scrollDisp(this.rows - 1); - return cancel(ev); + result.scrollDisp = this.rows - 1; } else { - key = '\x1b[6~'; + result.key = '\x1b[6~'; } break; - // F1 case 112: - key = '\x1bOP'; + // F1-F12 + if (modifiers) { + result.key = '\x1b[1;' + (modifiers + 1) + 'P'; + } else { + result.key = '\x1bOP'; + } break; - // F2 case 113: - key = '\x1bOQ'; + if (modifiers) { + result.key = '\x1b[1;' + (modifiers + 1) + 'Q'; + } else { + result.key = '\x1bOQ'; + } break; - // F3 case 114: - key = '\x1bOR'; + if (modifiers) { + result.key = '\x1b[1;' + (modifiers + 1) + 'R'; + } else { + result.key = '\x1bOR'; + } break; - // F4 case 115: - key = '\x1bOS'; + if (modifiers) { + result.key = '\x1b[1;' + (modifiers + 1) + 'S'; + } else { + result.key = '\x1bOS'; + } break; - // F5 case 116: - key = '\x1b[15~'; + if (modifiers) { + result.key = '\x1b[15;' + (modifiers + 1) + '~'; + } else { + result.key = '\x1b[15~'; + } break; - // F6 case 117: - key = '\x1b[17~'; + if (modifiers) { + result.key = '\x1b[17;' + (modifiers + 1) + '~'; + } else { + result.key = '\x1b[17~'; + } break; - // F7 case 118: - key = '\x1b[18~'; + if (modifiers) { + result.key = '\x1b[18;' + (modifiers + 1) + '~'; + } else { + result.key = '\x1b[18~'; + } break; - // F8 case 119: - key = '\x1b[19~'; + if (modifiers) { + result.key = '\x1b[19;' + (modifiers + 1) + '~'; + } else { + result.key = '\x1b[19~'; + } break; - // F9 case 120: - key = '\x1b[20~'; + if (modifiers) { + result.key = '\x1b[20;' + (modifiers + 1) + '~'; + } else { + result.key = '\x1b[20~'; + } break; - // F10 case 121: - key = '\x1b[21~'; + if (modifiers) { + result.key = '\x1b[21;' + (modifiers + 1) + '~'; + } else { + result.key = '\x1b[21~'; + } break; - // F11 case 122: - key = '\x1b[23~'; + if (modifiers) { + result.key = '\x1b[23;' + (modifiers + 1) + '~'; + } else { + result.key = '\x1b[23~'; + } break; - // F12 case 123: - key = '\x1b[24~'; + if (modifiers) { + result.key = '\x1b[24;' + (modifiers + 1) + '~'; + } else { + result.key = '\x1b[24~'; + } break; default: // a-z and space - if (ev.ctrlKey) { + if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { if (ev.keyCode >= 65 && ev.keyCode <= 90) { - // Ctrl-A - if (this.screenKeys) { - if (!this.prefixMode && !this.selectMode && ev.keyCode === 65) { - this.enterPrefix(); - return cancel(ev); - } - } - // Ctrl-V - if (this.prefixMode && ev.keyCode === 86) { - this.leavePrefix(); - return; - } - // Ctrl-C - if ((this.prefixMode || this.selectMode) && ev.keyCode === 67) { - if (this.visualMode) { - setTimeout(function() { - self.leaveVisual(); - }, 1); - } - return; - } - 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 (ev.altKey) { + } else if (!this.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) { + // On Mac this is a third level shift. Use instead. 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) return true; - - if (this.prefixMode) { - this.leavePrefix(); - return cancel(ev); - } - - if (this.selectMode) { - this.keySelect(ev, key); - return cancel(ev); - } - - this.emit('keydown', ev); - this.emit('key', key, ev); - - this.showCursor(); - this.handler(key); - - return cancel(ev); + return result; }; Terminal.prototype.setgLevel = function(g) { @@ -2847,10 +2957,16 @@ Terminal.prototype.setgCharset = function(g, charset) { } }; +/** + * Handle a keypress event. + * Key Resources: + * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent + * @param {KeyboardEvent} ev The keypress event to be handled. + */ Terminal.prototype.keyPress = function(ev) { var key; - cancel(ev); + this.cancel(ev); if (ev.charCode) { key = ev.charCode; @@ -2862,30 +2978,36 @@ Terminal.prototype.keyPress = function(ev) { return false; } - if (!key || ev.ctrlKey || ev.altKey || ev.metaKey) return false; - - key = String.fromCharCode(key); - - if (this.prefixMode) { - this.leavePrefix(); - this.keyPrefix(ev, key); + if (!key || ( + (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev) + )) { return false; } - if (this.selectMode) { - this.keySelect(ev, key); - return false; - } + key = String.fromCharCode(key); this.emit('keypress', key, ev); this.emit('key', key, ev); - this.showCursor(); this.handler(key); return false; }; +Terminal.prototype.compositionStart = function (ev) { + console.log("COMPOSITION: START | Data: " + ev.data); +}; + +Terminal.prototype.compositionUpdate = function (ev) { + console.log("COMPOSITION: UPDATE | Data: " + ev.data); +}; + +Terminal.prototype.compositionEnd = function (ev) { + console.log("COMPOSITION: END | Data: " + ev.data); + + Terminal.focus.send(ev.data); +}; + Terminal.prototype.send = function(data) { var self = this; @@ -5824,6 +5946,15 @@ Terminal.charsets.ISOLatin = null; // /A * Helpers */ +function contains(el, arr) { + for (var i = 0; i < arr.length; i += 1) { + if (el === arr[i]) { + return true; + } + } + return false; +} + function on(el, type, handler, capture) { el.addEventListener(type, handler, capture || false); } @@ -5879,6 +6010,19 @@ function indexOf(obj, el) { return -1; } +function isThirdLevelShift(term, ev) { + var thirdLevelKey = + (term.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || + (term.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 isWide(ch) { if (ch <= '\uff00') return false; return (ch >= '\uff01' && ch <= '\uffbe') @@ -5955,6 +6099,109 @@ function keys(obj) { return keys; } +var wcwidth = (function(opts) { + // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c + // combining characters + var COMBINING = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB], + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + // binary search + function bisearch(ucs) { + var min = 0; + var max = COMBINING.length - 1; + var mid; + if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1]) + return false; + while (max >= min) { + mid = Math.floor((min + max) / 2); + if (ucs > COMBINING[mid][1]) + min = mid + 1; + else if (ucs < COMBINING[mid][0]) + max = mid - 1; + else + return true; + } + return false; + } + function wcwidth(ucs) { + // test for 8-bit control characters + if (ucs === 0) + return opts.nul; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return opts.control; + // binary search in table of non-spacing characters + if (bisearch(ucs)) + return 0; + // if we arrive here, ucs is not a combining or C0/C1 control character + return 1 + + ( + ucs >= 0x1100 && + ( + ucs <= 0x115f || // Hangul Jamo init. consonants + ucs == 0x2329 || + ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || // CJK..Yi + (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables + (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs + (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms + (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms + (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd) + ) + ); + } + return wcwidth; +})({nul: 0, control: 0}); // configurable options + /** * Expose */ From 0c9bfe11de52af7a6c9abb40fd3f6787fbe83b00 Mon Sep 17 00:00:00 2001 From: Exile Date: Sun, 25 Sep 2016 12:12:59 +0300 Subject: [PATCH 4/7] Removed the different various modes and dead code/variables --- README.md | 9 +- src/term.js | 954 +--------------------------------------------------- 2 files changed, 13 insertions(+), 950 deletions(-) diff --git a/README.md b/README.md index f05b267..886540d 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,15 @@ A full xterm clone written in javascript. Used by [**Cloud 9v2**](https://github.com/exsilium/cloud9). -Maintenance fork to keep compatibility with existing implementation. Backporting crucial fixes from [sourcelair/xterm.js](https://github.com/sourcelair/xterm.js) +Maintenance fork to keep compatibility with existing implementation. Backporting crucial fixes from [sourcelair/xterm.js](https://github.com/sourcelair/xterm.js). Project focus is on usability and compatibility - keeping it simple. * Fix cross platform input problems [sourcelair/xterm.js#60](https://github.com/sourcelair/xterm.js/pull/60); * Textarea used for DOM event capturing (`compositionstart`, `-update`, `-end`); * Enhancements made in `evaluateKeyEscapeSequence`; +* Killed the various modes: prefixMode; selectMode; visualMode; searchMode - use tmux/screen instead ## License -Copyright (c) 2014-2016, Sten Feldman (MIT License) -Copyright (c) 2014-2016, SourceLair, Private Company (www.sourcelair.com) (MIT License) -Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) +* Copyright (c) 2013-2016, Sten Feldman (MIT License) +* Copyright (c) 2014-2016, SourceLair, Private Company (www.sourcelair.com) (MIT License) +* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) diff --git a/src/term.js b/src/term.js index 8b50fcd..7e9a312 100644 --- a/src/term.js +++ b/src/term.js @@ -172,8 +172,7 @@ var normal = 0 , osc = 3 , charset = 4 , dcs = 5 - , ignore = 6 - , UDK = { type: 'udk' }; + , ignore = 6; /** * Terminal @@ -234,13 +233,6 @@ function Terminal(options) { this.cols = options.cols || options.geometry[0]; this.rows = options.rows || options.geometry[1]; - // Act as though we are a node TTY stream: - this.setRawMode; - this.isTTY = true; - this.isRaw = true; - this.columns = this.cols; - this.rows = this.rows; - if (options.handler) { this.on('data', options.handler); } @@ -260,23 +252,10 @@ function Terminal(options) { // modes this.applicationKeypad = false; this.applicationCursor = false; - this.originMode = false; this.insertMode = false; this.wraparoundMode = false; this.normal = null; - // select modes - this.prefixMode = false; - this.selectMode = false; - this.visualMode = false; - this.searchMode = false; - this.searchDown; - this.entry = ''; - this.entryPrefix = 'Search: '; - this._real; - this._selected; - this._textarea; - // charset this.charset = null; this.gcharset = null; @@ -474,14 +453,6 @@ Terminal.prototype.focus = function() { if (this.sendFocus) this.send('\x1b[I'); this.showCursor(); - // try { - // this.element.focus(); - // } catch (e) { - // ; - // } - - // this.emit('focus'); - Terminal.focus = this; }; @@ -492,14 +463,6 @@ Terminal.prototype.blur = function() { this.refresh(this.y, this.y); if (this.sendFocus) this.send('\x1b[O'); - // try { - // this.element.blur(); - // } catch (e) { - // ; - // } - - // this.emit('blur'); - Terminal.focus = null; }; @@ -520,8 +483,6 @@ Terminal.prototype.initGlobal = function() { Terminal.bindKeys(document); - Terminal.bindCopy(document); - if (this.useStyle) { Terminal.insertStyle(document, this.colors[256], this.colors[257]); } @@ -544,20 +505,14 @@ Terminal.prototype.clearSelection = function () { */ Terminal.bindPaste = function(document) { - // This seems to work well for ctrl-V and middle-click, - // even without the contentEditable workaround. - var window = document.defaultView; - on(window, 'paste', function(ev) { - var term = Terminal.focus; - if (!term) return; + on(Terminal._textarea, 'paste', function(ev) { + ev.stopPropagation(); if (ev.clipboardData) { - term.send(ev.clipboardData.getData('text/plain')); - } else if (term.context.clipboardData) { - term.send(term.context.clipboardData.getData('Text')); + var text = ev.clipboardData.getData('text/plain'); + Terminal.focus.send(text); + Terminal._textarea.value = ''; + return Terminal.cancel(ev); } - // Not necessary. Do it anyway for good measure. - term.element.contentEditable = 'inherit'; - return cancel(ev); }); }; @@ -654,51 +609,6 @@ Terminal.bindKeys = function(document) { }); }; -/** - * Copy Selection w/ Ctrl-C (Select Mode) - */ - -Terminal.bindCopy = function(document) { - var window = document.defaultView; - - // if (!('onbeforecopy' in document)) { - // // Copies to *only* the clipboard. - // on(window, 'copy', function fn(ev) { - // var term = Terminal.focus; - // if (!term) return; - // if (!term._selected) return; - // var text = term.grabText( - // term._selected.x1, term._selected.x2, - // term._selected.y1, term._selected.y2); - // term.emit('copy', text); - // ev.clipboardData.setData('text/plain', text); - // }); - // return; - // } - - // Copies to primary selection *and* clipboard. - // NOTE: This may work better on capture phase, - // or using the `beforecopy` event. - on(window, 'copy', function(ev) { - var term = Terminal.focus; - if (!term) return; - if (!term._selected) return; - var textarea = term.getCopyTextarea(); - var text = term.grabText( - term._selected.x1, term._selected.x2, - term._selected.y1, term._selected.y2); - term.emit('copy', text); - textarea.focus(); - textarea.textContent = text; - textarea.value = text; - textarea.setSelectionRange(0, text.length); - setTimeout(function() { - term.element.focus(); - term.focus(); - }, 1); - }); -}; - /** * Insert a default style */ @@ -729,19 +639,6 @@ Terminal.insertStyle = function(document, bg, fg) { + ' background: ' + fg + ';\n' + '}\n'; - // var out = ''; - // each(Terminal.colors, function(color, i) { - // if (i === 256) { - // out += '\n.term-bg-color-default { background-color: ' + color + '; }'; - // } - // if (i === 257) { - // out += '\n.term-fg-color-default { color: ' + color + '; }'; - // } - // out += '\n.term-bg-color-' + i + ' { background-color: ' + color + '; }'; - // out += '\n.term-fg-color-' + i + ' { color: ' + color + '; }'; - // }); - // style.innerHTML += out + '\n'; - head.insertBefore(style, head.firstChild); }; @@ -864,11 +761,6 @@ Terminal.prototype.open = function(parent) { } }); - // This causes slightly funky behavior. - // on(this.element, 'blur', function() { - // self.blur(); - // }); - on(this.element, 'mousedown', function() { self.focus(); }); @@ -923,10 +815,6 @@ Terminal.prototype.open = function(parent) { this.emit('open'); }; -Terminal.prototype.setRawMode = function(value) { - this.isRaw = !!value; -}; - // XTerm mouse events // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking // To better understand these @@ -1352,7 +1240,7 @@ Terminal.prototype.refresh = function(start, end) { if (y === this.y && this.cursorState - && (this.ydisp === this.ybase || this.selectMode) + && (this.ydisp === this.ybase) && !this.cursorHidden) { x = this.x; } else { @@ -4935,102 +4823,6 @@ Terminal.prototype.deleteColumns = function() { this.maxRange(); }; -/** - * Prefix/Select/Visual/Search Modes - */ - -Terminal.prototype.enterPrefix = function() { - this.prefixMode = true; -}; - -Terminal.prototype.leavePrefix = function() { - this.prefixMode = false; -}; - -Terminal.prototype.enterSelect = function() { - this._real = { - x: this.x, - y: this.y, - ydisp: this.ydisp, - ybase: this.ybase, - cursorHidden: this.cursorHidden, - lines: this.copyBuffer(this.lines), - write: this.write - }; - this.write = function() {}; - this.selectMode = true; - this.visualMode = false; - this.cursorHidden = false; - this.refresh(this.y, this.y); -}; - -Terminal.prototype.leaveSelect = function() { - this.x = this._real.x; - this.y = this._real.y; - this.ydisp = this._real.ydisp; - this.ybase = this._real.ybase; - this.cursorHidden = this._real.cursorHidden; - this.lines = this._real.lines; - this.write = this._real.write; - delete this._real; - this.selectMode = false; - this.visualMode = false; - this.refresh(0, this.rows - 1); -}; - -Terminal.prototype.enterVisual = function() { - this._real.preVisual = this.copyBuffer(this.lines); - this.selectText(this.x, this.x, this.ydisp + this.y, this.ydisp + this.y); - this.visualMode = true; -}; - -Terminal.prototype.leaveVisual = function() { - this.lines = this._real.preVisual; - delete this._real.preVisual; - delete this._selected; - this.visualMode = false; - this.refresh(0, this.rows - 1); -}; - -Terminal.prototype.enterSearch = function(down) { - this.entry = ''; - this.searchMode = true; - this.searchDown = down; - this._real.preSearch = this.copyBuffer(this.lines); - this._real.preSearchX = this.x; - this._real.preSearchY = this.y; - - var bottom = this.ydisp + this.rows - 1; - for (var i = 0; i < this.entryPrefix.length; i++) { - //this.lines[bottom][i][0] = (this.defAttr & ~0x1ff) | 4; - //this.lines[bottom][i][1] = this.entryPrefix[i]; - this.lines[bottom][i] = [ - (this.defAttr & ~0x1ff) | 4, - this.entryPrefix[i] - ]; - } - - this.y = this.rows - 1; - this.x = this.entryPrefix.length; - - this.refresh(this.rows - 1, this.rows - 1); -}; - -Terminal.prototype.leaveSearch = function() { - this.searchMode = false; - - if (this._real.preSearch) { - this.lines = this._real.preSearch; - this.x = this._real.preSearchX; - this.y = this._real.preSearchY; - delete this._real.preSearch; - delete this._real.preSearchX; - delete this._real.preSearchY; - } - - this.refresh(this.rows - 1, this.rows - 1); -}; - Terminal.prototype.copyBuffer = function(lines) { var lines = lines || this.lines , out = []; @@ -5088,120 +4880,6 @@ Terminal.prototype.copyText = function(text) { }, 1); }; -Terminal.prototype.selectText = function(x1, x2, y1, y2) { - var ox1 - , ox2 - , oy1 - , oy2 - , tmp - , x - , y - , xl - , attr; - - if (this._selected) { - ox1 = this._selected.x1; - ox2 = this._selected.x2; - oy1 = this._selected.y1; - oy2 = this._selected.y2; - - if (oy2 < oy1) { - tmp = ox2; - ox2 = ox1; - ox1 = tmp; - tmp = oy2; - oy2 = oy1; - oy1 = tmp; - } - - if (ox2 < ox1 && oy1 === oy2) { - tmp = ox2; - ox2 = ox1; - ox1 = tmp; - } - - for (y = oy1; y <= oy2; y++) { - x = 0; - xl = this.cols - 1; - if (y === oy1) { - x = ox1; - } - if (y === oy2) { - xl = ox2; - } - for (; x <= xl; x++) { - if (this.lines[y][x].old != null) { - //this.lines[y][x][0] = this.lines[y][x].old; - //delete this.lines[y][x].old; - attr = this.lines[y][x].old; - delete this.lines[y][x].old; - this.lines[y][x] = [attr, this.lines[y][x][1]]; - } - } - } - - y1 = this._selected.y1; - x1 = this._selected.x1; - } - - y1 = Math.max(y1, 0); - y1 = Math.min(y1, this.ydisp + this.rows - 1); - - y2 = Math.max(y2, 0); - y2 = Math.min(y2, this.ydisp + this.rows - 1); - - this._selected = { x1: x1, x2: x2, y1: y1, y2: y2 }; - - if (y2 < y1) { - tmp = x2; - x2 = x1; - x1 = tmp; - tmp = y2; - y2 = y1; - y1 = tmp; - } - - if (x2 < x1 && y1 === y2) { - tmp = x2; - x2 = x1; - x1 = tmp; - } - - for (y = y1; y <= y2; y++) { - x = 0; - xl = this.cols - 1; - if (y === y1) { - x = x1; - } - if (y === y2) { - xl = x2; - } - for (; x <= xl; x++) { - //this.lines[y][x].old = this.lines[y][x][0]; - //this.lines[y][x][0] &= ~0x1ff; - //this.lines[y][x][0] |= (0x1ff << 9) | 4; - attr = this.lines[y][x][0]; - this.lines[y][x] = [ - (attr & ~0x1ff) | ((0x1ff << 9) | 4), - this.lines[y][x][1] - ]; - this.lines[y][x].old = attr; - } - } - - y1 = y1 - this.ydisp; - y2 = y2 - this.ydisp; - - y1 = Math.max(y1, 0); - y1 = Math.min(y1, this.rows - 1); - - y2 = Math.max(y2, 0); - y2 = Math.min(y2, this.rows - 1); - - //this.refresh(y1, y2); - this.refresh(0, this.rows - 1); -}; - Terminal.prototype.grabText = function(x1, x2, y1, y2) { var out = '' , buf = '' @@ -5264,622 +4942,6 @@ Terminal.prototype.grabText = function(x1, x2, y1, y2) { return out; }; -Terminal.prototype.keyPrefix = function(ev, key) { - if (key === 'k' || key === '&') { - this.destroy(); - } else if (key === 'p' || key === ']') { - this.emit('request paste'); - } else if (key === 'c') { - this.emit('request create'); - } else if (key >= '0' && key <= '9') { - key = +key - 1; - if (!~key) key = 9; - this.emit('request term', key); - } else if (key === 'n') { - this.emit('request term next'); - } else if (key === 'P') { - this.emit('request term previous'); - } else if (key === ':') { - this.emit('request command mode'); - } else if (key === '[') { - this.enterSelect(); - } -}; - -Terminal.prototype.keySelect = function(ev, key) { - this.showCursor(); - - if (this.searchMode || key === 'n' || key === 'N') { - return this.keySearch(ev, key); - } - - if (key === '\x04') { // ctrl-d - var y = this.ydisp + this.y; - if (this.ydisp === this.ybase) { - // Mimic vim behavior - this.y = Math.min(this.y + (this.rows - 1) / 2 | 0, this.rows - 1); - this.refresh(0, this.rows - 1); - } else { - this.scrollDisp((this.rows - 1) / 2 | 0); - } - if (this.visualMode) { - this.selectText(this.x, this.x, y, this.ydisp + this.y); - } - return; - } - - if (key === '\x15') { // ctrl-u - var y = this.ydisp + this.y; - if (this.ydisp === 0) { - // Mimic vim behavior - this.y = Math.max(this.y - (this.rows - 1) / 2 | 0, 0); - this.refresh(0, this.rows - 1); - } else { - this.scrollDisp(-(this.rows - 1) / 2 | 0); - } - if (this.visualMode) { - this.selectText(this.x, this.x, y, this.ydisp + this.y); - } - return; - } - - if (key === '\x06') { // ctrl-f - var y = this.ydisp + this.y; - this.scrollDisp(this.rows - 1); - if (this.visualMode) { - this.selectText(this.x, this.x, y, this.ydisp + this.y); - } - return; - } - - if (key === '\x02') { // ctrl-b - var y = this.ydisp + this.y; - this.scrollDisp(-(this.rows - 1)); - if (this.visualMode) { - this.selectText(this.x, this.x, y, this.ydisp + this.y); - } - return; - } - - if (key === 'k' || key === '\x1b[A') { - var y = this.ydisp + this.y; - this.y--; - if (this.y < 0) { - this.y = 0; - this.scrollDisp(-1); - } - if (this.visualMode) { - this.selectText(this.x, this.x, y, this.ydisp + this.y); - } else { - this.refresh(this.y, this.y + 1); - } - return; - } - - if (key === 'j' || key === '\x1b[B') { - var y = this.ydisp + this.y; - this.y++; - if (this.y >= this.rows) { - this.y = this.rows - 1; - this.scrollDisp(1); - } - if (this.visualMode) { - this.selectText(this.x, this.x, y, this.ydisp + this.y); - } else { - this.refresh(this.y - 1, this.y); - } - return; - } - - if (key === 'h' || key === '\x1b[D') { - var x = this.x; - this.x--; - if (this.x < 0) { - this.x = 0; - } - if (this.visualMode) { - this.selectText(x, this.x, this.ydisp + this.y, this.ydisp + this.y); - } else { - this.refresh(this.y, this.y); - } - return; - } - - if (key === 'l' || key === '\x1b[C') { - var x = this.x; - this.x++; - if (this.x >= this.cols) { - this.x = this.cols - 1; - } - if (this.visualMode) { - this.selectText(x, this.x, this.ydisp + this.y, this.ydisp + this.y); - } else { - this.refresh(this.y, this.y); - } - return; - } - - if (key === 'v' || key === ' ') { - if (!this.visualMode) { - this.enterVisual(); - } else { - this.leaveVisual(); - } - return; - } - - if (key === 'y') { - if (this.visualMode) { - var text = this.grabText( - this._selected.x1, this._selected.x2, - this._selected.y1, this._selected.y2); - this.copyText(text); - this.leaveVisual(); - // this.leaveSelect(); - } - return; - } - - if (key === 'q' || key === '\x1b') { - if (this.visualMode) { - this.leaveVisual(); - } else { - this.leaveSelect(); - } - return; - } - - if (key === 'w' || key === 'W') { - var ox = this.x; - var oy = this.y; - var oyd = this.ydisp; - - var x = this.x; - var y = this.y; - var yb = this.ydisp; - var saw_space = false; - - for (;;) { - var line = this.lines[yb + y]; - while (x < this.cols) { - if (line[x][1] <= ' ') { - saw_space = true; - } else if (saw_space) { - break; - } - x++; - } - if (x >= this.cols) x = this.cols - 1; - if (x === this.cols - 1 && line[x][1] <= ' ') { - x = 0; - if (++y >= this.rows) { - y--; - if (++yb > this.ybase) { - yb = this.ybase; - x = this.x; - break; - } - } - continue; - } - break; - } - - this.x = x, this.y = y; - this.scrollDisp(-this.ydisp + yb); - - if (this.visualMode) { - this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); - } - return; - } - - if (key === 'b' || key === 'B') { - var ox = this.x; - var oy = this.y; - var oyd = this.ydisp; - - var x = this.x; - var y = this.y; - var yb = this.ydisp; - - for (;;) { - var line = this.lines[yb + y]; - var saw_space = x > 0 && line[x][1] > ' ' && line[x - 1][1] > ' '; - while (x >= 0) { - if (line[x][1] <= ' ') { - if (saw_space && (x + 1 < this.cols && line[x + 1][1] > ' ')) { - x++; - break; - } else { - saw_space = true; - } - } - x--; - } - if (x < 0) x = 0; - if (x === 0 && (line[x][1] <= ' ' || !saw_space)) { - x = this.cols - 1; - if (--y < 0) { - y++; - if (--yb < 0) { - yb++; - x = 0; - break; - } - } - continue; - } - break; - } - - this.x = x, this.y = y; - this.scrollDisp(-this.ydisp + yb); - - if (this.visualMode) { - this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); - } - return; - } - - if (key === 'e' || key === 'E') { - var x = this.x + 1; - var y = this.y; - var yb = this.ydisp; - if (x >= this.cols) x--; - - for (;;) { - var line = this.lines[yb + y]; - while (x < this.cols) { - if (line[x][1] <= ' ') { - x++; - } else { - break; - } - } - while (x < this.cols) { - if (line[x][1] <= ' ') { - if (x - 1 >= 0 && line[x - 1][1] > ' ') { - x--; - break; - } - } - x++; - } - if (x >= this.cols) x = this.cols - 1; - if (x === this.cols - 1 && line[x][1] <= ' ') { - x = 0; - if (++y >= this.rows) { - y--; - if (++yb > this.ybase) { - yb = this.ybase; - break; - } - } - continue; - } - break; - } - - this.x = x, this.y = y; - this.scrollDisp(-this.ydisp + yb); - - if (this.visualMode) { - this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); - } - return; - } - - if (key === '^' || key === '0') { - var ox = this.x; - - if (key === '0') { - this.x = 0; - } else if (key === '^') { - var line = this.lines[this.ydisp + this.y]; - var x = 0; - while (x < this.cols) { - if (line[x][1] > ' ') { - break; - } - x++; - } - if (x >= this.cols) x = this.cols - 1; - this.x = x; - } - - if (this.visualMode) { - this.selectText(ox, this.x, this.ydisp + this.y, this.ydisp + this.y); - } else { - this.refresh(this.y, this.y); - } - return; - } - - if (key === '$') { - var ox = this.x; - var line = this.lines[this.ydisp + this.y]; - var x = this.cols - 1; - while (x >= 0) { - if (line[x][1] > ' ') { - if (this.visualMode && x < this.cols - 1) x++; - break; - } - x--; - } - if (x < 0) x = 0; - this.x = x; - if (this.visualMode) { - this.selectText(ox, this.x, this.ydisp + this.y, this.ydisp + this.y); - } else { - this.refresh(this.y, this.y); - } - return; - } - - if (key === 'g' || key === 'G') { - var ox = this.x; - var oy = this.y; - var oyd = this.ydisp; - if (key === 'g') { - this.x = 0, this.y = 0; - this.scrollDisp(-this.ydisp); - } else if (key === 'G') { - this.x = 0, this.y = this.rows - 1; - this.scrollDisp(this.ybase); - } - if (this.visualMode) { - this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); - } - return; - } - - if (key === 'H' || key === 'M' || key === 'L') { - var ox = this.x; - var oy = this.y; - if (key === 'H') { - this.x = 0, this.y = 0; - } else if (key === 'M') { - this.x = 0, this.y = this.rows / 2 | 0; - } else if (key === 'L') { - this.x = 0, this.y = this.rows - 1; - } - if (this.visualMode) { - this.selectText(ox, this.x, this.ydisp + oy, this.ydisp + this.y); - } else { - this.refresh(oy, oy); - this.refresh(this.y, this.y); - } - return; - } - - if (key === '{' || key === '}') { - var ox = this.x; - var oy = this.y; - var oyd = this.ydisp; - - var line; - var saw_full = false; - var found = false; - var first_is_space = -1; - var y = this.y + (key === '{' ? -1 : 1); - var yb = this.ydisp; - var i; - - if (key === '{') { - if (y < 0) { - y++; - if (yb > 0) yb--; - } - } else if (key === '}') { - if (y >= this.rows) { - y--; - if (yb < this.ybase) yb++; - } - } - - for (;;) { - line = this.lines[yb + y]; - - for (i = 0; i < this.cols; i++) { - if (line[i][1] > ' ') { - if (first_is_space === -1) { - first_is_space = 0; - } - saw_full = true; - break; - } else if (i === this.cols - 1) { - if (first_is_space === -1) { - first_is_space = 1; - } else if (first_is_space === 0) { - found = true; - } else if (first_is_space === 1) { - if (saw_full) found = true; - } - break; - } - } - - if (found) break; - - if (key === '{') { - y--; - if (y < 0) { - y++; - if (yb > 0) yb--; - else break; - } - } else if (key === '}') { - y++; - if (y >= this.rows) { - y--; - if (yb < this.ybase) yb++; - else break; - } - } - } - - if (!found) { - if (key === '{') { - y = 0; - yb = 0; - } else if (key === '}') { - y = this.rows - 1; - yb = this.ybase; - } - } - - this.x = 0, this.y = y; - this.scrollDisp(-this.ydisp + yb); - - if (this.visualMode) { - this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); - } - return; - } - - if (key === '/' || key === '?') { - if (!this.visualMode) { - this.enterSearch(key === '/'); - } - return; - } - - return false; -}; - -Terminal.prototype.keySearch = function(ev, key) { - if (key === '\x1b') { - this.leaveSearch(); - return; - } - - if (key === '\r' || (!this.searchMode && (key === 'n' || key === 'N'))) { - this.leaveSearch(); - - var entry = this.entry; - - if (!entry) { - this.refresh(0, this.rows - 1); - return; - } - - var ox = this.x; - var oy = this.y; - var oyd = this.ydisp; - - var line; - var found = false; - var wrapped = false; - var x = this.x + 1; - var y = this.ydisp + this.y; - var yb, i; - var up = key === 'N' - ? this.searchDown - : !this.searchDown; - - for (;;) { - line = this.lines[y]; - - while (x < this.cols) { - for (i = 0; i < entry.length; i++) { - if (x + i >= this.cols) break; - if (line[x + i][1] !== entry[i]) { - break; - } else if (line[x + i][1] === entry[i] && i === entry.length - 1) { - found = true; - break; - } - } - if (found) break; - x += i + 1; - } - if (found) break; - - x = 0; - - if (!up) { - y++; - if (y > this.ybase + this.rows - 1) { - if (wrapped) break; - // this.setMessage('Search wrapped. Continuing at TOP.'); - wrapped = true; - y = 0; - } - } else { - y--; - if (y < 0) { - if (wrapped) break; - // this.setMessage('Search wrapped. Continuing at BOTTOM.'); - wrapped = true; - y = this.ybase + this.rows - 1; - } - } - } - - if (found) { - if (y - this.ybase < 0) { - yb = y; - y = 0; - if (yb > this.ybase) { - y = yb - this.ybase; - yb = this.ybase; - } - } else { - yb = this.ybase; - y -= this.ybase; - } - - this.x = x, this.y = y; - this.scrollDisp(-this.ydisp + yb); - - if (this.visualMode) { - this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); - } - return; - } - - // this.setMessage("No matches found."); - this.refresh(0, this.rows - 1); - - return; - } - - if (key === '\b' || key === '\x7f') { - if (this.entry.length === 0) return; - var bottom = this.ydisp + this.rows - 1; - this.entry = this.entry.slice(0, -1); - var i = this.entryPrefix.length + this.entry.length; - //this.lines[bottom][i][1] = ' '; - this.lines[bottom][i] = [ - this.lines[bottom][i][0], - ' ' - ]; - this.x--; - this.refresh(this.rows - 1, this.rows - 1); - this.refresh(this.y, this.y); - return; - } - - if (key.length === 1 && key >= ' ' && key <= '~') { - var bottom = this.ydisp + this.rows - 1; - this.entry += key; - var i = this.entryPrefix.length + this.entry.length - 1; - //this.lines[bottom][i][0] = (this.defAttr & ~0x1ff) | 4; - //this.lines[bottom][i][1] = key; - this.lines[bottom][i] = [ - (this.defAttr & ~0x1ff) | 4, - key - ]; - this.x++; - this.refresh(this.rows - 1, this.rows - 1); - this.refresh(this.y, this.y); - return; - } - - return false; -}; - /** * Character Sets */ From 4a74ebbd0614184f9f0b9cc3ddb239a492f5981b Mon Sep 17 00:00:00 2001 From: Sten Feldman Date: Wed, 5 Oct 2016 09:24:39 +0300 Subject: [PATCH 5/7] Updated Readme and added ToDo section --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 886540d..d71932e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ Maintenance fork to keep compatibility with existing implementation. Backporting * Enhancements made in `evaluateKeyEscapeSequence`; * Killed the various modes: prefixMode; selectMode; visualMode; searchMode - use tmux/screen instead +# ToDo + +- [ ] PuTTY like select to Copy and Right-click to Paste functionality + ## License * Copyright (c) 2013-2016, Sten Feldman (MIT License) From 6f207f051f97853523d3848e5b0d0ec4a799f289 Mon Sep 17 00:00:00 2001 From: Exile Date: Thu, 6 Oct 2016 13:45:59 +0000 Subject: [PATCH 6/7] Textarea id; PuTTY-style copy-paste ToDo removed - Textarea id introduced so that the element can be addressed from the application level. - PuTTY-style copy-paste implemented in cloud9 terminal side. This is an application level feature which is unsuitable to be done directly in term.js. --- README.md | 4 ---- src/term.js | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index d71932e..886540d 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,6 @@ Maintenance fork to keep compatibility with existing implementation. Backporting * Enhancements made in `evaluateKeyEscapeSequence`; * Killed the various modes: prefixMode; selectMode; visualMode; searchMode - use tmux/screen instead -# ToDo - -- [ ] PuTTY like select to Copy and Right-click to Paste functionality - ## License * Copyright (c) 2013-2016, Sten Feldman (MIT License) diff --git a/src/term.js b/src/term.js index 7e9a312..4d6f0c1 100644 --- a/src/term.js +++ b/src/term.js @@ -707,6 +707,7 @@ Terminal.prototype.open = function(parent) { this.parent.appendChild(this.element); var textarea = document.createElement('textarea'); + textarea.id = "termTextarea"; textarea.style.position = 'absolute'; textarea.style.left = '-32000px'; textarea.style.top = '-32000px'; From d23894c0119ac22d5e97bfa3a81988b745c6fd62 Mon Sep 17 00:00:00 2001 From: Exile Date: Wed, 10 May 2017 11:58:17 +0000 Subject: [PATCH 7/7] Make OS level paste work via keyboard shortcut - Cmd-V on mac - Ctrl-V elsewhere - Aims to fix one of the issues reported in exsilium/cloud9#21 --- src/term.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/term.js b/src/term.js index 4d6f0c1..6c9d6bd 100644 --- a/src/term.js +++ b/src/term.js @@ -2713,6 +2713,12 @@ Terminal.prototype.evaluateKeyEscapeSequence = function (ev) { result.key = '\x1b[6~'; } break; + case 86: + // Handle keyboard paste (Firefox & Chrome) + if ((this.isMac && ev.metaKey) || (!this.isMac && ev.ctrlKey)) { + Terminal._textarea.focus(); + } + break; case 112: // F1-F12 if (modifiers) { @@ -2855,8 +2861,6 @@ Terminal.prototype.setgCharset = function(g, charset) { Terminal.prototype.keyPress = function(ev) { var key; - this.cancel(ev); - if (ev.charCode) { key = ev.charCode; } else if (ev.which == null) { @@ -2867,6 +2871,15 @@ Terminal.prototype.keyPress = function(ev) { return false; } + // Don't cancel on paste and set focus on textarea (Safari) + if ((this.isMac && ev.metaKey && key === 118) || + (!this.isMac && ev.ctrlKey && key === 118)) { + Terminal._textarea.focus(); + } + else { + this.cancel(ev); + } + if (!key || ( (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev) )) {