Merge pull request #78 from sourcelair/drop-hidden-textarea

Drop hidden textarea for input handling
This commit is contained in:
Paris Kasidiaris
2016-06-07 09:36:44 +03:00
5 changed files with 149 additions and 522 deletions
+1
View File
@@ -0,0 +1 @@
web: ./bin/server
Executable
+4
View File
@@ -0,0 +1,4 @@
#! /usr/bin/env sh
# Development server for xterm.js
browser-sync start --server --port 8000 --files="**" --no-ui --index index.html --directory
+8 -2
View File
@@ -1,6 +1,6 @@
var terminalContainer = document.getElementById('terminal-container'),
term = new Terminal(),
shellprompt = '> ';
shellprompt = '$ ';
term.open(terminalContainer);
term.fit();
@@ -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();
@@ -30,3 +32,7 @@ term.on('key', function (key, ev) {
term.write(key);
}
});
term.on('paste', function (data, ev) {
term.write(data);
});
+1 -18
View File
@@ -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
*/
@@ -2140,7 +2123,7 @@
* in order to allow child elements to adjust.
*/
.terminal .xterm-rows > div {
line-height: 1.2;
line-height: 1.3;
}
/**
+135 -502
View File
@@ -105,10 +105,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);
@@ -390,7 +391,7 @@
* Focused Terminal
*/
Terminal.prototype.focus = function() {
if (document.activeElement === this.textarea) {
if (document.activeElement === this.element) {
return;
}
@@ -399,8 +400,8 @@
}
this.showCursor();
this.textarea.focus();
Terminal.focus = this;
this.element.focus();
this.emit('focus', {terminal: this});
};
Terminal.prototype.blur = function() {
@@ -410,36 +411,104 @@
this.cursorState = 0;
this.refresh(this.y, this.y);
this.textarea.blur();
this.element.blur();
if (this.sendFocus) {
this.send('\x1b[O');
}
Terminal.focus = null;
this.emit('blur', {terminal: this});
};
/**
* 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);
};
/**
* 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
* 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.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.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;
}
term.textarea.value = '';
return term.cancel(ev);
});
/**
* 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, '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();
}
}
});
/**
* 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();
});
};
@@ -449,42 +518,57 @@
*/
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);
};
/*
* Bind copy event
/**
* Bind copy event. Stript trailing whitespaces from selection.
*/
Terminal.bindCopy = function(term) {
on(term.element, 'copy', function(ev) {
return; //temporary
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();
});
};
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);
});
};
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);
});
};
@@ -502,7 +586,7 @@
this.children.push(row);
return row;
}
};
/*
@@ -542,6 +626,7 @@
this.element.classList.add('xterm');
this.element.classList.add('xterm-theme-' + this.theme);
this.element.setAttribute('tabindex', 0);
this.element.spellcheck = 'false';
/*
* Create the container that will hold the lines of the terminal and then
@@ -552,27 +637,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();
}
@@ -2425,7 +2489,7 @@
this.showCursor();
this.handler(key);
return this.cancel(ev);
return this.cancel(ev, true);
};
Terminal.prototype.setgLevel = function(g) {
@@ -2443,8 +2507,6 @@
Terminal.prototype.keyPress = function(ev) {
var key;
this.cancel(ev);
if (ev.charCode) {
key = ev.charCode;
} else if (ev.which == null) {
@@ -2461,11 +2523,21 @@
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) {
this.clearSelection();
}
this.emit('keypress', key, ev);
this.emit('key', key, ev);
this.showCursor();
this.handler(key);
this.cancel(ev, true);
return false;
};
@@ -4159,6 +4231,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.
@@ -4178,446 +4251,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();
} 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
*/