From 58056df23d2dc5e0f64f266e85d33b06293f0646 Mon Sep 17 00:00:00 2001 From: Paris Date: Thu, 10 Mar 2016 02:07:10 +0200 Subject: [PATCH 01/13] Start working on replacing textarea with contentEditable --- src/xterm.js | 61 ++++++++-------------------------------------------- 1 file changed, 9 insertions(+), 52 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index 42c8b81c..46f4a87f 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -383,7 +383,7 @@ * Focused Terminal */ Terminal.prototype.focus = function() { - if (document.activeElement === this.textarea) { + if (document.activeElement === this.element) { return; } @@ -392,7 +392,7 @@ } this.showCursor(); - this.textarea.focus(); + this.element.focus(); }; @@ -403,7 +403,7 @@ this.cursorState = 0; this.refresh(this.y, this.y); - this.textarea.blur(); + this.element.blur(); if (this.sendFocus) { this.send('\x1b[O'); @@ -424,14 +424,12 @@ * Bind to paste event */ Terminal.bindPaste = function(term) { - on([term.textarea, term.element], 'paste', function(ev) { - ev.stopPropagation(); + on(term.element, 'paste', function(ev) { if (ev.clipboardData) { var text = ev.clipboardData.getData('text/plain'); term.handler(text); } - term.textarea.value = ''; - return term.cancel(ev); + return term.cancel(ev, true); }); }; @@ -441,33 +439,12 @@ */ Terminal.bindKeys = function(term) { on(term.element, 'keydown', function(ev) { - if (document.activeElement != this) { - return; - } term.keyDown(ev); }, true); on(term.element, 'keypress', function(ev) { - if (document.activeElement != this) { - return; - } term.keyPress(ev); }, true); - - on(term.element, 'keyup', term.focus.bind(term)); - - on(term.textarea, 'keydown', function(ev) { - term.keyDown(ev); - }, true); - - on(term.textarea, 'keypress', function(ev) { - term.keyPress(ev); - - /* - * Truncate the textarea's value, since it is not needed - */ - this.value = ''; - }, true); }; @@ -534,6 +511,7 @@ this.element.classList.add('xterm'); this.element.classList.add('xterm-theme-' + this.theme); this.element.setAttribute('tabindex', 0); + this.element.contentEditable = 'true'; /* * Create the container that will hold the lines of the terminal and then @@ -544,27 +522,6 @@ this.element.appendChild(this.rowContainer); this.children = []; - /* - * Create the container that will hold helpers like the textarea for - * capturing DOM Events. Then produce the helpers. - */ - this.helperContainer = document.createElement('div'); - this.helperContainer.classList.add('xterm-helpers'); - this.element.appendChild(this.helperContainer); - this.textarea = document.createElement('textarea'); - this.textarea.classList.add('xterm-helper-textarea'); - this.textarea.setAttribute('autocorrect', 'off'); - this.textarea.setAttribute('autocapitalize', 'off'); - this.textarea.setAttribute('spellcheck', 'false'); - this.textarea.tabIndex = 0; - this.textarea.onfocus = function() { - self.emit('focus', {terminal: self}); - } - this.textarea.onblur = function() { - self.emit('blur', {terminal: self}); - } - this.helperContainer.appendChild(this.textarea); - for (; i < this.rows; i++) { this.insertRow(); } @@ -2388,7 +2345,7 @@ this.showCursor(); this.handler(key); - return this.cancel(ev); + return this.cancel(ev, true); }; Terminal.prototype.setgLevel = function(g) { @@ -2406,8 +2363,6 @@ Terminal.prototype.keyPress = function(ev) { var key; - this.cancel(ev); - if (ev.charCode) { key = ev.charCode; } else if (ev.which == null) { @@ -2429,6 +2384,8 @@ this.showCursor(); this.handler(key); + this.cancel(ev, true); + return false; }; From 18b2a1c3b4194b36158a24a4646f578e66fcb6ea Mon Sep 17 00:00:00 2001 From: Paris Date: Thu, 10 Mar 2016 02:55:24 +0200 Subject: [PATCH 02/13] Handle drop event --- src/xterm.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/xterm.js b/src/xterm.js index 46f4a87f..c9fec907 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -418,6 +418,7 @@ Terminal.bindPaste(this); Terminal.bindKeys(this); Terminal.bindCopy(this); + Terminal.bindDrop(this); }; /** @@ -458,6 +459,17 @@ }; + Terminal.bindDrop = function (term) { + /* + * Do not perform the "drop" event. Altering the contents of the + * terminal with drag n drop is unwanted behavior. + */ + on(term.element, 'drop', function (ev) { + term.cancel(ev, true); + }); + }; + + /* * Insert the given row to the terminal or produce a new one * if no row argument is passed. Return the inserted row. @@ -512,6 +524,7 @@ this.element.classList.add('xterm-theme-' + this.theme); this.element.setAttribute('tabindex', 0); this.element.contentEditable = 'true'; + this.element.spellcheck = 'false'; /* * Create the container that will hold the lines of the terminal and then From 6406a88ddf4371246a5c59b9853e69c71d8e88fe Mon Sep 17 00:00:00 2001 From: Paris Date: Fri, 8 Apr 2016 18:10:27 +0300 Subject: [PATCH 03/13] pointless --- demo/main.js | 4 +++- src/xterm.js | 28 ++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/demo/main.js b/demo/main.js index d9bc9284..263ba168 100644 --- a/demo/main.js +++ b/demo/main.js @@ -15,7 +15,9 @@ term.writeln(''); term.prompt(); term.on('key', function (key, ev) { - var printable = (!ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey); + var printable = ( + !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey + ); if (ev.keyCode == 13) { term.prompt(); diff --git a/src/xterm.js b/src/xterm.js index c9fec907..fd9d03a4 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -100,10 +100,11 @@ }; EventEmitter.prototype.once = function(type, listener) { + var self = this; function on() { var args = Array.prototype.slice.call(arguments); - this.removeListener(type, on); - return listener.apply(this, args); + self.removeListener(type, on); + return listener.apply(self, args); } on.listener = listener; return this.on(type, on); @@ -430,7 +431,6 @@ var text = ev.clipboardData.getData('text/plain'); term.handler(text); } - return term.cancel(ev, true); }); }; @@ -440,6 +440,16 @@ */ Terminal.bindKeys = function(term) { on(term.element, 'keydown', function(ev) { + /* + * Clear all selections if the key pressed was not among + * Shift, Alt, Cmd, Ctrl. + */ + var selection = window.getSelection(); + if (selection.toString() != '') { + if ([16, 17, 18, 91].indexOf(ev.keyCode) == -1) { + selection.removeAllRanges(); + } + } term.keyDown(ev); }, true); @@ -470,6 +480,17 @@ }; + Terminal.click = function (term) { + /* + * Do not perform the "drop" event. Altering the contents of the + * terminal with drag n drop is unwanted behavior. + */ + on(term.element, 'click', function (ev) { + term.cancel(ev, true); + }); + }; + + /* * Insert the given row to the terminal or produce a new one * if no row argument is passed. Return the inserted row. @@ -523,7 +544,6 @@ this.element.classList.add('xterm'); this.element.classList.add('xterm-theme-' + this.theme); this.element.setAttribute('tabindex', 0); - this.element.contentEditable = 'true'; this.element.spellcheck = 'false'; /* From 82cac90005ca1dbfd6603944a2af9756eed205e8 Mon Sep 17 00:00:00 2001 From: Paris Date: Thu, 2 Jun 2016 12:02:51 +0300 Subject: [PATCH 04/13] Remove helper styling from xterm.css --- src/xterm.css | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/xterm.css b/src/xterm.css index 7a4246a9..5cfa6ff7 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -51,23 +51,6 @@ color: #000; } -.terminal .xterm-helpers { - position: absolute; - top: 0; -} - -.terminal .xterm-helper-textarea { - /* - * HACK: to fix IE's blinking cursor - * Move textarea out of the screen to the far left, so that the cursor is not visible. - */ - text-indent: -9999em; - opacity: 0; - width: 0; - height: 0; - z-index: -10; -} - /* * Determine default colors for xterm.js */ From 178b611b0c7be4559a4c38474b2429bbe955efa9 Mon Sep 17 00:00:00 2001 From: Paris Date: Thu, 2 Jun 2016 12:27:43 +0300 Subject: [PATCH 05/13] Remove useless methods --- src/xterm.js | 59 +--------------------------------------------------- 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index e3410f20..ce4759fe 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -4144,6 +4144,7 @@ this.maxRange(); }; + // CSI P m SP ~ // Delete P s Column(s) (default = 1) (DECDC), VT420 and up // NOTE: xterm doesn't enable this code by default. @@ -4163,64 +4164,6 @@ this.maxRange(); }; - - Terminal.prototype.copyBuffer = function(lines) { - var lines = lines || this.lines - , out = []; - - for (var y = 0; y < lines.length; y++) { - out[y] = []; - for (var x = 0; x < lines[y].length; x++) { - out[y][x] = [lines[y][x][0], lines[y][x][1]]; - } - } - - return out; - }; - - Terminal.prototype.getCopyTextarea = function(text) { - var textarea = this._copyTextarea - , document = this.document; - - if (!textarea) { - 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'; - - document.getElementsByTagName('body')[0].appendChild(textarea); - - this._copyTextarea = textarea; - } - - return textarea; - }; - - // NOTE: Only works for primary selection on X11. - // Non-X11 users should use Ctrl-C instead. - Terminal.prototype.copyText = function(text) { - var self = this - , textarea = this.getCopyTextarea(); - - this.emit('copy', text); - - textarea.focus(); - textarea.textContent = text; - textarea.value = text; - textarea.setSelectionRange(0, text.length); - - setTimeout(function() { - self.element.focus(); - self.focus(); - }, 1); - }; - Terminal.prototype.keyPrefix = function(ev, key) { if (key === 'k' || key === '&') { this.destroy(); From fa7d214e78929a310e9a85612f6aefddd48898f4 Mon Sep 17 00:00:00 2001 From: Paris Date: Thu, 2 Jun 2016 12:35:03 +0300 Subject: [PATCH 06/13] Remove even more useless code --- src/xterm.js | 382 --------------------------------------------------- 1 file changed, 382 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index ce4759fe..ca11292c 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -4164,388 +4164,6 @@ this.maxRange(); }; - 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'); - } - }; - - Terminal.prototype.keySelect = function(ev, key) { - this.showCursor(); - - 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); - } - 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); - } - return; - } - - if (key === '\x06') { // ctrl-f - var y = this.ydisp + this.y; - this.scrollDisp(this.rows - 1); - return; - } - - if (key === '\x02') { // ctrl-b - var y = this.ydisp + this.y; - this.scrollDisp(-(this.rows - 1)); - 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); - } - 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); - } - 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; - } - 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; - } - this.refresh(this.y, this.y); - 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); - - 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); - - 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); - - 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; - } - - 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) { - x--; - } - if (x < 0) x = 0; - this.x = x; - 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); - } - 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; - } - 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); - - return; - } - - return false; - }; - /** * Character Sets */ From 8fa1a465847e6429a151d77b08f6f776bc860165 Mon Sep 17 00:00:00 2001 From: Paris Date: Thu, 2 Jun 2016 13:05:58 +0300 Subject: [PATCH 07/13] Handle the paste event better --- demo/main.js | 6 +++++- src/xterm.js | 27 +++++++++++++++------------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/demo/main.js b/demo/main.js index 263ba168..844d5791 100644 --- a/demo/main.js +++ b/demo/main.js @@ -1,6 +1,6 @@ var terminalContainer = document.getElementById('terminal-container'), term = new Terminal(), - shellprompt = '> '; + shellprompt = '$ '; term.open(terminalContainer); term.fit(); @@ -32,3 +32,7 @@ term.on('key', function (key, ev) { term.write(key); } }); + +term.on('paste', function (data, ev) { + term.write(data); +}); diff --git a/src/xterm.js b/src/xterm.js index ca11292c..e100e7c2 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -430,15 +430,28 @@ }; /** - * Bind to paste event + * Bind to paste event and allow right-click pasting. */ Terminal.bindPaste = function(term) { on(term.element, 'paste', function(ev) { if (ev.clipboardData) { var text = ev.clipboardData.getData('text/plain'); + term.emit('paste', text, ev); term.handler(text); } }); + + /** + * Contenteditable hack in order to allow right-click paste + */ + on(term.element, 'contextmenu', function (ev) { + console.log('hey'); + term.element.contentEditable = true; + }); + + on(term.element, 'mouseup', function (ev) { + term.element.contentEditable = false; + }); }; @@ -447,16 +460,6 @@ */ Terminal.bindKeys = function(term) { on(term.element, 'keydown', function(ev) { - /* - * Clear all selections if the key pressed was not among - * Shift, Alt, Cmd, Ctrl. - */ - var selection = window.getSelection(); - if (selection.toString() != '') { - if ([16, 17, 18, 91].indexOf(ev.keyCode) == -1) { - selection.removeAllRanges(); - } - } term.keyDown(ev); }, true); @@ -471,7 +474,7 @@ */ Terminal.bindCopy = function(term) { on(term.element, 'copy', function(ev) { - return; //temporary + return; }); }; From 73205158200f034dd66e9bf163137d0e25585e39 Mon Sep 17 00:00:00 2001 From: Paris Date: Thu, 2 Jun 2016 13:08:06 +0300 Subject: [PATCH 08/13] Tiny styling update in row line height --- src/xterm.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xterm.css b/src/xterm.css index 5cfa6ff7..055c0fc0 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -2123,7 +2123,7 @@ * in order to allow child elements to adjust. */ .terminal .xterm-rows > div { - line-height: 1.2; + line-height: 1.3; } /** From f3bd6145a5c95cdb453fbc85401f5355079138ed Mon Sep 17 00:00:00 2001 From: Paris Date: Thu, 2 Jun 2016 13:31:38 +0300 Subject: [PATCH 09/13] Vanish selection when a key with data is being pressed --- src/xterm.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index e100e7c2..a1bdd20c 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -421,7 +421,6 @@ /** * Initialize default behavior */ - Terminal.prototype.initGlobal = function() { Terminal.bindPaste(this); Terminal.bindKeys(this); @@ -442,10 +441,11 @@ }); /** - * Contenteditable hack in order to allow right-click paste + * Contenteditable hack in order to allow right-click paste. + * Set contentEditable to true when right clicking and then set it back to false on mouse up + * in order to hide the contentEditable cursor. */ on(term.element, 'contextmenu', function (ev) { - console.log('hey'); term.element.contentEditable = true; }); @@ -514,7 +514,7 @@ this.children.push(row); return row; - } + }; /* @@ -2447,6 +2447,18 @@ key = String.fromCharCode(key); + /** + * When a key is pressed and a character is sent to the terminal, then clear any text + * selected in the terminal. + */ + if (key) { + var selectionBaseNode = window.getSelection().baseNode; + + if (selectionBaseNode && (this.element.contains(selectionBaseNode.parentElement))) { + window.getSelection().removeAllRanges(); + } + } + this.emit('keypress', key, ev); this.emit('key', key, ev); this.showCursor(); From 647216876b646d7b0f3719eeab8ceb3d46d13564 Mon Sep 17 00:00:00 2001 From: Paris Date: Fri, 3 Jun 2016 11:04:46 +0300 Subject: [PATCH 10/13] Add a few hacks for better paste handling across browsers --- src/xterm.js | 77 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index a1bdd20c..d0ce8cc3 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -428,8 +428,36 @@ Terminal.bindDrop(this); }; + /** + * 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(); + } + }; + + /** + * This function temporarily enables (leases) the contentEditable value of the terminal, which + * should be set back to false within 5 seconds at most. + */ + Terminal.prototype.leaseContentEditable = function (ms, callback) { + var term = this; + + term.element.contentEditable = true; + setTimeout(function () { + term.element.contentEditable = false; + if (typeof callback == 'function') { + callback.call(term); + } + }, ms || 5000); + }; + /** - * Bind to paste event and allow right-click pasting. + * Bind to paste event and allow both keyboard and right-click pasting, without having the + * contentEditable value set to true. */ Terminal.bindPaste = function(term) { on(term.element, 'paste', function(ev) { @@ -437,20 +465,47 @@ var text = ev.clipboardData.getData('text/plain'); term.emit('paste', text, ev); term.handler(text); + /** + * Cancel the paste event, or else things will be pasted twice: + * 1. by the terminal handler + * 2. by the browser, because of the contentEditable value being true + */ + term.cancel(ev, true); + + /** + * After the paste event is completed, always set the contentEditable value to false. + */ + term.element.contentEditable = false; } }); /** - * Contenteditable hack in order to allow right-click paste. - * Set contentEditable to true when right clicking and then set it back to false on mouse up - * in order to hide the contentEditable cursor. + * Hack pasting with keyboard, in order to make it work without contentEditable. + * When a user types Ctrl + Shift + V or Cmd + V on a Mac, lease the contentEditable value + * as true. */ - on(term.element, 'contextmenu', function (ev) { - term.element.contentEditable = true; + on(term.element, 'keydown', function (ev) { + /** + * If on a Mac, lease the contentEditable value temporarily, when the user presses + * the Cmd button, in order to cope with some sync issues on Safari. + */ + if (term.isMac && ev.keyCode == 91) { + term.leaseContentEditable(1000); + } + + if (ev.keyCode == 86) { // keyCode 96 corresponds to "v" + if (term.isMac && ev.metaKey) { + term.leaseContentEditable(); + } + } }); - on(term.element, 'mouseup', function (ev) { - term.element.contentEditable = false; + /** + * Hack pasting with right-click in order to allow right-click paste, by leasing the + * contentEditable value as true. + */ + on(term.element, 'contextmenu', function (ev) { + term.leaseContentEditable(); }); }; @@ -2452,11 +2507,7 @@ * selected in the terminal. */ if (key) { - var selectionBaseNode = window.getSelection().baseNode; - - if (selectionBaseNode && (this.element.contains(selectionBaseNode.parentElement))) { - window.getSelection().removeAllRanges(); - } + this.clearSelection(); } this.emit('keypress', key, ev); From c02cc8449856f5952eae02d2346c3e2c298cb408 Mon Sep 17 00:00:00 2001 From: Paris Date: Sun, 5 Jun 2016 06:35:33 +0300 Subject: [PATCH 11/13] Fix cut and copy events - Do not actually cut on cut - Strip trailing whitespaces on copy (fix #66) --- src/xterm.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index d0ce8cc3..fc8fd382 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -422,9 +422,10 @@ * Initialize default behavior */ Terminal.prototype.initGlobal = function() { - Terminal.bindPaste(this); Terminal.bindKeys(this); + Terminal.bindPaste(this); Terminal.bindCopy(this); + Terminal.bindCut(this); Terminal.bindDrop(this); }; @@ -524,12 +525,26 @@ }; - /* - * Bind copy event + /** + * Bind copy event. Stript trailing whitespaces from selection. */ Terminal.bindCopy = function(term) { on(term.element, 'copy', function(ev) { - return; + var selectedText = window.getSelection().toString(), + copiedText = selectedText.split('\n').map(function (element) { + return element.replace(/\s+$/g, ''); + }).join('\n'); + ev.clipboardData.setData('text/plain', copiedText); + ev.preventDefault(); + }); + }; + + /** + * Cancel the cut event completely + */ + Terminal.bindCut = function(term) { + on(term.element, 'cut', function (ev) { + ev.preventDefault(); }); }; From 8754f8d895bfd889fa7c5cfee6a4aad049ebacce Mon Sep 17 00:00:00 2001 From: Paris Date: Sun, 5 Jun 2016 06:58:09 +0300 Subject: [PATCH 12/13] Fix focus and blur events --- src/xterm.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/xterm.js b/src/xterm.js index fc8fd382..ebed287b 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -401,6 +401,7 @@ this.showCursor(); this.element.focus(); + this.emit('focus', {terminal: this}); }; Terminal.prototype.blur = function() { @@ -416,6 +417,7 @@ this.send('\x1b[O'); } Terminal.focus = null; + this.emit('blur', {terminal: this}); }; /** From 0fd0f45d8b05110b3fba7cbd20e4dae4d7ff0875 Mon Sep 17 00:00:00 2001 From: Paris Date: Mon, 6 Jun 2016 19:38:13 +0300 Subject: [PATCH 13/13] Fix indentation --- demo/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/main.js b/demo/main.js index 844d5791..7bca13d4 100644 --- a/demo/main.js +++ b/demo/main.js @@ -34,5 +34,5 @@ term.on('key', function (key, ev) { }); term.on('paste', function (data, ev) { - term.write(data); + term.write(data); });