Compare commits

...
38 Commits
Author SHA1 Message Date
Paris Kasidiaris 4b7279146a Bump version to 2.7.0
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2017-06-06 15:04:59 +00:00
Paris Kasidiaris c4a2f7911d Merge pull request #674 from sourcelair/issue-#671-restore-paste-event
Restore the "paste" terminal event
2017-06-06 11:23:18 +03:00
Paris Kasidiaris db9fe53af3 Merge branch 'master' into issue-#671-restore-paste-event 2017-06-06 11:16:16 +03:00
Daniel Imms 42724c7f42 Merge pull request #672 from Tyriar/662_parser_state_hack
Fix bug with parser state when using vtop
2017-05-27 15:07:52 -07:00
Paris Kasidiaris 616f069538 Fix #671 - Restore paste event 2017-05-27 15:30:26 +03:00
Daniel Imms 2c3d98abc8 Fix bug with parser state when using vtop
Fixes #662
2017-05-25 09:36:59 -07:00
Daniel Imms 35b32b721e Merge pull request #668 from Tyriar/fix_pt_valid_var_declarations
Declare pt and valid vars in correct spot
2017-05-22 08:19:52 -07:00
Daniel Imms ccf1a047d3 Merge branch 'master' into fix_pt_valid_var_declarations 2017-05-22 08:03:53 -07:00
Daniel Imms db0338cbcf Merge pull request #669 from Tyriar/pull_getCoords_into_module
Pull get coords into module
2017-05-22 08:03:31 -07:00
Daniel Imms 06742eb507 Merge branch 'master' into pull_getCoords_into_module 2017-05-22 07:57:55 -07:00
Daniel Imms 0fab4ff5d1 Merge pull request #667 from Tyriar/add_missing_emit_arg
Add missing EventEmitter.emit args/types
2017-05-22 07:57:35 -07:00
Daniel Imms d2b0d514f8 Add missing semi-colons 2017-05-21 13:51:02 -07:00
Daniel Imms 70e35de56b Polish comments and cap code 2017-05-21 12:05:30 -07:00
Daniel Imms c323e0ad4a jsdoc 2017-05-21 11:57:24 -07:00
Daniel Imms 4d32826891 Pull getCoords into a Mouse util module
This is part of the work to prepare for the upcoing selection changes

Related #207
2017-05-21 11:50:01 -07:00
Daniel Imms 13088f55a0 Declare pt and valid vars in correct spot 2017-05-21 11:27:57 -07:00
Daniel Imms ea008411e4 Add missing EventEmitter.emit args/types 2017-05-21 11:25:18 -07:00
Daniel Imms a95ac990ff Merge pull request #450 from Tyriar/449_keep_span_pool
Keep row spans in an object pool to reduce garbage collection by reusing DOM nodes
2017-05-16 10:12:27 -07:00
Daniel Imms aad8439542 Fix lint errors 2017-05-16 10:07:55 -07:00
Daniel Imms 1a777edffe Add missing return type 2017-05-16 10:04:12 -07:00
Daniel Imms 537018f71f DomElementObjectPool jsdoc 2017-05-16 10:03:56 -07:00
Daniel Imms 67ca3a9334 More string interpolation 2017-05-16 09:57:50 -07:00
Daniel Imms 5fb30979c1 Add jsdoc back, was lost in merge 2017-05-14 14:28:06 -07:00
Daniel Imms cf863d4849 Use string interpolation 2017-05-14 14:26:45 -07:00
Daniel Imms 47814847f9 Remove var usages 2017-05-14 12:47:17 -07:00
Daniel Imms 9ec8974545 More clean up 2017-05-14 12:40:45 -07:00
Daniel Imms c887c6e0f6 Clean up 2017-05-14 12:27:06 -07:00
Daniel Imms 6c8949bba1 Only wrap single width unicode chars 2017-05-14 12:20:01 -07:00
Daniel Imms 508d3ab68c Merge remote-tracking branch 'ups/master' into 449_keep_span_pool 2017-05-14 12:09:47 -07:00
Paris Kasidiaris 6dad013783 Merge pull request #659 from jdanyow/paste
Normalize line endings on paste
2017-05-11 12:28:38 +03:00
Jeremy Danyow 0532e5cbf0 Normalize line endings on paste 2017-05-09 15:21:56 -07:00
Daniel Imms abcdf3e252 Merge branch 'master' into 449_keep_span_pool 2017-05-08 11:35:42 -07:00
Daniel Imms 3baa6b9237 Wrap all characters temporarily in spans to force fixed with
Fixes #467
2017-05-07 16:08:01 -07:00
Daniel Imms 23e6a475ee Merge remote-tracking branch 'origin/master' into 449_keep_span_pool 2017-05-07 15:49:40 -07:00
Daniel Imms 6b5ced0078 Merge remote-tracking branch 'upstream/master' into 449_keep_span_pool 2017-01-08 17:09:23 -08:00
Daniel Imms c7e8498166 Clear row and return spans before generating row 2017-01-04 09:44:58 -08:00
Daniel Imms 2b4c019f13 Use object pool in refresh 2017-01-04 09:32:23 -08:00
Daniel Imms 335850380e Implement DomElementObjectPool with tests 2017-01-04 08:50:19 -08:00
16 changed files with 558 additions and 250 deletions
+1
View File
@@ -30,6 +30,7 @@ Ian Lewis <ianlewis@google.com>
imoses <ido@twiggle.com>
InDieTasten <indietasten@gmail.com>
Jean Bruenn <himself@jeanbruenn.info>
Jeremy Danyow <jedanyow@microsoft.com>
Jörg Breitbart <jerch@rockborn.de>
Justin Mecham <justin@mecham.me>
Lucian Buzzo <lucian.buzzo@gmail.com>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm.js",
"version": "2.6.0",
"version": "2.7.0",
"ignore": ["demo", "test", ".gitignore"],
"main": [
"dist/xterm.js",
+2 -1
View File
@@ -153,7 +153,8 @@
overflow-y: scroll;
}
.terminal .xterm-wide-char {
.terminal .xterm-wide-char,
.terminal .xterm-normal-char {
display: inline-block;
}
+209 -110
View File
@@ -372,10 +372,13 @@ var EventEmitter = (function () {
return this.on(type, on);
};
EventEmitter.prototype.emit = function (type) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
if (!this._events[type]) {
return;
}
var args = Array.prototype.slice.call(arguments, 1);
var obj = this._events[type];
for (var i = 0; i < obj.length; i++) {
obj[i].apply(this, args);
@@ -1928,11 +1931,14 @@ var Parser = (function () {
if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) {
if (ch === EscapeSequences_1.C0.ESC)
this._position++;
var pt = void 0;
var valid = void 0;
switch (this._terminal.prefix) {
case '':
break;
case '$q':
var pt = this._terminal.currentParam, valid = false;
pt = this._terminal.currentParam;
valid = false;
switch (pt) {
case '"q':
pt = '0"q';
@@ -1960,8 +1966,8 @@ var Parser = (function () {
case '+p':
break;
case '+q':
pt = this._terminal.currentParam
, valid = false;
pt = this._terminal.currentParam;
valid = false;
this._terminal.send(EscapeSequences_1.C0.ESC + 'P' + +valid + '+r' + pt + EscapeSequences_1.C0.ESC + '\\');
break;
default:
@@ -1996,6 +2002,7 @@ var Parser = (function () {
break;
}
}
return this._state;
};
Parser.prototype.setState = function (state) {
this._state = state;
@@ -2028,6 +2035,7 @@ exports.Parser = Parser;
},{"./Charsets":1,"./EscapeSequences":3}],8:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var DomElementObjectPool_1 = require("./utils/DomElementObjectPool");
var MAX_REFRESH_FRAME_SKIP = 5;
var FLAGS;
(function (FLAGS) {
@@ -2045,9 +2053,11 @@ var Renderer = (function () {
this._refreshRowsQueue = [];
this._refreshFramesSkipped = 0;
this._refreshAnimationFrame = null;
this._spanElementObjectPool = new DomElementObjectPool_1.DomElementObjectPool('span');
if (brokenBold === null) {
brokenBold = checkBoldBroken(this._terminal.element);
}
this._spanElementObjectPool = new DomElementObjectPool_1.DomElementObjectPool('span');
}
Renderer.prototype.queueRefresh = function (start, end) {
this._refreshRowsQueue.push({ start: start, end: end });
@@ -2085,80 +2095,102 @@ var Renderer = (function () {
this._refresh(start, end);
};
Renderer.prototype._refresh = function (start, end) {
var x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement;
var parent;
if (end - start >= this._terminal.rows / 2) {
parent = this._terminal.element.parentNode;
if (parent) {
this._terminal.element.removeChild(this._terminal.rowContainer);
}
}
width = this._terminal.cols;
y = start;
var width = this._terminal.cols;
var y = start;
if (end >= this._terminal.rows) {
this._terminal.log('`end` is too large. Most likely a bad CSR.');
end = this._terminal.rows - 1;
}
for (; y <= end; y++) {
row = y + this._terminal.ydisp;
line = this._terminal.lines.get(row);
if (!line || !this._terminal.children[y]) {
continue;
}
out = '';
if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp)
&& this._terminal.cursorState
&& !this._terminal.cursorHidden) {
var row = y + this._terminal.ydisp;
var line = this._terminal.lines.get(row);
var x = void 0;
if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp) &&
this._terminal.cursorState &&
!this._terminal.cursorHidden) {
x = this._terminal.x;
}
else {
x = -1;
}
attr = this._terminal.defAttr;
i = 0;
for (; i < width; i++) {
if (!line[i]) {
var attr = this._terminal.defAttr;
var documentFragment = document.createDocumentFragment();
var innerHTML = '';
var currentElement = void 0;
while (this._terminal.children[y].children.length) {
var child = this._terminal.children[y].children[0];
this._terminal.children[y].removeChild(child);
this._spanElementObjectPool.release(child);
}
for (var i = 0; i < width; i++) {
var data = line[i][0];
var ch = line[i][1];
var ch_width = line[i][2];
if (!ch_width) {
continue;
}
data = line[i][0];
ch = line[i][1];
ch_width = line[i][2];
if (!ch_width)
continue;
if (i === x)
if (i === x) {
data = -1;
}
if (data !== attr) {
if (attr !== this._terminal.defAttr) {
out += '</span>';
if (innerHTML) {
currentElement.innerHTML = innerHTML;
innerHTML = '';
}
documentFragment.appendChild(currentElement);
currentElement = null;
}
if (data !== this._terminal.defAttr) {
if (innerHTML && !currentElement) {
currentElement = this._spanElementObjectPool.acquire();
}
if (currentElement) {
if (innerHTML) {
currentElement.innerHTML = innerHTML;
innerHTML = '';
}
documentFragment.appendChild(currentElement);
}
currentElement = this._spanElementObjectPool.acquire();
if (data === -1) {
out += '<span class="reverse-video terminal-cursor">';
currentElement.classList.add('reverse-video', 'terminal-cursor');
}
else {
var classNames = [];
bg = data & 0x1ff;
fg = (data >> 9) & 0x1ff;
flags = data >> 18;
var bg = data & 0x1ff;
var fg = (data >> 9) & 0x1ff;
var flags = data >> 18;
if (flags & FLAGS.BOLD) {
if (!brokenBold) {
classNames.push('xterm-bold');
currentElement.classList.add('xterm-bold');
}
if (fg < 8)
if (fg < 8) {
fg += 8;
}
}
if (flags & FLAGS.UNDERLINE) {
classNames.push('xterm-underline');
currentElement.classList.add('xterm-underline');
}
if (flags & FLAGS.BLINK) {
classNames.push('xterm-blink');
currentElement.classList.add('xterm-blink');
}
if (flags & FLAGS.INVERSE) {
bg = [fg, fg = bg][0];
if ((flags & 1) && fg < 8)
var temp = bg;
bg = fg;
fg = temp;
if ((flags & 1) && fg < 8) {
fg += 8;
}
}
if (flags & FLAGS.INVISIBLE) {
classNames.push('xterm-hidden');
currentElement.classList.add('xterm-hidden');
}
if (flags & FLAGS.INVERSE) {
if (bg === 257) {
@@ -2169,50 +2201,55 @@ var Renderer = (function () {
}
}
if (bg < 256) {
classNames.push('xterm-bg-color-' + bg);
currentElement.classList.add("xterm-bg-color-" + bg);
}
if (fg < 256) {
classNames.push('xterm-color-' + fg);
currentElement.classList.add("xterm-color-" + fg);
}
out += '<span';
if (classNames.length) {
out += ' class="' + classNames.join(' ') + '"';
}
out += '>';
}
}
}
if (ch_width === 2) {
out += '<span class="xterm-wide-char">';
innerHTML += "<span class=\"xterm-wide-char\">" + ch + "</span>";
}
switch (ch) {
case '&':
out += '&amp;';
break;
case '<':
out += '&lt;';
break;
case '>':
out += '&gt;';
break;
default:
if (ch <= ' ') {
out += '&nbsp;';
}
else {
out += ch;
}
break;
else if (ch.charCodeAt(0) > 255) {
innerHTML += "<span class=\"xterm-normal-char\">" + ch + "</span>";
}
if (ch_width === 2) {
out += '</span>';
else {
switch (ch) {
case '&':
innerHTML += '&amp;';
break;
case '<':
innerHTML += '&lt;';
break;
case '>':
innerHTML += '&gt;';
break;
default:
if (ch <= ' ') {
innerHTML += '&nbsp;';
}
else {
innerHTML += ch;
}
break;
}
}
attr = data;
}
if (attr !== this._terminal.defAttr) {
out += '</span>';
if (innerHTML && !currentElement) {
currentElement = this._spanElementObjectPool.acquire();
}
this._terminal.children[y].innerHTML = out;
if (currentElement) {
if (innerHTML) {
currentElement.innerHTML = innerHTML;
innerHTML = '';
}
documentFragment.appendChild(currentElement);
currentElement = null;
}
this._terminal.children[y].appendChild(documentFragment);
}
if (parent) {
this._terminal.element.appendChild(this._terminal.rowContainer);
@@ -2239,7 +2276,7 @@ function checkBoldBroken(terminal) {
},{}],9:[function(require,module,exports){
},{"./utils/DomElementObjectPool":14}],9:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Viewport = (function () {
@@ -2328,6 +2365,13 @@ function prepareTextForClipboard(text) {
return processedText;
}
exports.prepareTextForClipboard = prepareTextForClipboard;
function prepareTextForTerminal(text, isMSWindows) {
if (isMSWindows) {
return text.replace(/\r?\n/g, '\n');
}
return text;
}
exports.prepareTextForTerminal = prepareTextForTerminal;
function copyHandler(ev, term) {
var copiedText = window.getSelection().toString(), text = prepareTextForClipboard(copiedText);
if (term.browser.isMSIE) {
@@ -2343,8 +2387,10 @@ function pasteHandler(ev, term) {
ev.stopPropagation();
var text;
var dispatchPaste = function (text) {
text = prepareTextForTerminal(text, term.browser.isMSWindows);
term.handler(text);
term.textarea.value = '';
term.emit('paste', text);
return term.cancel(ev);
};
if (term.browser.isMSIE) {
@@ -2415,7 +2461,7 @@ exports.isMSWindows = Generic_1.contains(['Windows', 'Win16', 'Win32', 'WinCE'],
},{"./Generic":14}],12:[function(require,module,exports){
},{"./Generic":15}],12:[function(require,module,exports){
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
@@ -2626,6 +2672,53 @@ exports.CircularList = CircularList;
},{}],14:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var DomElementObjectPool = (function () {
function DomElementObjectPool(type) {
this.type = type;
this._type = type;
this._pool = [];
this._inUse = {};
}
DomElementObjectPool.prototype.acquire = function () {
var element;
if (this._pool.length === 0) {
element = this._createNew();
}
else {
element = this._pool.pop();
}
this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element;
return element;
};
DomElementObjectPool.prototype.release = function (element) {
if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) {
throw new Error('Could not release an element not yet acquired');
}
delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)];
this._cleanElement(element);
this._pool.push(element);
};
DomElementObjectPool.prototype._createNew = function () {
var element = document.createElement(this._type);
var id = DomElementObjectPool._objectCount++;
element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10));
return element;
};
DomElementObjectPool.prototype._cleanElement = function (element) {
element.className = '';
element.innerHTML = '';
};
return DomElementObjectPool;
}());
DomElementObjectPool.OBJECT_ID_ATTRIBUTE = 'data-obj-id';
DomElementObjectPool._objectCount = 0;
exports.DomElementObjectPool = DomElementObjectPool;
},{}],15:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function contains(arr, el) {
return arr.indexOf(el) >= 0;
}
@@ -2634,7 +2727,41 @@ exports.contains = contains;
},{}],15:[function(require,module,exports){
},{}],16:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function getCoords(event, rowContainer, charMeasure) {
if (event.pageX == null) {
return null;
}
var x = event.pageX;
var y = event.pageY;
var el = rowContainer;
while (el && el !== self.document.documentElement) {
x -= el.offsetLeft;
y -= el.offsetTop;
el = 'offsetParent' in el ? el.offsetParent : el.parentElement;
}
x = Math.ceil(x / charMeasure.width);
y = Math.ceil(y / charMeasure.height);
return [x, y];
}
exports.getCoords = getCoords;
function getRawByteCoords(event, rowContainer, charMeasure, colCount, rowCount) {
var coords = getCoords(event, rowContainer, charMeasure);
var x = coords[0];
var y = coords[1];
x = Math.min(Math.max(x, 0), colCount);
y = Math.min(Math.max(y, 0), rowCount);
x += 32;
y += 32;
return { x: x, y: y };
}
exports.getRawByteCoords = getRawByteCoords;
},{}],17:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var CompositionHelper_1 = require("./CompositionHelper");
@@ -2649,6 +2776,7 @@ var Renderer_1 = require("./Renderer");
var Linkifier_1 = require("./Linkifier");
var CharMeasure_1 = require("./utils/CharMeasure");
var Browser = require("./utils/Browser");
var Mouse_1 = require("./utils/Mouse");
var document = (typeof window != 'undefined') ? window.document : null;
var WRITE_BUFFER_PAUSE_THRESHOLD = 5;
var WRITE_BATCH_SIZE = 300;
@@ -3102,14 +3230,16 @@ Terminal.loadAddon = function (addon, callback) {
}
};
Terminal.prototype.updateCharSizeCSS = function () {
this.charSizeStyleElement.textContent = '.xterm-wide-char{width:' + (this.charMeasure.width * 2) + 'px;}';
this.charSizeStyleElement.textContent =
".xterm-wide-char{width:" + this.charMeasure.width * 2 + "px;}" +
(".xterm-normal-char{width:" + this.charMeasure.width + "px;}");
};
Terminal.prototype.bindMouse = function () {
var el = this.element, self = this, pressed = 32;
function sendButton(ev) {
var button, pos;
button = getButton(ev);
pos = getCoords(ev);
pos = Mouse_1.getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
if (!pos)
return;
sendEvent(button, pos);
@@ -3126,7 +3256,7 @@ Terminal.prototype.bindMouse = function () {
}
function sendMove(ev) {
var button = pressed, pos;
pos = getCoords(ev);
pos = Mouse_1.getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
if (!pos)
return;
button += 32;
@@ -3265,38 +3395,6 @@ Terminal.prototype.bindMouse = function () {
button = (32 + (mod << 2)) + button;
return button;
}
function getCoords(ev) {
var x, y, w, h, el;
if (ev.pageX == null)
return;
x = ev.pageX;
y = ev.pageY;
el = self.element;
while (el && el !== self.document.documentElement) {
x -= el.offsetLeft;
y -= el.offsetTop;
el = 'offsetParent' in el
? el.offsetParent
: el.parentNode;
}
x = Math.ceil(x / self.charMeasure.width);
y = Math.ceil(y / self.charMeasure.height);
if (x < 0)
x = 0;
if (x > self.cols)
x = self.cols;
if (y < 0)
y = 0;
if (y > self.rows)
y = self.rows;
x += 32;
y += 32;
return {
x: x,
y: y,
type: 'wheel'
};
}
on(el, 'mousedown', function (ev) {
if (!self.mouseEvents)
return;
@@ -3452,7 +3550,8 @@ Terminal.prototype.innerWrite = function () {
}
this.refreshStart = this.y;
this.refreshEnd = this.y;
this.parser.parse(data);
var state = this.parser.parse(data);
this.parser.setState(state);
this.updateRange(this.y);
this.refresh(this.refreshStart, this.refreshEnd);
}
@@ -4214,6 +4313,6 @@ module.exports = Terminal;
},{"./CompositionHelper":2,"./EscapeSequences":3,"./EventEmitter":4,"./InputHandler":5,"./Linkifier":6,"./Parser":7,"./Renderer":8,"./Viewport":9,"./handlers/Clipboard":10,"./utils/Browser":11,"./utils/CharMeasure":12,"./utils/CircularList":13}]},{},[15])(15)
},{"./CompositionHelper":2,"./EscapeSequences":3,"./EventEmitter":4,"./InputHandler":5,"./Linkifier":6,"./Parser":7,"./Renderer":8,"./Viewport":9,"./handlers/Clipboard":10,"./utils/Browser":11,"./utils/CharMeasure":12,"./utils/CircularList":13,"./utils/Mouse":16}]},{},[17])(17)
});
//# sourceMappingURL=xterm.js.map
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "2.6.0",
"version": "2.7.0",
"ignore": [
"demo",
"test",
+1 -4
View File
@@ -53,14 +53,11 @@ export class EventEmitter {
return this.on(type, on);
}
public emit(type): void {
public emit(type: string, ...args: any[]): void {
if (!this._events[type]) {
return;
}
let args = Array.prototype.slice.call(arguments, 1);
let obj = this._events[type];
for (let i = 0; i < obj.length; i++) {
obj[i].apply(this, args);
}
+8 -6
View File
@@ -181,7 +181,7 @@ export class Parser {
*
* @param data The data to parse.
*/
public parse(data: string) {
public parse(data: string): ParserState {
let l = data.length, j, cs, ch, code, low;
this._position = 0;
@@ -471,6 +471,8 @@ export class Parser {
case ParserState.DCS:
if (ch === C0.ESC || ch === C0.BEL) {
if (ch === C0.ESC) this._position++;
let pt;
let valid: boolean;
switch (this._terminal.prefix) {
// User-Defined Keys (DECUDK).
@@ -480,8 +482,8 @@ export class Parser {
// Request Status String (DECRQSS).
// test: echo -e '\eP$q"p\e\\'
case '$q':
let pt = this._terminal.currentParam
, valid = false;
pt = this._terminal.currentParam;
valid = false;
switch (pt) {
// DECSCA
@@ -526,9 +528,8 @@ export class Parser {
// This can cause a small glitch in vim.
// test: echo -ne '\eP+q6b64\e\\'
case '+q':
// TODO: Don't declare pt twice
/*let*/ pt = this._terminal.currentParam
, valid = false;
pt = this._terminal.currentParam;
valid = false;
this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\');
break;
@@ -563,6 +564,7 @@ export class Parser {
break;
}
}
return this._state;
}
/**
+108 -77
View File
@@ -3,6 +3,7 @@
*/
import { ITerminal } from './Interfaces';
import { DomElementObjectPool } from './utils/DomElementObjectPool';
/**
* The maximum number of refresh frames to skip when the write buffer is non-
@@ -30,12 +31,15 @@ export class Renderer {
private _refreshFramesSkipped = 0;
private _refreshAnimationFrame = null;
private _spanElementObjectPool = new DomElementObjectPool('span');
constructor(private _terminal: ITerminal) {
// Figure out whether boldness affects
// the character width of monospace fonts.
if (brokenBold === null) {
brokenBold = checkBoldBroken((<any>this._terminal).element);
}
this._spanElementObjectPool = new DomElementObjectPool('span');
// TODO: Pull more DOM interactions into Renderer.constructor, element for
// example should be owned by Renderer (and also exposed by Terminal due to
@@ -117,9 +121,8 @@ export class Renderer {
* @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)
*/
private _refresh(start: number, end: number): void {
let x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement;
// If this is a big refresh, remove the terminal rows from the DOM for faster calculations
let parent;
if (end - start >= this._terminal.rows / 2) {
parent = this._terminal.element.parentNode;
if (parent) {
@@ -127,8 +130,8 @@ export class Renderer {
}
}
width = this._terminal.cols;
y = start;
let width = this._terminal.cols;
let y = start;
if (end >= this._terminal.rows) {
this._terminal.log('`end` is too large. Most likely a bad CSR.');
@@ -136,80 +139,104 @@ export class Renderer {
}
for (; y <= end; y++) {
row = y + this._terminal.ydisp;
let row = y + this._terminal.ydisp;
line = this._terminal.lines.get(row);
if (!line || !this._terminal.children[y]) {
// Continue if the line is not available, this means a resize is currently in progress
continue;
}
out = '';
let line = this._terminal.lines.get(row);
if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp)
&& this._terminal.cursorState
&& !this._terminal.cursorHidden) {
let x;
if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp) &&
this._terminal.cursorState &&
!this._terminal.cursorHidden) {
x = this._terminal.x;
} else {
x = -1;
}
attr = this._terminal.defAttr;
i = 0;
let attr = this._terminal.defAttr;
for (; i < width; i++) {
if (!line[i]) {
// Continue if the character is not available, this means a resize is currently in progress
const documentFragment = document.createDocumentFragment();
let innerHTML = '';
let currentElement;
// Return the row's spans to the pool
while (this._terminal.children[y].children.length) {
const child = this._terminal.children[y].children[0];
this._terminal.children[y].removeChild(child);
this._spanElementObjectPool.release(<HTMLElement>child);
}
for (let i = 0; i < width; i++) {
// TODO: Could data be a more specific type?
let data: any = line[i][0];
const ch = line[i][1];
const ch_width: any = line[i][2];
if (!ch_width) {
continue;
}
data = line[i][0];
ch = line[i][1];
ch_width = line[i][2];
if (!ch_width)
continue;
if (i === x) data = -1;
if (i === x) {
data = -1;
}
if (data !== attr) {
if (attr !== this._terminal.defAttr) {
out += '</span>';
if (innerHTML) {
currentElement.innerHTML = innerHTML;
innerHTML = '';
}
documentFragment.appendChild(currentElement);
currentElement = null;
}
if (data !== this._terminal.defAttr) {
if (innerHTML && !currentElement) {
currentElement = this._spanElementObjectPool.acquire();
}
if (currentElement) {
if (innerHTML) {
currentElement.innerHTML = innerHTML;
innerHTML = '';
}
documentFragment.appendChild(currentElement);
}
currentElement = this._spanElementObjectPool.acquire();
if (data === -1) {
out += '<span class="reverse-video terminal-cursor">';
currentElement.classList.add('reverse-video', 'terminal-cursor');
} else {
let classNames = [];
bg = data & 0x1ff;
fg = (data >> 9) & 0x1ff;
flags = data >> 18;
let bg = data & 0x1ff;
let fg = (data >> 9) & 0x1ff;
let flags = data >> 18;
if (flags & FLAGS.BOLD) {
if (!brokenBold) {
classNames.push('xterm-bold');
currentElement.classList.add('xterm-bold');
}
// See: XTerm*boldColors
if (fg < 8) fg += 8;
if (fg < 8) {
fg += 8;
}
}
if (flags & FLAGS.UNDERLINE) {
classNames.push('xterm-underline');
currentElement.classList.add('xterm-underline');
}
if (flags & FLAGS.BLINK) {
classNames.push('xterm-blink');
currentElement.classList.add('xterm-blink');
}
// If inverse flag is on, then swap the foreground and background variables.
if (flags & FLAGS.INVERSE) {
/* One-line variable swap in JavaScript: http://stackoverflow.com/a/16201730 */
bg = [fg, fg = bg][0];
// Should inverse just be before the
// above boldColors effect instead?
if ((flags & 1) && fg < 8) fg += 8;
let temp = bg;
bg = fg;
fg = temp;
// Should inverse just be before the above boldColors effect instead?
if ((flags & 1) && fg < 8) {
fg += 8;
}
}
if (flags & FLAGS.INVISIBLE) {
classNames.push('xterm-hidden');
currentElement.classList.add('xterm-hidden');
}
/**
@@ -229,55 +256,60 @@ export class Renderer {
}
if (bg < 256) {
classNames.push('xterm-bg-color-' + bg);
currentElement.classList.add(`xterm-bg-color-${bg}`);
}
if (fg < 256) {
classNames.push('xterm-color-' + fg);
currentElement.classList.add(`xterm-color-${fg}`);
}
out += '<span';
if (classNames.length) {
out += ' class="' + classNames.join(' ') + '"';
}
out += '>';
}
}
}
if (ch_width === 2) {
out += '<span class="xterm-wide-char">';
}
switch (ch) {
case '&':
out += '&amp;';
break;
case '<':
out += '&lt;';
break;
case '>':
out += '&gt;';
break;
default:
if (ch <= ' ') {
out += '&nbsp;';
} else {
out += ch;
}
break;
}
if (ch_width === 2) {
out += '</span>';
// Wrap wide characters so they're sized correctly. It's more difficult to release these
// from the object pool so just create new ones via innerHTML.
innerHTML += `<span class="xterm-wide-char">${ch}</span>`;
} else if (ch.charCodeAt(0) > 255) {
// Wrap any non-wide unicode character as some fonts size them badly
innerHTML += `<span class="xterm-normal-char">${ch}</span>`;
} else {
switch (ch) {
case '&':
innerHTML += '&amp;';
break;
case '<':
innerHTML += '&lt;';
break;
case '>':
innerHTML += '&gt;';
break;
default:
if (ch <= ' ') {
innerHTML += '&nbsp;';
} else {
innerHTML += ch;
}
break;
}
}
attr = data;
}
if (attr !== this._terminal.defAttr) {
out += '</span>';
if (innerHTML && !currentElement) {
currentElement = this._spanElementObjectPool.acquire();
}
if (currentElement) {
if (innerHTML) {
currentElement.innerHTML = innerHTML;
innerHTML = '';
}
documentFragment.appendChild(currentElement);
currentElement = null;
}
this._terminal.children[y].innerHTML = out;
this._terminal.children[y].appendChild(documentFragment);
}
if (parent) {
@@ -289,8 +321,7 @@ export class Renderer {
}
// if bold is broken, we can't
// use it in the terminal.
// If bold is broken, we can't use it in the terminal.
function checkBoldBroken(terminal) {
const document = terminal.ownerDocument;
const el = document.createElement('span');
+11
View File
@@ -16,3 +16,14 @@ describe('evaluateCopiedTextProcessing', function () {
assert.equal(processedText.indexOf(nonBreakingSpace), -1);
});
});
describe('evaluatePastedTextProcessing', function () {
it('should replace carriage return + line feed with line feed on windows', function () {
const pastedText = 'foo\r\nbar\r\n',
processedText = Clipboard.prepareTextForTerminal(pastedText, false),
windowsProcessedText = Clipboard.prepareTextForTerminal(pastedText, true);
assert.equal(processedText, 'foo\r\nbar\r\n');
assert.equal(windowsProcessedText, 'foo\nbar\n');
});
});
+14
View File
@@ -38,6 +38,17 @@ export function prepareTextForClipboard(text: string): string {
return processedText;
}
/**
* Prepares text to be pasted into the terminal by normalizing the line endings
* @param text The pasted text that needs processing before inserting into the terminal
*/
export function prepareTextForTerminal(text: string, isMSWindows: boolean): string {
if (isMSWindows) {
return text.replace(/\r?\n/g, '\n');
}
return text;
}
/**
* Binds copy functionality to the given terminal.
* @param {ClipboardEvent} ev The original copy event to be handled
@@ -68,8 +79,11 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal) {
let text: string;
let dispatchPaste = function(text) {
text = prepareTextForTerminal(text, term.browser.isMSWindows);
term.handler(text);
term.textarea.value = '';
term.emit('paste', text);
return term.cancel(ev);
};
+47
View File
@@ -0,0 +1,47 @@
import { assert } from 'chai';
import { DomElementObjectPool } from './DomElementObjectPool';
class MockDocument {
private _attr: {[key: string]: string} = {};
constructor() {}
public getAttribute(key: string): string { return this._attr[key]; };
public setAttribute(key: string, value: string): void { this._attr[key] = value; }
}
describe('DomElementObjectPool', () => {
let pool: DomElementObjectPool;
beforeEach(() => {
pool = new DomElementObjectPool('span');
(<any>global).document = {
createElement: () => new MockDocument()
};
});
it('should acquire distinct elements', () => {
const element1 = pool.acquire();
const element2 = pool.acquire();
assert.notEqual(element1, element2);
});
it('should acquire released elements', () => {
const element = pool.acquire();
pool.release(element);
assert.equal(pool.acquire(), element);
});
it('should handle a series of acquisitions and releases', () => {
const element1 = pool.acquire();
const element2 = pool.acquire();
pool.release(element1);
assert.equal(pool.acquire(), element1);
pool.release(element1);
pool.release(element2);
assert.equal(pool.acquire(), element2);
assert.equal(pool.acquire(), element1);
});
it('should throw when releasing an element that was not acquired', () => {
assert.throws(() => pool.release(document.createElement('span')));
});
});
+75
View File
@@ -0,0 +1,75 @@
/**
* @module xterm/utils/DomElementObjectPool
* @license MIT
*/
/**
* An object pool that manages acquisition and releasing of DOM elements for
* when reuse is desirable.
*/
export class DomElementObjectPool {
private static readonly OBJECT_ID_ATTRIBUTE = 'data-obj-id';
private static _objectCount = 0;
private _type: string;
private _pool: HTMLElement[];
private _inUse: {[key: string]: HTMLElement};
/**
* @param type The DOM element type (div, span, etc.).
*/
constructor(private type: string) {
this._type = type;
this._pool = [];
this._inUse = {};
}
/**
* Acquire an element from the pool, creating it if the pool is empty.
*/
public acquire(): HTMLElement {
let element: HTMLElement;
if (this._pool.length === 0) {
element = this._createNew();
} else {
element = this._pool.pop();
}
this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element;
return element;
}
/**
* Release an element back into the pool. It's up to the caller of this
* function to ensure that all external references to the element have been
* removed.
* @param element The element being released.
*/
public release(element: HTMLElement): void {
if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) {
throw new Error('Could not release an element not yet acquired');
}
delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)];
this._cleanElement(element);
this._pool.push(element);
}
/**
* Creates a new element for the pool.
*/
private _createNew(): HTMLElement {
const element = document.createElement(this._type);
const id = DomElementObjectPool._objectCount++;
element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10));
return element;
}
/**
* Resets an element back to a "clean state".
* @param element The element to be cleaned.
*/
private _cleanElement(element: HTMLElement): void {
element.className = '';
element.innerHTML = '';
}
}
+64
View File
@@ -0,0 +1,64 @@
/**
* @license MIT
*/
import { CharMeasure } from './CharMeasure';
/**
* Gets coordinates within the terminal for a particular mouse event. The result
* is returned as an array in the form [x, y] instead of an object as it's a
* little faster and this function is used in some low level code.
* @param event The mouse event.
* @param rowContainer The terminal's row container.
* @param charMeasure The char measure object used to determine character sizes.
*/
export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure): [number, number] {
// Ignore browsers that don't support MouseEvent.pageX
if (event.pageX == null) {
return null;
}
let x = event.pageX;
let y = event.pageY;
let el = rowContainer;
// Converts the coordinates from being relative to the document to being
// relative to the terminal.
while (el && el !== self.document.documentElement) {
x -= el.offsetLeft;
y -= el.offsetTop;
el = 'offsetParent' in el ? <HTMLElement>el.offsetParent : <HTMLElement>el.parentElement;
}
// Convert to cols/rows
x = Math.ceil(x / charMeasure.width);
y = Math.ceil(y / charMeasure.height);
return [x, y];
}
/**
* Gets coordinates within the terminal for a particular mouse event, wrapping
* them to the bounds of the terminal and adding 32 to both the x and y values
* as expected by xterm.
* @param event The mouse event.
* @param rowContainer The terminal's row container.
* @param charMeasure The char measure object used to determine character sizes.
* @param colCount The number of columns in the terminal.
* @param rowCount The number of rows in the terminal.
*/
export function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number): { x: number, y: number } {
const coords = getCoords(event, rowContainer, charMeasure);
let x = coords[0];
let y = coords[1];
// Ensure coordinates are within the terminal viewport.
x = Math.min(Math.max(x, 0), colCount);
y = Math.min(Math.max(y, 0), rowCount);
// xterm sends raw bytes and starts at 32 (SP) for each.
x += 32;
y += 32;
return { x, y };
}
+2 -1
View File
@@ -153,7 +153,8 @@
overflow-y: scroll;
}
.terminal .xterm-wide-char {
.terminal .xterm-wide-char,
.terminal .xterm-normal-char {
display: inline-block;
}
+13 -48
View File
@@ -24,6 +24,7 @@ import { CharMeasure } from './utils/CharMeasure';
import * as Browser from './utils/Browser';
import * as Keyboard from './utils/Keyboard';
import { CHARSETS } from './Charsets';
import { getRawByteCoords } from './utils/Mouse';
/**
* Terminal Emulation References:
@@ -760,7 +761,9 @@ Terminal.loadAddon = function(addon, callback) {
* character width has been changed.
*/
Terminal.prototype.updateCharSizeCSS = function() {
this.charSizeStyleElement.textContent = '.xterm-wide-char{width:' + (this.charMeasure.width * 2) + 'px;}';
this.charSizeStyleElement.textContent =
`.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` +
`.xterm-normal-char{width:${this.charMeasure.width}px;}`
}
/**
@@ -787,7 +790,7 @@ Terminal.prototype.bindMouse = function() {
button = getButton(ev);
// get mouse coordinates
pos = getCoords(ev);
pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
if (!pos) return;
sendEvent(button, pos);
@@ -815,7 +818,7 @@ Terminal.prototype.bindMouse = function() {
var button = pressed
, pos;
pos = getCoords(ev);
pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
if (!pos) return;
// buttons marked as motions
@@ -990,50 +993,6 @@ Terminal.prototype.bindMouse = function() {
return button;
}
// mouse coordinates measured in cols/rows
function getCoords(ev) {
var x, y, w, h, el;
// ignore browsers without pageX for now
if (ev.pageX == null) return;
x = ev.pageX;
y = ev.pageY;
el = self.element;
// should probably check offsetParent
// but this is more portable
while (el && el !== self.document.documentElement) {
x -= el.offsetLeft;
y -= el.offsetTop;
el = 'offsetParent' in el
? el.offsetParent
: el.parentNode;
}
// convert to cols/rows
x = Math.ceil(x / self.charMeasure.width);
y = Math.ceil(y / self.charMeasure.height);
// be sure to avoid sending
// bad positions to the program
if (x < 0) x = 0;
if (x > self.cols) x = self.cols;
if (y < 0) y = 0;
if (y > self.rows) y = self.rows;
// xterm sends raw bytes and
// starts at 32 (SP) for each.
x += 32;
y += 32;
return {
x: x,
y: y,
type: 'wheel'
};
}
on(el, 'mousedown', function(ev) {
if (!self.mouseEvents) return;
@@ -1293,7 +1252,13 @@ Terminal.prototype.innerWrite = function() {
this.refreshStart = this.y;
this.refreshEnd = this.y;
this.parser.parse(data);
// HACK: Set the parser state based on it's state at the time of return.
// This works around the bug #662 which saw the parser state reset in the
// middle of parsing escape sequence in two chunks. For some reason the
// state of the parser resets to 0 after exiting parser.parse. This change
// just sets the state back based on the correct return statement.
var state = this.parser.parse(data);
this.parser.setState(state);
this.updateRange(this.y);
this.refresh(this.refreshStart, this.refreshEnd);