From 0d60718f9862ba0c039a88187b3397d8ab41ca90 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 13 Aug 2013 19:04:08 -0500 Subject: [PATCH 01/10] add selection mode. --- src/term.js | 240 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 239 insertions(+), 1 deletion(-) diff --git a/src/term.js b/src/term.js index 8014e62b..39df26b4 100644 --- a/src/term.js +++ b/src/term.js @@ -1119,7 +1119,7 @@ Terminal.prototype.refresh = function(start, end) { if (y === this.y && this.cursorState - && this.ydisp === this.ybase + && (this.ydisp === this.ybase || this.selectMode) && !this.cursorHidden) { x = this.x; } else { @@ -2477,6 +2477,12 @@ Terminal.prototype.keyDown = function(ev) { if (this.allowKeyPaste && ev.keyCode === 86) { return; } + if (String.fromCharCode(ev.keyCode).toLowerCase() === 'a') { + if (!this.waitingForSelect) { + this.waitingForSelect = true; + return cancel(ev); + } + } key = String.fromCharCode(ev.keyCode - 64); } else if (ev.keyCode === 32) { // NUL @@ -2506,6 +2512,13 @@ Terminal.prototype.keyDown = function(ev) { break; } + //this.waitingForSelect = false; + + if (key && this.selectMode) { + this.keySelect(ev, key); + return cancel(ev); + } + this.emit('keydown', ev); // if (this.emit('keydown', key, ev) === false) { this.showCursor(); cancel(ev); return; } @@ -2522,6 +2535,214 @@ Terminal.prototype.keyDown = function(ev) { return true; }; +Terminal.prototype.enterSelect = function(ev, key) { + this._savedSelect = { + x: this.x, + y: this.y, + ydisp: this.ydisp, + ybase: this.ybase, + cursorHidden: this.cursorHidden, + lines: copyBuffer(this.lines), + write: this.write + }; + this.write = function() {}; + this.cursorHidden = false; + this.selectMode = true; + this.visualMode = false; +}; + +Terminal.prototype.leaveSelect = function(ev, key) { + this.x = this._savedSelect.x; + this.y = this._savedSelect.y; + this.ydisp = this._savedSelect.ydisp; + this.ybase = this._savedSelect.ybase; + this.cursorHidden = this._savedSelect.cursorHidden; + this.lines = this._savedSelect.lines; + this.write = this._savedSelect.write; + delete this._savedSelect; + this.selectMode = false; + this.visualMode = false; + this.refresh(0, this.rows - 1); +}; + +Terminal.prototype.keySelect = function(ev, key) { + console.log(JSON.stringify(key || null)); + + this.showCursor(); + + if (key === '\x04') { // ctrl-d + var y = this.ydisp + this.y; + this.scrollDisp(-(this.rows - 1)); + if (this.visualMode) { + this.selectText(y, this.ydisp + this.y); + } + } else if (key === '\x15') { // ctrl-u + var y = this.ydisp + this.y; + this.scrollDisp(this.rows - 1); + if (this.visualMode) { + this.selectText(this.ydisp + this.y, y); + } + } else if (key === 'k') { + var y = this.ydisp + this.y; + this.y--; + if (this.y < 0) { + this.y = 0; + this.scrollDisp(-1); + } + if (this.visualMode) { + this.selectText(this.ydisp + this.y, y); + } else { + this.refresh(this.y, this.y + 1); + } + } else if (key === 'j') { + 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(y, this.ydisp + this.y); + } else { + this.refresh(this.y - 1, this.y); + } + } else if (key === 'h') { + this.x--; + if (this.x < 0) { + this.x = 0; + } + this.refresh(this.y, this.y); + } else if (key === 'l') { + this.x++; + if (this.x >= this.cols) { + this.x = this.cols - 1; + } + this.refresh(this.y, this.y); + } else if (key === 'v') { + if (!this.visualMode) { + this._savedSelect.preVisual = copyBuffer(this.lines); + this.selectText(this.ydisp + this.y, this.ydisp + this.y); + this.visualMode = true; + } + } else if (key === 'y') { + this.emit('copy', this.grabText(this._selected.from, this._selected.to)); + this.lines = this._savedSelect.preVisual; + delete this._savedSelect.preVisual; + delete this._selected; + this.visualMode = false; + this.refresh(0, this.rows - 1); + } else if (key === 'q') { + if (this.visualMode) { + this.lines = this._savedSelect.preVisual; + delete this._savedSelect.preVisual; + delete this._selected; + this.visualMode = false; + this.refresh(0, this.rows - 1); + } else { + this.leaveSelect(); + } + } +}; + +function copyBuffer(lines) { + var 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.selectText = function(from, to) { + var ofrom, oto, tmp, x, y; + + if (this._selected) { + ofrom = this._selected.from; + oto = this._selected.to; + + if (oto < ofrom) { + tmp = oto; + oto = ofrom; + ofrom = tmp; + } + + for (y = ofrom; y <= oto; y++) { + for (x = 0; x < this.cols; x++) { + if (this.lines[y][x].old != null) { + this.lines[y][x][0] = this.lines[y][x].old; + delete this.lines[y][x].old; + } + } + } + + from = this._selected.from; + } + + from = Math.max(from, 0); + from = Math.min(from, this.ydisp + this.rows - 1); + + to = Math.max(to, 0); + to = Math.min(to, this.ydisp + this.rows - 1); + + this._selected = { from: from, to: to }; + + if (to < from) { + tmp = to; + to = from; + from = tmp; + } + + for (y = from; y <= to; y++) { + for (x = 0; x < this.cols; 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; + } + } + + from = from - this.ydisp; + to = to - this.ydisp; + + from = Math.max(from, 0); + from = Math.min(from, this.rows - 1); + + to = Math.max(to, 0); + to = Math.min(to, this.rows - 1); + + console.log([from, to]); + + //this.refresh(from, to); + this.refresh(0, this.rows - 1); +}; + +Terminal.prototype.grabText = function(from, to) { + var out = '' + , buf = '' + , ch + , x + , y; + + for (y = from; y <= to; y++) { + for (x = 0; x < this.cols; x++) { + ch = this.lines[y][x][1]; + if (ch === ' ') { + buf += ch; + continue; + } + if (buf) { + out += buf; + } + out += ch; + } + buf = ''; + out += '\n'; + } + + return out; +}; + Terminal.prototype.setgLevel = function(g) { this.glevel = g; this.charset = this.charsets[g]; @@ -2553,6 +2774,23 @@ Terminal.prototype.keyPress = function(ev) { key = String.fromCharCode(key); + if (this.waitingForSelect && key === '[') { + this.enterSelect(); + this.waitingForSelect = false; + return false; + } + //this.waitingForSelect = false; + + if (this.selectMode) { + this.keySelect(ev, key); + return false; + } + + if (this.visualMode) { + this.keyDownVisual(ev, key); + return false; + } + this.emit('keypress', key, ev); // if (this.emit('keypress', key, ev) === false) { this.showCursor(); return; } this.emit('key', key, ev); From ffcbfdecd8fd4bc99a4f57f68d7a36c630036dba Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 13 Aug 2013 20:59:56 -0500 Subject: [PATCH 02/10] more selection mode work. --- src/term.js | 773 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 702 insertions(+), 71 deletions(-) diff --git a/src/term.js b/src/term.js index 39df26b4..5cc65d40 100644 --- a/src/term.js +++ b/src/term.js @@ -2477,9 +2477,11 @@ Terminal.prototype.keyDown = function(ev) { if (this.allowKeyPaste && ev.keyCode === 86) { return; } - if (String.fromCharCode(ev.keyCode).toLowerCase() === 'a') { - if (!this.waitingForSelect) { - this.waitingForSelect = true; + // Ctrl-A + //if (this.screenKeys && ev.keyCode === 65) { + if (ev.keyCode === 65) { + if (!this.prefixMode) { + this.prefixMode = true; return cancel(ev); } } @@ -2512,7 +2514,7 @@ Terminal.prototype.keyDown = function(ev) { break; } - //this.waitingForSelect = false; + //this.prefixMode = false; if (key && this.selectMode) { this.keySelect(ev, key); @@ -2566,23 +2568,61 @@ Terminal.prototype.leaveSelect = function(ev, key) { }; Terminal.prototype.keySelect = function(ev, key) { - console.log(JSON.stringify(key || null)); - 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; - this.scrollDisp(-(this.rows - 1)); - if (this.visualMode) { - this.selectText(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); } - } else if (key === '\x15') { // ctrl-u + if (this.visualMode) { + this.selectText(this.x, this.x, this.ydisp + this.y, 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.ydisp + this.y, y); + this.selectText(this.x, this.x, this.ydisp + this.y, y); } - } else if (key === 'k') { + 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') { var y = this.ydisp + this.y; this.y--; if (this.y < 0) { @@ -2590,11 +2630,14 @@ Terminal.prototype.keySelect = function(ev, key) { this.scrollDisp(-1); } if (this.visualMode) { - this.selectText(this.ydisp + this.y, y); + this.selectText(this.x, this.x, this.ydisp + this.y, y); } else { this.refresh(this.y, this.y + 1); } - } else if (key === 'j') { + return; + } + + if (key === 'j') { var y = this.ydisp + this.y; this.y++; if (this.y >= this.rows) { @@ -2602,36 +2645,70 @@ Terminal.prototype.keySelect = function(ev, key) { this.scrollDisp(1); } if (this.visualMode) { - this.selectText(y, this.ydisp + this.y); + this.selectText(this.x, this.x, y, this.ydisp + this.y); } else { this.refresh(this.y - 1, this.y); } - } else if (key === 'h') { + return; + } + + if (key === 'h') { + var x = this.x; this.x--; if (this.x < 0) { this.x = 0; } - this.refresh(this.y, this.y); - } else if (key === 'l') { + if (this.visualMode) { + this.selectText(this.x, x, this.ydisp + this.y, this.ydisp + this.y); + } else { + this.refresh(this.y, this.y); + } + return; + } + + if (key === 'l') { + var x = this.x; this.x++; if (this.x >= this.cols) { this.x = this.cols - 1; } - this.refresh(this.y, this.y); - } else if (key === 'v') { + 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') { if (!this.visualMode) { this._savedSelect.preVisual = copyBuffer(this.lines); - this.selectText(this.ydisp + this.y, this.ydisp + this.y); + this.selectText(this.x, this.x, this.ydisp + this.y, this.ydisp + this.y); this.visualMode = true; + } else { + this.lines = this._savedSelect.preVisual; + delete this._savedSelect.preVisual; + delete this._selected; + this.visualMode = false; + this.refresh(0, this.rows - 1); } - } else if (key === 'y') { - this.emit('copy', this.grabText(this._selected.from, this._selected.to)); + return; + } + + if (key === 'y') { + var text = this.grabText( + this._selected.x1, this._selected.x2, + this._selected.y1, this._selected.y2); + this.emit('copy', text); this.lines = this._savedSelect.preVisual; delete this._savedSelect.preVisual; delete this._selected; this.visualMode = false; this.refresh(0, this.rows - 1); - } else if (key === 'q') { + return; + } + + if (key === 'q') { if (this.visualMode) { this.lines = this._savedSelect.preVisual; delete this._savedSelect.preVisual; @@ -2641,6 +2718,486 @@ Terminal.prototype.keySelect = function(ev, key) { } 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 l = this.lines[yb + y]; + var saw_space = x > 0 && l[x][1] > ' ' && l[x - 1][1] > ' '; + while (x >= 0) { + if (l[x][1] <= ' ') { + if (saw_space && (x + 1 < this.cols && l[x + 1][1] > ' ')) { + x++; + break; + } else { + saw_space = true; + } + } + x--; + } + if (x < 0) x = 0; + if (x == 0 && (l[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(this.x, ox, this.ydisp + this.y, oy + oyd); + } + 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 l = this.lines[yb + y]; + while (x < this.cols) { + if (l[x][1] <= ' ') { + x++; + } else { + break; + } + } + while (x < this.cols) { + if (l[x][1] <= ' ') { + if (x - 1 >= 0 && l[x - 1][1] > ' ') { + x--; + break; + } + } + x++; + } + if (x >= this.cols) x = this.cols - 1; + if (x == this.cols - 1 && l[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 l = this.lines[this.ydisp + this.y]; + var x = 0; + while (x < this.cols) { + if (l[x][1] > ' ') { + break; + } + x++; + } + if (x >= this.cols) x = this.cols - 1; + this.x = x; + } + + if (this.visualMode) { + this.selectText(this.x, ox, this.ydisp + this.y, this.ydisp + this.y); + } else { + this.refresh(this.y, this.y); + } + return; + } + + if (key === '$') { + var ox = this.x; + var l = this.lines[this.ydisp + this.y]; + var x = this.cols - 1; + while (x >= 0) { + if (l[x][1] > ' ') { + 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); + if (this.visualMode) { + this.selectText(this.x, ox, this.ydisp + this.y, oy + oyd); + } + } 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 === '{' || 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 (key === '{') { + if (this.visualMode) { + this.selectText(this.x, ox, this.ydisp + this.y, oy + oyd); + } + } else if (key === '}') { + if (this.visualMode) { + this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); + } + } + return; + } + + if (key === '/' || key === '?') { + this.entry = ''; + this.searchMode = true; + this.searchDown = key === '/'; + this._savedSelect.preSearch = copyBuffer(this.lines); + this._savedSelect.preSearchX = this.x; + this._savedSelect.preSearchY = this.y; + this.entryPrefix = 'Search: '; + 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); + return; + } +}; + +Terminal.prototype.keySearch = function(ev, key) { + if (this.searchMode || key === 'n' || key === 'N') { + if (key === '\x1b') { + this.searchMode = false; + + if (this._savedSelect.preSearch) { + this.lines = this._savedSelect.preSearch; + this.x = this._savedSelect.preSearchX; + this.y = this._savedSelect.preSearchY; + delete this._savedSelect.preSearch; + delete this._savedSelect.preSearchX; + delete this._savedSelect.preSearchY; + } + + this.refresh(this.rows - 1, this.rows - 1); + return; + } + + if (key === '\r' || (!this.searchMode && (key === 'n' || key === 'N'))) { + this.searchMode = false; + + if (this._savedSelect.preSearch) { + this.lines = this._savedSelect.preSearch; + this.x = this._savedSelect.preSearchX; + this.y = this._savedSelect.preSearchY; + delete this._savedSelect.preSearch; + delete this._savedSelect.preSearchX; + delete this._savedSelect.preSearchY; + } + + 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; + // set_message("Search wrapped. Continuing at TOP."); + wrapped = true; + y = 0; + } + } else { + y--; + if (y < 0) { + if (wrapped) break; + // set_message("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 (!wrapped) UPDATE_SCROLL; + + if (this.visualMode) { + if (this.y < oy || this.ydisp < oyd) { + this.selectText(this.x, ox, this.ydisp + this.y, oy + oyd); + } else { + this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); + } + } + return; + } else { + // set_message("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) { + 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; + } } }; @@ -2655,77 +3212,154 @@ function copyBuffer(lines) { return out; } -Terminal.prototype.selectText = function(from, to) { - var ofrom, oto, tmp, x, y; +Terminal.prototype.selectText = function(x1, x2, y1, y2) { + var ox1 + , ox2 + , oy1 + , oy2 + , tmp + , x + , y + , xl + , attr; if (this._selected) { - ofrom = this._selected.from; - oto = this._selected.to; + ox1 = this._selected.x1; + ox2 = this._selected.x2; + oy1 = this._selected.y1; + oy2 = this._selected.y2; - if (oto < ofrom) { - tmp = oto; - oto = ofrom; - ofrom = tmp; + if (oy2 < oy1) { + tmp = ox2; + ox2 = ox1; + ox1 = tmp; + tmp = oy2; + oy2 = oy1; + oy1 = tmp; } - for (y = ofrom; y <= oto; y++) { - for (x = 0; x < this.cols; x++) { + if (ox2 < ox1 && oy1 === oy2) { + tmp = ox2; + ox2 = ox1; + ox1 = tmp; + } + + for (y = oy1; y <= oy2; y++) { + x = 0; + xl = this.cols; + if (y === oy1) { + x = ox1; + } + if (y === oy2) { + xl = ox2; + } + for (; x < this.cols; x++) { if (this.lines[y][x].old != null) { - this.lines[y][x][0] = this.lines[y][x].old; + //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]]; } } } - from = this._selected.from; + y1 = this._selected.y1; + x1 = this._selected.x1; } - from = Math.max(from, 0); - from = Math.min(from, this.ydisp + this.rows - 1); + y1 = Math.max(y1, 0); + y1 = Math.min(y1, this.ydisp + this.rows - 1); - to = Math.max(to, 0); - to = Math.min(to, this.ydisp + this.rows - 1); + y2 = Math.max(y2, 0); + y2 = Math.min(y2, this.ydisp + this.rows - 1); - this._selected = { from: from, to: to }; + this._selected = { x1: x1, x2: x2, y1: y1, y2: y2 }; - if (to < from) { - tmp = to; - to = from; - from = tmp; + if (y2 < y1) { + tmp = x2; + x2 = x1; + x1 = tmp; + tmp = y2; + y2 = y1; + y1 = tmp; } - for (y = from; y <= to; y++) { - for (x = 0; x < this.cols; 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; + if (x2 < x1 && y1 === y2) { + tmp = x2; + x2 = x1; + x1 = tmp; + } + + for (y = y1; y <= y2; y++) { + x = 0; + xl = this.cols; + 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; } } - from = from - this.ydisp; - to = to - this.ydisp; + y1 = y1 - this.ydisp; + y2 = y2 - this.ydisp; - from = Math.max(from, 0); - from = Math.min(from, this.rows - 1); + y1 = Math.max(y1, 0); + y1 = Math.min(y1, this.rows - 1); - to = Math.max(to, 0); - to = Math.min(to, this.rows - 1); + y2 = Math.max(y2, 0); + y2 = Math.min(y2, this.rows - 1); - console.log([from, to]); - - //this.refresh(from, to); + //this.refresh(y1, y2); this.refresh(0, this.rows - 1); }; -Terminal.prototype.grabText = function(from, to) { +Terminal.prototype.grabText = function(x1, x2, y1, y2) { var out = '' , buf = '' , ch , x - , y; + , y + , xl + , tmp; - for (y = from; y <= to; y++) { - for (x = 0; x < this.cols; x++) { + 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; + if (y === y1) { + x = x1; + } + if (y === y2) { + xl = x2; + } + for (; x < xl; x++) { ch = this.lines[y][x][1]; if (ch === ' ') { buf += ch; @@ -2733,8 +3367,10 @@ Terminal.prototype.grabText = function(from, to) { } if (buf) { out += buf; + buf = ''; } out += ch; + if (isWide(ch)) x++; } buf = ''; out += '\n'; @@ -2774,23 +3410,18 @@ Terminal.prototype.keyPress = function(ev) { key = String.fromCharCode(key); - if (this.waitingForSelect && key === '[') { + if (this.prefixMode && key === '[') { this.enterSelect(); - this.waitingForSelect = false; + this.prefixMode = false; return false; } - //this.waitingForSelect = false; + //this.prefixMode = false; if (this.selectMode) { this.keySelect(ev, key); return false; } - if (this.visualMode) { - this.keyDownVisual(ev, key); - return false; - } - this.emit('keypress', key, ev); // if (this.emit('keypress', key, ev) === false) { this.showCursor(); return; } this.emit('key', key, ev); From cc7f4d0d7cf6f9d05916bd4127ac49b875663915 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 14 Aug 2013 16:57:16 -0500 Subject: [PATCH 03/10] add clipboard support for selections. --- src/term.js | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/term.js b/src/term.js index 5cc65d40..a41b672f 100644 --- a/src/term.js +++ b/src/term.js @@ -458,6 +458,9 @@ Terminal.prototype.initGlobal = function() { Terminal.bindKeys(document); + //if (this.screenKeys) + Terminal.bindCopy(document); + if (this.isIpad) { Terminal.fixIpad(document); } @@ -540,6 +543,51 @@ 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._copyTextarea(); + 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); + }); +}; + /** * Fix iPad - no idea if this works */ @@ -2517,6 +2565,10 @@ Terminal.prototype.keyDown = function(ev) { //this.prefixMode = false; if (key && this.selectMode) { + // Ctrl-C - copy + if (key === '\x03') { + return; + } this.keySelect(ev, key); return cancel(ev); } @@ -2699,7 +2751,7 @@ Terminal.prototype.keySelect = function(ev, key) { var text = this.grabText( this._selected.x1, this._selected.x2, this._selected.y1, this._selected.y2); - this.emit('copy', text); + this.copyText(text); this.lines = this._savedSelect.preVisual; delete this._savedSelect.preVisual; delete this._selected; @@ -3043,6 +3095,47 @@ Terminal.prototype.keySelect = function(ev, key) { } }; +Terminal.prototype._copyTextarea = function(text) { + var textarea = this._textarea + , document = this.document; + + if (!textarea) { + textarea = document.createElement('textarea'); + textarea.style.position = 'absolute'; + textarea.style.left = '-32000px'; + textarea.style.top = '-32000px'; + textarea.style.opacity = '0'; + textarea.style.backgroundColor = 'transparent'; + textarea.style.borderStyle = 'none'; + textarea.style.outlineStyle = 'none'; + + document.getElementsByTagName('body')[0].appendChild(textarea); + + this._textarea = 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._copyTextarea(); + + 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.keySearch = function(ev, key) { if (this.searchMode || key === 'n' || key === 'N') { if (key === '\x1b') { From da887cf4a56a6cef43c836f226e1dc43caeb10a7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 14 Aug 2013 17:29:04 -0500 Subject: [PATCH 04/10] more prefix commands. --- src/term.js | 52 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/term.js b/src/term.js index a41b672f..74904537 100644 --- a/src/term.js +++ b/src/term.js @@ -2526,12 +2526,19 @@ Terminal.prototype.keyDown = function(ev) { return; } // Ctrl-A - //if (this.screenKeys && ev.keyCode === 65) { - if (ev.keyCode === 65) { - if (!this.prefixMode) { - this.prefixMode = true; - return cancel(ev); - } + //if (this.screenKeys) + if (!this.prefixMode && !this.selectMode && ev.keyCode === 65) { + this.prefixMode = true; + return cancel(ev); + } + // Ctrl-V + if (this.prefixMode && ev.keyCode === 86) { + this.prefixMode = false; + return; + } + // Ctrl-C + if (this.selectMode && ev.keyCode === 67) { + return; } key = String.fromCharCode(ev.keyCode - 64); } else if (ev.keyCode === 32) { @@ -2562,13 +2569,12 @@ Terminal.prototype.keyDown = function(ev) { break; } - //this.prefixMode = false; + if (key && this.prefixMode) { + this.prefixMode = false; + return cancel(ev); + } if (key && this.selectMode) { - // Ctrl-C - copy - if (key === '\x03') { - return; - } this.keySelect(ev, key); return cancel(ev); } @@ -2600,9 +2606,10 @@ Terminal.prototype.enterSelect = function(ev, key) { write: this.write }; this.write = function() {}; - this.cursorHidden = false; this.selectMode = true; this.visualMode = false; + this.cursorHidden = false; + this.refresh(this.y, this.y); }; Terminal.prototype.leaveSelect = function(ev, key) { @@ -3503,12 +3510,27 @@ Terminal.prototype.keyPress = function(ev) { key = String.fromCharCode(key); - if (this.prefixMode && key === '[') { - this.enterSelect(); + if (this.prefixMode) { + 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') { + this.emit('request tab', +key); + } else if (key === 'n') { + this.emit('request tab next'); + } else if (key === 'P') { + this.emit('request tab previous'); + } else if (key === ':') { + // this.enterCommand(); + } else if (key === '[') { + this.enterSelect(); + } this.prefixMode = false; return false; } - //this.prefixMode = false; if (this.selectMode) { this.keySelect(ev, key); From c5e8a7ab624dee4a88e795f50fbcfd56e0c12236 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 14 Aug 2013 18:30:26 -0500 Subject: [PATCH 05/10] add HML keys. fix search text selection. --- src/term.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/term.js b/src/term.js index 74904537..fae59eeb 100644 --- a/src/term.js +++ b/src/term.js @@ -2987,6 +2987,25 @@ Terminal.prototype.keySelect = function(ev, key) { 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; @@ -3252,11 +3271,7 @@ Terminal.prototype.keySearch = function(ev, key) { // if (!wrapped) UPDATE_SCROLL; if (this.visualMode) { - if (this.y < oy || this.ydisp < oyd) { - this.selectText(this.x, ox, this.ydisp + this.y, oy + oyd); - } else { - this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); - } + this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); } return; } else { From ee7498dff5fe6d8398bf7b180d62db47a31e2a20 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 14 Aug 2013 18:36:31 -0500 Subject: [PATCH 06/10] convert double equals to triple equals. --- src/term.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/term.js b/src/term.js index fae59eeb..1a0882f1 100644 --- a/src/term.js +++ b/src/term.js @@ -2801,7 +2801,7 @@ Terminal.prototype.keySelect = function(ev, key) { x++; } if (x >= this.cols) x = this.cols - 1; - if (x == this.cols - 1 && line[x][1] <= ' ') { + if (x === this.cols - 1 && line[x][1] <= ' ') { x = 0; if (++y >= this.rows) { y--; @@ -2849,7 +2849,7 @@ Terminal.prototype.keySelect = function(ev, key) { x--; } if (x < 0) x = 0; - if (x == 0 && (l[x][1] <= ' ' || !saw_space)) { + if (x === 0 && (l[x][1] <= ' ' || !saw_space)) { x = this.cols - 1; if (--y < 0) { y++; @@ -2898,7 +2898,7 @@ Terminal.prototype.keySelect = function(ev, key) { x++; } if (x >= this.cols) x = this.cols - 1; - if (x == this.cols - 1 && l[x][1] <= ' ') { + if (x === this.cols - 1 && l[x][1] <= ' ') { x = 0; if (++y >= this.rows) { y--; @@ -3036,17 +3036,17 @@ Terminal.prototype.keySelect = function(ev, key) { for (i = 0; i < this.cols; i++) { if (line[i][1] > ' ') { - if (first_is_space == -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) { + } else if (i === this.cols - 1) { + if (first_is_space === -1) { first_is_space = 1; - } else if (first_is_space == 0) { + } else if (first_is_space === 0) { found = true; - } else if (first_is_space == 1) { + } else if (first_is_space === 1) { if (saw_full) found = true; } break; @@ -3219,9 +3219,9 @@ Terminal.prototype.keySearch = function(ev, key) { while (x < this.cols) { for (i = 0; i < entry.length; i++) { if (x + i >= this.cols) break; - if (line[x + i][1] != entry[i]) { + if (line[x + i][1] !== entry[i]) { break; - } else if (line[x + i][1] == entry[i] && i == entry.length - 1) { + } else if (line[x + i][1] === entry[i] && i === entry.length - 1) { found = true; break; } @@ -3283,7 +3283,7 @@ Terminal.prototype.keySearch = function(ev, key) { } if (key === '\b' || key === '\x7f') { - if (this.entry.length == 0) return; + 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; From 9d1a14d021192e4417a5c6527b5093b125c7555b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 14 Aug 2013 18:38:05 -0500 Subject: [PATCH 07/10] minor. rename variable. fix grabText. search keys. --- src/term.js | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/term.js b/src/term.js index 1a0882f1..874fabb6 100644 --- a/src/term.js +++ b/src/term.js @@ -2835,11 +2835,11 @@ Terminal.prototype.keySelect = function(ev, key) { var yb = this.ydisp; for (;;) { - var l = this.lines[yb + y]; - var saw_space = x > 0 && l[x][1] > ' ' && l[x - 1][1] > ' '; + var line = this.lines[yb + y]; + var saw_space = x > 0 && line[x][1] > ' ' && line[x - 1][1] > ' '; while (x >= 0) { - if (l[x][1] <= ' ') { - if (saw_space && (x + 1 < this.cols && l[x + 1][1] > ' ')) { + if (line[x][1] <= ' ') { + if (saw_space && (x + 1 < this.cols && line[x + 1][1] > ' ')) { x++; break; } else { @@ -2849,7 +2849,7 @@ Terminal.prototype.keySelect = function(ev, key) { x--; } if (x < 0) x = 0; - if (x === 0 && (l[x][1] <= ' ' || !saw_space)) { + if (x === 0 && (line[x][1] <= ' ' || !saw_space)) { x = this.cols - 1; if (--y < 0) { y++; @@ -2880,17 +2880,17 @@ Terminal.prototype.keySelect = function(ev, key) { if (x >= this.cols) x--; for (;;) { - var l = this.lines[yb + y]; + var line = this.lines[yb + y]; while (x < this.cols) { - if (l[x][1] <= ' ') { + if (line[x][1] <= ' ') { x++; } else { break; } } while (x < this.cols) { - if (l[x][1] <= ' ') { - if (x - 1 >= 0 && l[x - 1][1] > ' ') { + if (line[x][1] <= ' ') { + if (x - 1 >= 0 && line[x - 1][1] > ' ') { x--; break; } @@ -2898,7 +2898,7 @@ Terminal.prototype.keySelect = function(ev, key) { x++; } if (x >= this.cols) x = this.cols - 1; - if (x === this.cols - 1 && l[x][1] <= ' ') { + if (x === this.cols - 1 && line[x][1] <= ' ') { x = 0; if (++y >= this.rows) { y--; @@ -2927,10 +2927,10 @@ Terminal.prototype.keySelect = function(ev, key) { if (key === '0') { this.x = 0; } else if (key === '^') { - var l = this.lines[this.ydisp + this.y]; + var line = this.lines[this.ydisp + this.y]; var x = 0; while (x < this.cols) { - if (l[x][1] > ' ') { + if (line[x][1] > ' ') { break; } x++; @@ -2949,10 +2949,10 @@ Terminal.prototype.keySelect = function(ev, key) { if (key === '$') { var ox = this.x; - var l = this.lines[this.ydisp + this.y]; + var line = this.lines[this.ydisp + this.y]; var x = this.cols - 1; while (x >= 0) { - if (l[x][1] > ' ') { + if (line[x][1] > ' ') { break; } x--; @@ -3298,7 +3298,7 @@ Terminal.prototype.keySearch = function(ev, key) { return; } - if (key) { + 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; @@ -3491,6 +3491,15 @@ Terminal.prototype.grabText = function(x1, x2, y1, y2) { out += '\n'; } + // If we're not at the end of the + // line, don't add a newline. + for (x = x2 + 1, y = y2; x < this.cols; x++) { + if (this.lines[y][x][1] !== ' ') { + out = out.slice(0, -1); + break; + } + } + return out; }; @@ -3526,6 +3535,7 @@ Terminal.prototype.keyPress = function(ev) { key = String.fromCharCode(key); if (this.prefixMode) { + this.prefixMode = false; if (key === 'k' || key === '&') { this.destroy(); } else if (key === 'p' || key === ']') { @@ -3543,7 +3553,6 @@ Terminal.prototype.keyPress = function(ev) { } else if (key === '[') { this.enterSelect(); } - this.prefixMode = false; return false; } From 9e6cb6b6b147004dec3a3371d095b1648e00a489 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 16 Aug 2013 12:00:11 -0500 Subject: [PATCH 08/10] major clean up of modes. --- src/term.js | 1927 ++++++++++++++++++++++++++------------------------- 1 file changed, 981 insertions(+), 946 deletions(-) diff --git a/src/term.js b/src/term.js index 874fabb6..5aafd5c1 100644 --- a/src/term.js +++ b/src/term.js @@ -107,7 +107,6 @@ EventEmitter.prototype.emit = function(type) { for (; i < l; i++) { obj[i].apply(this, args); - // if (obj[i].apply(this, args) === false) return false; } }; @@ -572,7 +571,7 @@ Terminal.bindCopy = function(document) { var term = Terminal.focus; if (!term) return; if (!term._selected) return; - var textarea = term._copyTextarea(); + var textarea = term.getCopyTextarea(); var text = term.grabText( term._selected.x1, term._selected.x2, term._selected.y1, term._selected.y2); @@ -2528,16 +2527,17 @@ Terminal.prototype.keyDown = function(ev) { // Ctrl-A //if (this.screenKeys) if (!this.prefixMode && !this.selectMode && ev.keyCode === 65) { - this.prefixMode = true; + this.enterPrefix(); return cancel(ev); } // Ctrl-V if (this.prefixMode && ev.keyCode === 86) { - this.prefixMode = false; + this.leavePrefix(); return; } // Ctrl-C - if (this.selectMode && ev.keyCode === 67) { + if ((this.prefixMode || this.selectMode) && ev.keyCode === 67) { + if (this.visualMode) this.leaveVisual(); return; } key = String.fromCharCode(ev.keyCode - 64); @@ -2569,938 +2569,27 @@ Terminal.prototype.keyDown = function(ev) { break; } - if (key && this.prefixMode) { - this.prefixMode = false; + // this.emit('keydown', ev); + + if (!key) return true; + + if (this.prefixMode) { + this.leavePrefix(); return cancel(ev); } - if (key && this.selectMode) { + if (this.selectMode) { this.keySelect(ev, key); return cancel(ev); } this.emit('keydown', ev); - // if (this.emit('keydown', key, ev) === false) { this.showCursor(); cancel(ev); return; } + this.emit('key', key, ev); - if (key) { - this.emit('key', key, ev); - // if (this.emit('key', key, ev) === false) { this.showCursor(); cancel(ev); return; } - - this.showCursor(); - this.handler(key); - - return cancel(ev); - } - - return true; -}; - -Terminal.prototype.enterSelect = function(ev, key) { - this._savedSelect = { - x: this.x, - y: this.y, - ydisp: this.ydisp, - ybase: this.ybase, - cursorHidden: this.cursorHidden, - lines: 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(ev, key) { - this.x = this._savedSelect.x; - this.y = this._savedSelect.y; - this.ydisp = this._savedSelect.ydisp; - this.ybase = this._savedSelect.ybase; - this.cursorHidden = this._savedSelect.cursorHidden; - this.lines = this._savedSelect.lines; - this.write = this._savedSelect.write; - delete this._savedSelect; - this.selectMode = false; - this.visualMode = false; - this.refresh(0, this.rows - 1); -}; - -Terminal.prototype.keySelect = function(ev, key) { this.showCursor(); + this.handler(key); - 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, this.ydisp + this.y, 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, this.ydisp + this.y, 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') { - 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, this.ydisp + this.y, y); - } else { - this.refresh(this.y, this.y + 1); - } - return; - } - - if (key === 'j') { - 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') { - var x = this.x; - this.x--; - if (this.x < 0) { - this.x = 0; - } - if (this.visualMode) { - this.selectText(this.x, x, this.ydisp + this.y, this.ydisp + this.y); - } else { - this.refresh(this.y, this.y); - } - return; - } - - if (key === 'l') { - 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') { - if (!this.visualMode) { - this._savedSelect.preVisual = copyBuffer(this.lines); - this.selectText(this.x, this.x, this.ydisp + this.y, this.ydisp + this.y); - this.visualMode = true; - } else { - this.lines = this._savedSelect.preVisual; - delete this._savedSelect.preVisual; - delete this._selected; - this.visualMode = false; - this.refresh(0, this.rows - 1); - } - return; - } - - if (key === 'y') { - var text = this.grabText( - this._selected.x1, this._selected.x2, - this._selected.y1, this._selected.y2); - this.copyText(text); - this.lines = this._savedSelect.preVisual; - delete this._savedSelect.preVisual; - delete this._selected; - this.visualMode = false; - this.refresh(0, this.rows - 1); - return; - } - - if (key === 'q') { - if (this.visualMode) { - this.lines = this._savedSelect.preVisual; - delete this._savedSelect.preVisual; - delete this._selected; - this.visualMode = false; - this.refresh(0, this.rows - 1); - } 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(this.x, ox, this.ydisp + this.y, oy + oyd); - } - 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(this.x, ox, 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] > ' ') { - 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); - if (this.visualMode) { - this.selectText(this.x, ox, this.ydisp + this.y, oy + oyd); - } - } 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 (key === '{') { - if (this.visualMode) { - this.selectText(this.x, ox, this.ydisp + this.y, oy + oyd); - } - } else if (key === '}') { - if (this.visualMode) { - this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); - } - } - return; - } - - if (key === '/' || key === '?') { - this.entry = ''; - this.searchMode = true; - this.searchDown = key === '/'; - this._savedSelect.preSearch = copyBuffer(this.lines); - this._savedSelect.preSearchX = this.x; - this._savedSelect.preSearchY = this.y; - this.entryPrefix = 'Search: '; - 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); - return; - } -}; - -Terminal.prototype._copyTextarea = function(text) { - var textarea = this._textarea - , document = this.document; - - if (!textarea) { - textarea = document.createElement('textarea'); - textarea.style.position = 'absolute'; - textarea.style.left = '-32000px'; - textarea.style.top = '-32000px'; - textarea.style.opacity = '0'; - textarea.style.backgroundColor = 'transparent'; - textarea.style.borderStyle = 'none'; - textarea.style.outlineStyle = 'none'; - - document.getElementsByTagName('body')[0].appendChild(textarea); - - this._textarea = 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._copyTextarea(); - - 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.keySearch = function(ev, key) { - if (this.searchMode || key === 'n' || key === 'N') { - if (key === '\x1b') { - this.searchMode = false; - - if (this._savedSelect.preSearch) { - this.lines = this._savedSelect.preSearch; - this.x = this._savedSelect.preSearchX; - this.y = this._savedSelect.preSearchY; - delete this._savedSelect.preSearch; - delete this._savedSelect.preSearchX; - delete this._savedSelect.preSearchY; - } - - this.refresh(this.rows - 1, this.rows - 1); - return; - } - - if (key === '\r' || (!this.searchMode && (key === 'n' || key === 'N'))) { - this.searchMode = false; - - if (this._savedSelect.preSearch) { - this.lines = this._savedSelect.preSearch; - this.x = this._savedSelect.preSearchX; - this.y = this._savedSelect.preSearchY; - delete this._savedSelect.preSearch; - delete this._savedSelect.preSearchX; - delete this._savedSelect.preSearchY; - } - - 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; - // set_message("Search wrapped. Continuing at TOP."); - wrapped = true; - y = 0; - } - } else { - y--; - if (y < 0) { - if (wrapped) break; - // set_message("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 (!wrapped) UPDATE_SCROLL; - - if (this.visualMode) { - this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); - } - return; - } else { - // set_message("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; - } - } -}; - -function copyBuffer(lines) { - var 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.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; - if (y === oy1) { - x = ox1; - } - if (y === oy2) { - xl = ox2; - } - for (; x < this.cols; 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; - 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 = '' - , ch - , x - , y - , xl - , tmp; - - 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; - if (y === y1) { - x = x1; - } - if (y === y2) { - xl = x2; - } - for (; x < xl; x++) { - ch = this.lines[y][x][1]; - if (ch === ' ') { - buf += ch; - continue; - } - if (buf) { - out += buf; - buf = ''; - } - out += ch; - if (isWide(ch)) x++; - } - buf = ''; - out += '\n'; - } - - // If we're not at the end of the - // line, don't add a newline. - for (x = x2 + 1, y = y2; x < this.cols; x++) { - if (this.lines[y][x][1] !== ' ') { - out = out.slice(0, -1); - break; - } - } - - return out; + return cancel(ev); }; Terminal.prototype.setgLevel = function(g) { @@ -3535,24 +2624,8 @@ Terminal.prototype.keyPress = function(ev) { key = String.fromCharCode(key); if (this.prefixMode) { - this.prefixMode = false; - 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') { - this.emit('request tab', +key); - } else if (key === 'n') { - this.emit('request tab next'); - } else if (key === 'P') { - this.emit('request tab previous'); - } else if (key === ':') { - // this.enterCommand(); - } else if (key === '[') { - this.enterSelect(); - } + this.leavePrefix(); + this.keyPrefix(ev, key); return false; } @@ -3562,9 +2635,7 @@ Terminal.prototype.keyPress = function(ev) { } this.emit('keypress', key, ev); - // if (this.emit('keypress', key, ev) === false) { this.showCursor(); return; } this.emit('key', key, ev); - // if (this.emit('key', key, ev) === false) { this.showCursor(); return; } this.showCursor(); this.handler(key); @@ -5486,6 +4557,970 @@ 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; + this.entryPrefix = 'Search: '; + 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 = []; + + 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.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.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; + if (y === oy1) { + x = ox1; + } + if (y === oy2) { + xl = ox2; + } + for (; x < this.cols; 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; + 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 = '' + , ch + , x + , y + , xl + , tmp; + + 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; + if (y === y1) { + x = x1; + } + if (y === y2) { + xl = x2; + } + for (; x < xl; x++) { + ch = this.lines[y][x][1]; + if (ch === ' ') { + buf += ch; + continue; + } + if (buf) { + out += buf; + buf = ''; + } + out += ch; + if (isWide(ch)) x++; + } + buf = ''; + out += '\n'; + } + + // If we're not at the end of the + // line, don't add a newline. + for (x = x2 + 1, y = y2; x < this.cols; x++) { + if (this.lines[y][x][1] !== ' ') { + out = out.slice(0, -1); + break; + } + } + + 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') { + this.emit('request tab', +key); + } else if (key === 'n') { + this.emit('request tab next'); + } else if (key === 'P') { + this.emit('request tab previous'); + } else if (key === ':') { + // this.enterCommand(); + } 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, this.ydisp + this.y, 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, this.ydisp + this.y, 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') { + 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, this.ydisp + this.y, y); + } else { + this.refresh(this.y, this.y + 1); + } + return; + } + + if (key === 'j') { + 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') { + var x = this.x; + this.x--; + if (this.x < 0) { + this.x = 0; + } + if (this.visualMode) { + this.selectText(this.x, x, this.ydisp + this.y, this.ydisp + this.y); + } else { + this.refresh(this.y, this.y); + } + return; + } + + if (key === 'l') { + 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') { + 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') { + 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(this.x, ox, this.ydisp + this.y, oy + oyd); + } + 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(this.x, ox, 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] > ' ') { + 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); + if (this.visualMode) { + this.selectText(this.x, ox, this.ydisp + this.y, oy + oyd); + } + } 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 (key === '{') { + if (this.visualMode) { + this.selectText(this.x, ox, this.ydisp + this.y, oy + oyd); + } + } else if (key === '}') { + 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.keyVisual = function(ev, key) { + var ox = this.x + , oy = this.y + , oyd = this.ydisp + , ret; + + ret = this.keySelect(ev, key); + + if (ret !== false && this.visualMode) { + this.selectText(this.x, ox, this.ydisp + this.y, oy + oyd); + } + + return ret; +}; + +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; + // set_message("Search wrapped. Continuing at TOP."); + wrapped = true; + y = 0; + } + } else { + y--; + if (y < 0) { + if (wrapped) break; + // set_message("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 (!wrapped) UPDATE_SCROLL; + + if (this.visualMode) { + this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); + } + return; + } else { + // set_message("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 bcecbcd917c21a53696265fc0c4c5bd87e7fedb6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 16 Aug 2013 12:32:36 -0500 Subject: [PATCH 09/10] vim style selection+yanking. add props to constructor. --- src/term.js | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/term.js b/src/term.js index 5aafd5c1..b74698e4 100644 --- a/src/term.js +++ b/src/term.js @@ -207,6 +207,16 @@ function Terminal(options) { 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 = ''; + this._real; + // charset this.charset = null; this.gcharset = null; @@ -2357,7 +2367,8 @@ Terminal.prototype.writeln = function(data) { // Key Resources: // https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent Terminal.prototype.keyDown = function(ev) { - var key; + var self = this + , key; switch (ev.keyCode) { // backspace @@ -2537,7 +2548,11 @@ Terminal.prototype.keyDown = function(ev) { } // Ctrl-C if ((this.prefixMode || this.selectMode) && ev.keyCode === 67) { - if (this.visualMode) this.leaveVisual(); + if (this.visualMode) { + setTimeout(function() { + self.leaveVisual(); + }, 1); + } return; } key = String.fromCharCode(ev.keyCode - 64); @@ -4787,14 +4802,14 @@ Terminal.prototype.selectText = function(x1, x2, y1, y2) { for (y = y1; y <= y2; y++) { x = 0; - xl = this.cols; + xl = this.cols - 1; if (y === y1) { x = x1; } if (y === y2) { xl = x2; } - for (; x < xl; x++) { + 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; @@ -4846,14 +4861,14 @@ Terminal.prototype.grabText = function(x1, x2, y1, y2) { for (y = y1; y <= y2; y++) { x = 0; - xl = this.cols; + xl = this.cols - 1; if (y === y1) { x = x1; } if (y === y2) { xl = x2; } - for (; x < xl; x++) { + for (; x <= xl; x++) { ch = this.lines[y][x][1]; if (ch === ' ') { buf += ch; @@ -4872,7 +4887,7 @@ Terminal.prototype.grabText = function(x1, x2, y1, y2) { // If we're not at the end of the // line, don't add a newline. - for (x = x2 + 1, y = y2; x < this.cols; x++) { + for (x = x2, y = y2; x < this.cols; x++) { if (this.lines[y][x][1] !== ' ') { out = out.slice(0, -1); break; @@ -5218,6 +5233,7 @@ Terminal.prototype.keySelect = function(ev, key) { var x = this.cols - 1; while (x >= 0) { if (line[x][1] > ' ') { + if (this.visualMode && x < this.cols - 1) x++; break; } x--; From 74848936955ddbabc4d08b9e473d0ed55c85a3f6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 16 Aug 2013 14:59:23 -0500 Subject: [PATCH 10/10] options. events. readme. --- README.md | 25 +++++++++++++++++++++- example/index.html | 3 ++- src/term.js | 53 +++++++++++++++++----------------------------- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index f05e60ee..4564ad72 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ window.addEventListener('load', function() { socket.on('connect', function() { var term = new Terminal({ cols: 80, - rows: 24 + rows: 24, + screenKeys: true }); term.on('data', function(data) { @@ -47,6 +48,28 @@ window.addEventListener('load', function() { }, 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. + ## License Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) diff --git a/example/index.html b/example/index.html index 76739f8d..ccac5f51 100644 --- a/example/index.html +++ b/example/index.html @@ -37,7 +37,8 @@ var term = new Terminal({ cols: 80, rows: 24, - useStyle: true + useStyle: true, + screenKeys: true }); term.on('data', function(data) { diff --git a/src/term.js b/src/term.js index b74698e4..c0ccb56d 100644 --- a/src/term.js +++ b/src/term.js @@ -216,6 +216,8 @@ function Terminal(options) { this.entry = ''; this.entryPrefix = ''; this._real; + this._selected; + this._textarea; // charset this.charset = null; @@ -390,7 +392,7 @@ Terminal.defaults = { visualBell: false, popOnBell: false, scrollback: 1000, - // screenKeys: false, + screenKeys: false, // programFeatures: false, // escapeKey: null, debug: false, @@ -467,7 +469,6 @@ Terminal.prototype.initGlobal = function() { Terminal.bindKeys(document); - //if (this.screenKeys) Terminal.bindCopy(document); if (this.isIpad) { @@ -2536,10 +2537,11 @@ Terminal.prototype.keyDown = function(ev) { return; } // Ctrl-A - //if (this.screenKeys) - if (!this.prefixMode && !this.selectMode && ev.keyCode === 65) { - this.enterPrefix(); - return cancel(ev); + if (this.screenKeys) { + if (!this.prefixMode && !this.selectMode && ev.keyCode === 65) { + this.enterPrefix(); + return cancel(ev); + } } // Ctrl-V if (this.prefixMode && ev.keyCode === 86) { @@ -2584,8 +2586,6 @@ Terminal.prototype.keyDown = function(ev) { break; } - // this.emit('keydown', ev); - if (!key) return true; if (this.prefixMode) { @@ -4905,13 +4905,15 @@ Terminal.prototype.keyPrefix = function(ev, key) { } else if (key === 'c') { this.emit('request create'); } else if (key >= '0' && key <= '9') { - this.emit('request tab', +key); + key = +key - 1; + if (!~key) key = 9; + this.emit('request term', key); } else if (key === 'n') { - this.emit('request tab next'); + this.emit('request term next'); } else if (key === 'P') { - this.emit('request tab previous'); + this.emit('request term previous'); } else if (key === ':') { - // this.enterCommand(); + this.emit('request command mode'); } else if (key === '[') { this.enterSelect(); } @@ -5030,7 +5032,7 @@ Terminal.prototype.keySelect = function(ev, key) { return; } - if (key === 'v') { + if (key === 'v' || key === ' ') { if (!this.visualMode) { this.enterVisual(); } else { @@ -5388,21 +5390,6 @@ Terminal.prototype.keySelect = function(ev, key) { return false; }; -Terminal.prototype.keyVisual = function(ev, key) { - var ox = this.x - , oy = this.y - , oyd = this.ydisp - , ret; - - ret = this.keySelect(ev, key); - - if (ret !== false && this.visualMode) { - this.selectText(this.x, ox, this.ydisp + this.y, oy + oyd); - } - - return ret; -}; - Terminal.prototype.keySearch = function(ev, key) { if (key === '\x1b') { this.leaveSearch(); @@ -5457,7 +5444,7 @@ Terminal.prototype.keySearch = function(ev, key) { y++; if (y > this.ybase + this.rows - 1) { if (wrapped) break; - // set_message("Search wrapped. Continuing at TOP."); + // this.setMessage('Search wrapped. Continuing at TOP.'); wrapped = true; y = 0; } @@ -5465,7 +5452,7 @@ Terminal.prototype.keySearch = function(ev, key) { y--; if (y < 0) { if (wrapped) break; - // set_message("Search wrapped. Continuing at BOTTOM."); + // this.setMessage('Search wrapped. Continuing at BOTTOM.'); wrapped = true; y = this.ybase + this.rows - 1; } @@ -5488,17 +5475,15 @@ Terminal.prototype.keySearch = function(ev, key) { this.x = x, this.y = y; this.scrollDisp(-this.ydisp + yb); - // if (!wrapped) UPDATE_SCROLL; - if (this.visualMode) { this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); } return; - } else { - // set_message("No matches found."); } + // this.setMessage("No matches found."); this.refresh(0, this.rows - 1); + return; }