Comparing version 2.7.0 to 2.8.0
{ | ||
"name": "xterm.js", | ||
"version": "2.7.0", | ||
"version": "2.8.0", | ||
"ignore": ["demo", "test", ".gitignore"], | ||
@@ -5,0 +5,0 @@ "main": [ |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function prepareTextForClipboard(text) { | ||
var space = String.fromCharCode(32), nonBreakingSpace = String.fromCharCode(160), allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'), processedText = text.split('\n').map(function (line) { | ||
var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space); | ||
return processedLine; | ||
}).join('\n'); | ||
return processedText; | ||
} | ||
exports.prepareTextForClipboard = prepareTextForClipboard; | ||
function prepareTextForTerminal(text, isMSWindows) { | ||
if (isMSWindows) { | ||
return text.replace(/\r?\n/g, '\n'); | ||
return text.replace(/\r?\n/g, '\r'); | ||
} | ||
@@ -18,9 +10,8 @@ return text; | ||
exports.prepareTextForTerminal = prepareTextForTerminal; | ||
function copyHandler(ev, term) { | ||
var copiedText = window.getSelection().toString(), text = prepareTextForClipboard(copiedText); | ||
function copyHandler(ev, term, selectionManager) { | ||
if (term.browser.isMSIE) { | ||
window.clipboardData.setData('Text', text); | ||
window.clipboardData.setData('Text', selectionManager.selectionText); | ||
} | ||
else { | ||
ev.clipboardData.setData('text/plain', text); | ||
ev.clipboardData.setData('text/plain', selectionManager.selectionText); | ||
} | ||
@@ -54,38 +45,27 @@ ev.preventDefault(); | ||
exports.pasteHandler = pasteHandler; | ||
function rightClickHandler(ev, term) { | ||
var s = document.getSelection(), selectedText = prepareTextForClipboard(s.toString()), clickIsOnSelection = false, x = ev.clientX, y = ev.clientY; | ||
if (s.rangeCount) { | ||
var r = s.getRangeAt(0), cr = r.getClientRects(); | ||
for (var i = 0; i < cr.length; i++) { | ||
var rect = cr[i]; | ||
clickIsOnSelection = ((x > rect.left) && (x < rect.right) && | ||
(y > rect.top) && (y < rect.bottom)); | ||
if (clickIsOnSelection) { | ||
break; | ||
} | ||
} | ||
if (selectedText.match(/^\s$/) || !selectedText.length) { | ||
clickIsOnSelection = false; | ||
} | ||
} | ||
if (!clickIsOnSelection) { | ||
term.textarea.style.position = 'fixed'; | ||
term.textarea.style.width = '20px'; | ||
term.textarea.style.height = '20px'; | ||
term.textarea.style.left = (x - 10) + 'px'; | ||
term.textarea.style.top = (y - 10) + 'px'; | ||
term.textarea.style.zIndex = '1000'; | ||
term.textarea.focus(); | ||
setTimeout(function () { | ||
term.textarea.style.position = null; | ||
term.textarea.style.width = null; | ||
term.textarea.style.height = null; | ||
term.textarea.style.left = null; | ||
term.textarea.style.top = null; | ||
term.textarea.style.zIndex = null; | ||
}, 4); | ||
} | ||
function moveTextAreaUnderMouseCursor(ev, textarea) { | ||
textarea.style.position = 'fixed'; | ||
textarea.style.width = '20px'; | ||
textarea.style.height = '20px'; | ||
textarea.style.left = (ev.clientX - 10) + 'px'; | ||
textarea.style.top = (ev.clientY - 10) + 'px'; | ||
textarea.style.zIndex = '1000'; | ||
textarea.focus(); | ||
setTimeout(function () { | ||
textarea.style.position = null; | ||
textarea.style.width = null; | ||
textarea.style.height = null; | ||
textarea.style.left = null; | ||
textarea.style.top = null; | ||
textarea.style.zIndex = null; | ||
}, 4); | ||
} | ||
exports.moveTextAreaUnderMouseCursor = moveTextAreaUnderMouseCursor; | ||
function rightClickHandler(ev, textarea, selectionManager) { | ||
moveTextAreaUnderMouseCursor(ev, textarea); | ||
textarea.value = selectionManager.selectionText; | ||
textarea.select(); | ||
} | ||
exports.rightClickHandler = rightClickHandler; | ||
//# sourceMappingURL=Clipboard.js.map |
@@ -5,9 +5,2 @@ "use strict"; | ||
var Clipboard = require("./Clipboard"); | ||
describe('evaluateCopiedTextProcessing', function () { | ||
it('should strip trailing whitespaces and replace nbsps with spaces', function () { | ||
var nonBreakingSpace = String.fromCharCode(160), copiedText = 'echo' + nonBreakingSpace + 'hello' + nonBreakingSpace, processedText = Clipboard.prepareTextForClipboard(copiedText); | ||
chai_1.assert.equal(processedText.match(/\s+$/), null); | ||
chai_1.assert.equal(processedText.indexOf(nonBreakingSpace), -1); | ||
}); | ||
}); | ||
describe('evaluatePastedTextProcessing', function () { | ||
@@ -17,3 +10,3 @@ it('should replace carriage return + line feed with line feed on windows', function () { | ||
chai_1.assert.equal(processedText, 'foo\r\nbar\r\n'); | ||
chai_1.assert.equal(windowsProcessedText, 'foo\nbar\n'); | ||
chai_1.assert.equal(windowsProcessedText, 'foo\rbar\r'); | ||
}); | ||
@@ -20,0 +13,0 @@ }); |
@@ -35,3 +35,3 @@ "use strict"; | ||
this._terminal.y--; | ||
this._terminal.scroll(); | ||
this._terminal.scroll(true); | ||
} | ||
@@ -50,4 +50,5 @@ } | ||
&& this._terminal.lines.get(row)[this._terminal.cols - 2] | ||
&& this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2) | ||
&& this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2) { | ||
this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1]; | ||
} | ||
this._terminal.lines.get(row).splice(this._terminal.x, 0, [this._terminal.curAttr, ' ', 1]); | ||
@@ -516,3 +517,4 @@ } | ||
this._terminal.mouseEvents = true; | ||
this._terminal.element.style.cursor = 'default'; | ||
this._terminal.element.classList.add('enable-mouse-events'); | ||
this._terminal.selectionManager.disable(); | ||
this._terminal.log('Binding to mouse events.'); | ||
@@ -607,3 +609,4 @@ break; | ||
this._terminal.mouseEvents = false; | ||
this._terminal.element.style.cursor = ''; | ||
this._terminal.element.classList.remove('enable-mouse-events'); | ||
this._terminal.selectionManager.enable(); | ||
break; | ||
@@ -639,2 +642,3 @@ case 1004: | ||
this._terminal.normal = null; | ||
this._terminal.selectionManager.setBuffer(this._terminal.lines); | ||
this._terminal.refresh(0, this._terminal.rows - 1); | ||
@@ -641,0 +645,0 @@ this._terminal.viewport.syncScrollArea(); |
@@ -143,2 +143,12 @@ "use strict"; | ||
} | ||
else if (node.childNodes.length > 1) { | ||
for (var j = 0; j < node.childNodes.length; j++) { | ||
var childNode = node.childNodes[j]; | ||
var childSearchIndex = childNode.textContent.indexOf(uri); | ||
if (childSearchIndex !== -1) { | ||
this._replaceNodeSubstringWithNode(childNode, linkElement, uri, childSearchIndex); | ||
break; | ||
} | ||
} | ||
} | ||
else { | ||
@@ -194,14 +204,13 @@ var nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex); | ||
Linkifier.prototype._replaceNodeSubstringWithNode = function (targetNode, newNode, substring, substringIndex) { | ||
var node = targetNode; | ||
if (node.nodeType !== 3) { | ||
node = node.childNodes[0]; | ||
if (targetNode.childNodes.length === 1) { | ||
targetNode = targetNode.childNodes[0]; | ||
} | ||
if (node.childNodes.length === 0 && node.nodeType !== 3) { | ||
if (targetNode.nodeType !== 3) { | ||
throw new Error('targetNode must be a text node or only contain a single text node'); | ||
} | ||
var fullText = node.textContent; | ||
var fullText = targetNode.textContent; | ||
if (substringIndex === 0) { | ||
var rightText_1 = fullText.substring(substring.length); | ||
var rightTextNode_1 = this._document.createTextNode(rightText_1); | ||
this._replaceNode(node, newNode, rightTextNode_1); | ||
this._replaceNode(targetNode, newNode, rightTextNode_1); | ||
return 0; | ||
@@ -212,3 +221,3 @@ } | ||
var leftTextNode_1 = this._document.createTextNode(leftText_1); | ||
this._replaceNode(node, leftTextNode_1, newNode); | ||
this._replaceNode(targetNode, leftTextNode_1, newNode); | ||
return 0; | ||
@@ -220,3 +229,3 @@ } | ||
var rightTextNode = this._document.createTextNode(rightText); | ||
this._replaceNode(node, leftTextNode, newNode, rightTextNode); | ||
this._replaceNode(targetNode, leftTextNode, newNode, rightTextNode); | ||
return 1; | ||
@@ -223,0 +232,0 @@ }; |
@@ -124,2 +124,5 @@ "use strict"; | ||
}); | ||
it('should not duplicate text after a unicode character (wrapped in a span)', function (done) { | ||
assertLinkifiesRow('echo \'<span class="xterm-normal-char">🔷</span>foo\'', /foo/, 'echo \'<span class="xterm-normal-char">🔷</span><a>foo</a>\'', done); | ||
}); | ||
}); | ||
@@ -126,0 +129,0 @@ describe('validationCallback', function () { |
@@ -129,3 +129,4 @@ "use strict"; | ||
if (data === -1) { | ||
currentElement.classList.add('reverse-video', 'terminal-cursor'); | ||
currentElement.classList.add('reverse-video'); | ||
currentElement.classList.add('terminal-cursor'); | ||
} | ||
@@ -226,2 +227,37 @@ else { | ||
; | ||
Renderer.prototype.refreshSelection = function (start, end) { | ||
while (this._terminal.selectionContainer.children.length) { | ||
this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]); | ||
} | ||
if (!start || !end) { | ||
return; | ||
} | ||
var viewportStartRow = start[1] - this._terminal.ydisp; | ||
var viewportEndRow = end[1] - this._terminal.ydisp; | ||
var viewportCappedStartRow = Math.max(viewportStartRow, 0); | ||
var viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1); | ||
if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) { | ||
return; | ||
} | ||
var documentFragment = document.createDocumentFragment(); | ||
var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; | ||
var endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; | ||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); | ||
var middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; | ||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); | ||
if (viewportCappedStartRow !== viewportCappedEndRow) { | ||
var endCol_1 = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; | ||
documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol_1)); | ||
} | ||
this._terminal.selectionContainer.appendChild(documentFragment); | ||
}; | ||
Renderer.prototype._createSelectionElement = function (row, colStart, colEnd, rowCount) { | ||
if (rowCount === void 0) { rowCount = 1; } | ||
var element = document.createElement('div'); | ||
element.style.height = rowCount * this._terminal.charMeasure.height + "px"; | ||
element.style.top = row * this._terminal.charMeasure.height + "px"; | ||
element.style.left = colStart * this._terminal.charMeasure.width + "px"; | ||
element.style.width = this._terminal.charMeasure.width * (colEnd - colStart) + "px"; | ||
return element; | ||
}; | ||
return Renderer; | ||
@@ -228,0 +264,0 @@ }()); |
@@ -50,2 +50,6 @@ var assert = require('chai').assert; | ||
}); | ||
it('should not allow scrollback less than number of rows', function () { | ||
var setOptionCall = xterm.setOption.bind(xterm, 'scrollback', xterm.rows - 1); | ||
assert.equal(setOptionCall(), false); | ||
}); | ||
}); | ||
@@ -334,3 +338,3 @@ describe('clear', function () { | ||
}); | ||
describe('attachCustomEventHandler', function () { | ||
describe('attachCustomKeyEventHandler', function () { | ||
var evKeyDown = { | ||
@@ -341,2 +345,7 @@ preventDefault: function () { }, | ||
}; | ||
var evKeyPress = { | ||
preventDefault: function () { }, | ||
stopPropagation: function () { }, | ||
type: 'keypress' | ||
}; | ||
beforeEach(function () { | ||
@@ -351,23 +360,33 @@ xterm.handler = function () { }; | ||
} | ||
}, | ||
keypress: { | ||
bind: function () { | ||
return function () { return true; }; | ||
} | ||
} | ||
}; | ||
}); | ||
it('should process the keydown event based on what the handler returns', function () { | ||
it('should process the keydown/keypress event based on what the handler returns', function () { | ||
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true); | ||
xterm.attachCustomKeydownHandler(function (ev) { | ||
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), true); | ||
xterm.attachCustomKeyEventHandler(function (ev) { | ||
return ev.keyCode === 77; | ||
}); | ||
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true); | ||
xterm.attachCustomKeydownHandler(function (ev) { | ||
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), true); | ||
xterm.attachCustomKeyEventHandler(function (ev) { | ||
return ev.keyCode !== 77; | ||
}); | ||
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); | ||
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false); | ||
}); | ||
it('should alive after reset(ESC c Full Reset (RIS))', function () { | ||
xterm.attachCustomKeydownHandler(function (ev) { | ||
xterm.attachCustomKeyEventHandler(function (ev) { | ||
return ev.keyCode !== 77; | ||
}); | ||
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); | ||
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false); | ||
xterm.reset(); | ||
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); | ||
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false); | ||
}); | ||
@@ -374,0 +393,0 @@ }); |
@@ -13,3 +13,4 @@ "use strict"; | ||
exports.isMSWindows = Generic_1.contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform); | ||
exports.isLinux = platform.indexOf('Linux') >= 0; | ||
//# sourceMappingURL=Browser.js.map |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var CircularList = (function () { | ||
var EventEmitter_1 = require("../EventEmitter"); | ||
var CircularList = (function (_super) { | ||
__extends(CircularList, _super); | ||
function CircularList(maxLength) { | ||
this._array = new Array(maxLength); | ||
this._startIndex = 0; | ||
this._length = 0; | ||
var _this = _super.call(this) || this; | ||
_this._array = new Array(maxLength); | ||
_this._startIndex = 0; | ||
_this._length = 0; | ||
return _this; | ||
} | ||
@@ -41,3 +55,10 @@ Object.defineProperty(CircularList.prototype, "maxLength", { | ||
get: function () { | ||
return this._array.forEach; | ||
var _this = this; | ||
return function (callbackfn) { | ||
var i = 0; | ||
var length = _this.length; | ||
for (var i_1 = 0; i_1 < length; i_1++) { | ||
callbackfn(_this.get(i_1), i_1); | ||
} | ||
}; | ||
}, | ||
@@ -60,2 +81,3 @@ enumerable: true, | ||
} | ||
this.emit('trim', 1); | ||
} | ||
@@ -88,4 +110,6 @@ else { | ||
if (this._length + items.length > this.maxLength) { | ||
this._startIndex += (this._length + items.length) - this.maxLength; | ||
var countToTrim = (this._length + items.length) - this.maxLength; | ||
this._startIndex += countToTrim; | ||
this._length = this.maxLength; | ||
this.emit('trim', countToTrim); | ||
} | ||
@@ -103,2 +127,3 @@ else { | ||
this._length -= count; | ||
this.emit('trim', count); | ||
}; | ||
@@ -125,2 +150,3 @@ CircularList.prototype.shiftElements = function (start, count, offset) { | ||
this._startIndex++; | ||
this.emit('trim', 1); | ||
} | ||
@@ -139,5 +165,5 @@ } | ||
return CircularList; | ||
}()); | ||
}(EventEmitter_1.EventEmitter)); | ||
exports.CircularList = CircularList; | ||
//# sourceMappingURL=CircularList.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function getCoords(event, rowContainer, charMeasure) { | ||
function getCoordsRelativeToElement(event, element) { | ||
if (event.pageX == null) { | ||
@@ -9,19 +9,23 @@ return null; | ||
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; | ||
while (element && element !== self.document.documentElement) { | ||
x -= element.offsetLeft; | ||
y -= element.offsetTop; | ||
element = 'offsetParent' in element ? element.offsetParent : element.parentElement; | ||
} | ||
x = Math.ceil(x / charMeasure.width); | ||
y = Math.ceil(y / charMeasure.height); | ||
return [x, y]; | ||
} | ||
exports.getCoordsRelativeToElement = getCoordsRelativeToElement; | ||
function getCoords(event, rowContainer, charMeasure, colCount, rowCount, isSelection) { | ||
var coords = getCoordsRelativeToElement(event, rowContainer); | ||
coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width); | ||
coords[1] = Math.ceil(coords[1] / charMeasure.height); | ||
coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1); | ||
coords[1] = Math.min(Math.max(coords[1], 1), rowCount + 1); | ||
return coords; | ||
} | ||
exports.getCoords = getCoords; | ||
function getRawByteCoords(event, rowContainer, charMeasure, colCount, rowCount) { | ||
var coords = getCoords(event, rowContainer, charMeasure); | ||
var coords = getCoords(event, rowContainer, charMeasure, colCount, rowCount); | ||
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; | ||
@@ -28,0 +32,0 @@ y += 32; |
@@ -30,2 +30,3 @@ "use strict"; | ||
this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px'; | ||
this.terminal.selectionContainer.style.height = this.viewportElement.style.height; | ||
} | ||
@@ -73,2 +74,16 @@ this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px'; | ||
; | ||
Viewport.prototype.onTouchStart = function (ev) { | ||
this.lastTouchY = ev.touches[0].pageY; | ||
}; | ||
; | ||
Viewport.prototype.onTouchMove = function (ev) { | ||
var deltaY = this.lastTouchY - ev.touches[0].pageY; | ||
this.lastTouchY = ev.touches[0].pageY; | ||
if (deltaY === 0) { | ||
return; | ||
} | ||
this.viewportElement.scrollTop += deltaY; | ||
ev.preventDefault(); | ||
}; | ||
; | ||
return Viewport; | ||
@@ -75,0 +90,0 @@ }()); |
@@ -8,2 +8,3 @@ "use strict"; | ||
var viewportElement; | ||
var selectionContainer; | ||
var charMeasure; | ||
@@ -23,2 +24,7 @@ var viewport; | ||
} | ||
}, | ||
selectionContainer: { | ||
style: { | ||
height: 0 | ||
} | ||
} | ||
@@ -25,0 +31,0 @@ }; |
141
lib/xterm.js
@@ -13,2 +13,3 @@ "use strict"; | ||
var Linkifier_1 = require("./Linkifier"); | ||
var SelectionManager_1 = require("./SelectionManager"); | ||
var CharMeasure_1 = require("./utils/CharMeasure"); | ||
@@ -77,3 +78,3 @@ var Browser = require("./utils/Browser"); | ||
this.scrollBottom = this.rows - 1; | ||
this.customKeydownHandler = null; | ||
this.customKeyEventHandler = null; | ||
this.cursorBlinkInterval = null; | ||
@@ -118,2 +119,3 @@ this.applicationKeypad = false; | ||
this.renderer = this.renderer || null; | ||
this.selectionManager = this.selectionManager || null; | ||
this.linkifier = this.linkifier || new Linkifier_1.Linkifier(); | ||
@@ -130,2 +132,5 @@ this.writeBuffer = []; | ||
} | ||
if (this.selectionManager) { | ||
this.selectionManager.setBuffer(this.lines); | ||
} | ||
this.tabs; | ||
@@ -232,2 +237,8 @@ this.setupStops(); | ||
case 'scrollback': | ||
if (value < this.rows) { | ||
var msg = 'Setting the scrollback value less than the number of rows '; | ||
msg += "(" + this.rows + ") is not allowed."; | ||
console.warn(msg); | ||
return false; | ||
} | ||
if (this.options[key] !== value) { | ||
@@ -312,2 +323,3 @@ if (this.lines.length > value) { | ||
Terminal.prototype.initGlobal = function () { | ||
var _this = this; | ||
var term = this; | ||
@@ -317,18 +329,15 @@ Terminal.bindKeys(this); | ||
Terminal.bindBlur(this); | ||
on(this.element, 'copy', function (ev) { | ||
Clipboard_1.copyHandler.call(this, ev, term); | ||
on(this.element, 'copy', function (event) { | ||
if (_this.mouseEvents) { | ||
return; | ||
} | ||
Clipboard_1.copyHandler(event, term, _this.selectionManager); | ||
}); | ||
on(this.textarea, 'paste', function (ev) { | ||
Clipboard_1.pasteHandler.call(this, ev, term); | ||
}); | ||
on(this.element, 'paste', function (ev) { | ||
Clipboard_1.pasteHandler.call(this, ev, term); | ||
}); | ||
function rightClickHandlerWrapper(ev) { | ||
Clipboard_1.rightClickHandler.call(this, ev, term); | ||
} | ||
var pasteHandlerWrapper = function (event) { return Clipboard_1.pasteHandler(event, term); }; | ||
on(this.textarea, 'paste', pasteHandlerWrapper); | ||
on(this.element, 'paste', pasteHandlerWrapper); | ||
if (term.browser.isFirefox) { | ||
on(this.element, 'mousedown', function (ev) { | ||
if (ev.button == 2) { | ||
rightClickHandlerWrapper(ev); | ||
on(this.element, 'mousedown', function (event) { | ||
if (event.button == 2) { | ||
Clipboard_1.rightClickHandler(event, _this.textarea, _this.selectionManager); | ||
} | ||
@@ -338,4 +347,13 @@ }); | ||
else { | ||
on(this.element, 'contextmenu', rightClickHandlerWrapper); | ||
on(this.element, 'contextmenu', function (event) { | ||
Clipboard_1.rightClickHandler(event, _this.textarea, _this.selectionManager); | ||
}); | ||
} | ||
if (term.browser.isLinux) { | ||
on(this.element, 'auxclick', function (event) { | ||
if (event.button === 1) { | ||
Clipboard_1.moveTextAreaUnderMouseCursor(event, _this.textarea, _this.selectionManager); | ||
} | ||
}); | ||
} | ||
}; | ||
@@ -384,2 +402,3 @@ Terminal.bindKeys = function (term) { | ||
Terminal.prototype.open = function (parent, focus) { | ||
var _this = this; | ||
var self = this, i = 0, div; | ||
@@ -398,3 +417,2 @@ this.parent = parent || this.parent; | ||
this.setCursorBlinking(this.options.cursorBlink); | ||
this.element.style.height; | ||
this.element.setAttribute('tabindex', 0); | ||
@@ -407,2 +425,5 @@ this.viewportElement = document.createElement('div'); | ||
this.viewportElement.appendChild(this.viewportScrollArea); | ||
this.selectionContainer = document.createElement('div'); | ||
this.selectionContainer.classList.add('xterm-selection'); | ||
this.element.appendChild(this.selectionContainer); | ||
this.rowContainer = document.createElement('div'); | ||
@@ -441,3 +462,3 @@ this.rowContainer.classList.add('xterm-rows'); | ||
this.charMeasure.on('charsizechanged', function () { | ||
self.updateCharSizeCSS(); | ||
self.updateCharSizeStyles(); | ||
}); | ||
@@ -447,2 +468,13 @@ this.charMeasure.measure(); | ||
this.renderer = new Renderer_1.Renderer(this); | ||
this.selectionManager = new SelectionManager_1.SelectionManager(this, this.lines, this.rowContainer, this.charMeasure); | ||
this.selectionManager.on('refresh', function (data) { | ||
_this.renderer.refreshSelection(data.start, data.end); | ||
}); | ||
this.selectionManager.on('newselection', function (text) { | ||
_this.textarea.value = text; | ||
_this.textarea.focus(); | ||
_this.textarea.select(); | ||
}); | ||
this.on('scroll', function () { return _this.selectionManager.refresh(); }); | ||
this.viewportElement.addEventListener('scroll', function () { return _this.selectionManager.refresh(); }); | ||
this.refresh(0, this.rows - 1); | ||
@@ -481,6 +513,7 @@ this.initGlobal(); | ||
}; | ||
Terminal.prototype.updateCharSizeCSS = function () { | ||
Terminal.prototype.updateCharSizeStyles = function () { | ||
this.charSizeStyleElement.textContent = | ||
".xterm-wide-char{width:" + this.charMeasure.width * 2 + "px;}" + | ||
(".xterm-normal-char{width:" + this.charMeasure.width + "px;}"); | ||
(".xterm-normal-char{width:" + this.charMeasure.width + "px;}") + | ||
(".xterm-rows > div{height:" + this.charMeasure.height + "px;}"); | ||
}; | ||
@@ -687,2 +720,14 @@ Terminal.prototype.bindMouse = function () { | ||
}); | ||
on(el, 'touchstart', function (ev) { | ||
if (self.mouseEvents) | ||
return; | ||
self.viewport.onTouchStart(ev); | ||
return self.cancel(ev); | ||
}); | ||
on(el, 'touchmove', function (ev) { | ||
if (self.mouseEvents) | ||
return; | ||
self.viewport.onTouchMove(ev); | ||
return self.cancel(ev); | ||
}); | ||
}; | ||
@@ -717,3 +762,3 @@ Terminal.prototype.destroy = function () { | ||
}; | ||
Terminal.prototype.scroll = function () { | ||
Terminal.prototype.scroll = function (isWrapped) { | ||
var row; | ||
@@ -734,6 +779,6 @@ if (this.lines.length === this.lines.maxLength) { | ||
if (row === this.lines.length) { | ||
this.lines.push(this.blankLine()); | ||
this.lines.push(this.blankLine(undefined, isWrapped)); | ||
} | ||
else { | ||
this.lines.splice(row, 0, this.blankLine()); | ||
this.lines.splice(row, 0, this.blankLine(undefined, isWrapped)); | ||
} | ||
@@ -755,2 +800,5 @@ if (this.scrollTop !== 0) { | ||
if (disp < 0) { | ||
if (this.ydisp === 0) { | ||
return; | ||
} | ||
this.userScrolling = true; | ||
@@ -826,4 +874,9 @@ } | ||
Terminal.prototype.attachCustomKeydownHandler = function (customKeydownHandler) { | ||
this.customKeydownHandler = customKeydownHandler; | ||
var message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.'; | ||
console.warn(message); | ||
this.attachCustomKeyEventHandler(customKeydownHandler); | ||
}; | ||
Terminal.prototype.attachCustomKeyEventHandler = function (customKeyEventHandler) { | ||
this.customKeyEventHandler = customKeyEventHandler; | ||
}; | ||
Terminal.prototype.setHypertextLinkHandler = function (handler) { | ||
@@ -836,7 +889,7 @@ if (!this.linkifier) { | ||
}; | ||
Terminal.prototype.setHypertextValidationCallback = function (handler) { | ||
Terminal.prototype.setHypertextValidationCallback = function (callback) { | ||
if (!this.linkifier) { | ||
throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); | ||
} | ||
this.linkifier.setHypertextValidationCallback(handler); | ||
this.linkifier.setHypertextValidationCallback(callback); | ||
this.refresh(0, this.rows - 1); | ||
@@ -858,4 +911,16 @@ }; | ||
}; | ||
Terminal.prototype.hasSelection = function () { | ||
return this.selectionManager.hasSelection; | ||
}; | ||
Terminal.prototype.getSelection = function () { | ||
return this.selectionManager.selectionText; | ||
}; | ||
Terminal.prototype.clearSelection = function () { | ||
this.selectionManager.clearSelection(); | ||
}; | ||
Terminal.prototype.selectAll = function () { | ||
this.selectionManager.selectAll(); | ||
}; | ||
Terminal.prototype.keyDown = function (ev) { | ||
if (this.customKeydownHandler && this.customKeydownHandler(ev) === false) { | ||
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { | ||
return false; | ||
@@ -1160,2 +1225,7 @@ } | ||
} | ||
else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { | ||
if (ev.keyCode === 65) { | ||
this.selectAll(); | ||
} | ||
} | ||
break; | ||
@@ -1177,2 +1247,5 @@ } | ||
var key; | ||
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { | ||
return false; | ||
} | ||
this.cancel(ev); | ||
@@ -1199,3 +1272,3 @@ if (ev.charCode) { | ||
this.handler(key); | ||
return false; | ||
return true; | ||
}; | ||
@@ -1243,2 +1316,5 @@ Terminal.prototype.send = function (data) { | ||
} | ||
if (y > this.getOption('scrollback')) { | ||
this.setOption('scrollback', y); | ||
} | ||
var line, el, i, j, ch, addToY; | ||
@@ -1406,3 +1482,3 @@ if (x === this.cols && y === this.rows) { | ||
}; | ||
Terminal.prototype.blankLine = function (cur) { | ||
Terminal.prototype.blankLine = function (cur, isWrapped) { | ||
var attr = cur | ||
@@ -1412,2 +1488,5 @@ ? this.eraseAttr() | ||
var ch = [attr, ' ', 1], line = [], i = 0; | ||
if (isWrapped) { | ||
line.isWrapped = isWrapped; | ||
} | ||
for (; i < this.cols; i++) { | ||
@@ -1464,6 +1543,6 @@ line[i] = ch; | ||
this.options.cols = this.cols; | ||
var customKeydownHandler = this.customKeydownHandler; | ||
var customKeyEventHandler = this.customKeyEventHandler; | ||
var cursorBlinkInterval = this.cursorBlinkInterval; | ||
Terminal.call(this, this.options); | ||
this.customKeydownHandler = customKeydownHandler; | ||
this.customKeyEventHandler = customKeyEventHandler; | ||
this.cursorBlinkInterval = cursorBlinkInterval; | ||
@@ -1470,0 +1549,0 @@ this.refresh(0, this.rows - 1); |
{ | ||
"name": "xterm", | ||
"description": "Full xterm terminal, in your browser", | ||
"version": "2.7.0", | ||
"version": "2.8.0", | ||
"ignore": [ | ||
@@ -6,0 +6,0 @@ "demo", |
@@ -39,2 +39,4 @@ # [![xterm.js logo](logo.png)](https://xtermjs.org) | ||
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud. | ||
- [**Gravitational Teleport**](https://github.com/gravitational/teleport): Gravitational Teleport is a modern SSH server for remotely accessing clusters of Linux servers via SSH or HTTPS. | ||
- [**Hexlet**](https://en.hexlet.io): Practical programming courses (JavaScript, PHP, Unix, databases, functional programming). A steady path from the first line of code to the first job. | ||
@@ -41,0 +43,0 @@ Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. |
@@ -5,17 +5,2 @@ import { assert } from 'chai'; | ||
describe('evaluateCopiedTextProcessing', function () { | ||
it('should strip trailing whitespaces and replace nbsps with spaces', function () { | ||
let nonBreakingSpace = String.fromCharCode(160), | ||
copiedText = 'echo' + nonBreakingSpace + 'hello' + nonBreakingSpace, | ||
processedText = Clipboard.prepareTextForClipboard(copiedText); | ||
// No trailing spaces | ||
assert.equal(processedText.match(/\s+$/), null); | ||
// No non-breaking space | ||
assert.equal(processedText.indexOf(nonBreakingSpace), -1); | ||
}); | ||
}); | ||
describe('evaluatePastedTextProcessing', function () { | ||
@@ -28,4 +13,4 @@ it('should replace carriage return + line feed with line feed on windows', function () { | ||
assert.equal(processedText, 'foo\r\nbar\r\n'); | ||
assert.equal(windowsProcessedText, 'foo\nbar\n'); | ||
assert.equal(windowsProcessedText, 'foo\rbar\r'); | ||
}); | ||
}); |
@@ -8,3 +8,3 @@ /** | ||
import { ITerminal } from '../Interfaces'; | ||
import { ITerminal, ISelectionManager } from '../Interfaces'; | ||
@@ -21,24 +21,2 @@ interface IWindow extends Window { | ||
/** | ||
* Prepares text copied from terminal selection, to be saved in the clipboard by: | ||
* 1. stripping all trailing white spaces | ||
* 2. converting all non-breaking spaces to regular spaces | ||
* @param {string} text The copied text that needs processing for storing in clipboard | ||
* @returns {string} | ||
*/ | ||
export function prepareTextForClipboard(text: string): string { | ||
let space = String.fromCharCode(32), | ||
nonBreakingSpace = String.fromCharCode(160), | ||
allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'), | ||
processedText = text.split('\n').map(function (line) { | ||
// Strip all trailing white spaces and convert all non-breaking spaces | ||
// to regular spaces. | ||
let processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space); | ||
return processedLine; | ||
}).join('\n'); | ||
return processedText; | ||
} | ||
/** | ||
* Prepares text to be pasted into the terminal by normalizing the line endings | ||
@@ -49,3 +27,3 @@ * @param text The pasted text that needs processing before inserting into the terminal | ||
if (isMSWindows) { | ||
return text.replace(/\r?\n/g, '\n'); | ||
return text.replace(/\r?\n/g, '\r'); | ||
} | ||
@@ -59,15 +37,11 @@ return text; | ||
*/ | ||
export function copyHandler(ev: ClipboardEvent, term: ITerminal) { | ||
// We cast `window` to `any` type, because TypeScript has not declared the `clipboardData` | ||
// property that we use below for Internet Explorer. | ||
let copiedText = window.getSelection().toString(), | ||
text = prepareTextForClipboard(copiedText); | ||
export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager) { | ||
if (term.browser.isMSIE) { | ||
window.clipboardData.setData('Text', text); | ||
window.clipboardData.setData('Text', selectionManager.selectionText); | ||
} else { | ||
ev.clipboardData.setData('text/plain', text); | ||
ev.clipboardData.setData('text/plain', selectionManager.selectionText); | ||
} | ||
ev.preventDefault(); // Prevent or the original text will be copied. | ||
// Prevent or the original text will be copied. | ||
ev.preventDefault(); | ||
} | ||
@@ -108,66 +82,40 @@ | ||
/** | ||
* Bind to right-click event and allow right-click copy and paste. | ||
* | ||
* **Logic** | ||
* If text is selected and right-click happens on selected text, then | ||
* do nothing to allow seamless copying. | ||
* If no text is selected or right-click is outside of the selection | ||
* area, then bring the terminal's input below the cursor, in order to | ||
* trigger the event on the textarea and allow-right click paste, without | ||
* caring about disappearing selection. | ||
* @param {MouseEvent} ev The original right click event to be handled | ||
* @param {Terminal} term The terminal on which to apply the handled paste event | ||
* Moves the textarea under the mouse cursor and focuses it. | ||
* @param ev The original right click event to be handled. | ||
* @param textarea The terminal's textarea. | ||
*/ | ||
export function rightClickHandler(ev: MouseEvent, term: ITerminal) { | ||
let s = document.getSelection(), | ||
selectedText = prepareTextForClipboard(s.toString()), | ||
clickIsOnSelection = false, | ||
x = ev.clientX, | ||
y = ev.clientY; | ||
export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement) { | ||
// Bring textarea at the cursor position | ||
textarea.style.position = 'fixed'; | ||
textarea.style.width = '20px'; | ||
textarea.style.height = '20px'; | ||
textarea.style.left = (ev.clientX - 10) + 'px'; | ||
textarea.style.top = (ev.clientY - 10) + 'px'; | ||
textarea.style.zIndex = '1000'; | ||
if (s.rangeCount) { | ||
let r = s.getRangeAt(0), | ||
cr = r.getClientRects(); | ||
textarea.focus(); | ||
for (let i = 0; i < cr.length; i++) { | ||
let rect = cr[i]; | ||
// Reset the terminal textarea's styling | ||
setTimeout(function () { | ||
textarea.style.position = null; | ||
textarea.style.width = null; | ||
textarea.style.height = null; | ||
textarea.style.left = null; | ||
textarea.style.top = null; | ||
textarea.style.zIndex = null; | ||
}, 4); | ||
} | ||
clickIsOnSelection = ( | ||
(x > rect.left) && (x < rect.right) && | ||
(y > rect.top) && (y < rect.bottom) | ||
); | ||
/** | ||
* Bind to right-click event and allow right-click copy and paste. | ||
* @param ev The original right click event to be handled. | ||
* @param textarea The terminal's textarea. | ||
* @param selectionManager The terminal's selection manager. | ||
*/ | ||
export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager) { | ||
moveTextAreaUnderMouseCursor(ev, textarea); | ||
if (clickIsOnSelection) { | ||
break; | ||
} | ||
} | ||
// If we clicked on selection and selection is not a single space, | ||
// then mark the right click as copy-only. We check for the single | ||
// space selection, as this can happen when clicking on an | ||
// and there is not much pointing in copying a single space. | ||
if (selectedText.match(/^\s$/) || !selectedText.length) { | ||
clickIsOnSelection = false; | ||
} | ||
} | ||
// Bring textarea at the cursor position | ||
if (!clickIsOnSelection) { | ||
term.textarea.style.position = 'fixed'; | ||
term.textarea.style.width = '20px'; | ||
term.textarea.style.height = '20px'; | ||
term.textarea.style.left = (x - 10) + 'px'; | ||
term.textarea.style.top = (y - 10) + 'px'; | ||
term.textarea.style.zIndex = '1000'; | ||
term.textarea.focus(); | ||
// Reset the terminal textarea's styling | ||
setTimeout(function () { | ||
term.textarea.style.position = null; | ||
term.textarea.style.width = null; | ||
term.textarea.style.height = null; | ||
term.textarea.style.left = null; | ||
term.textarea.style.top = null; | ||
term.textarea.style.zIndex = null; | ||
}, 4); | ||
} | ||
// Get textarea ready to copy from the context menu | ||
textarea.value = selectionManager.selectionText; | ||
textarea.select(); | ||
} |
@@ -60,3 +60,3 @@ /** | ||
this._terminal.y--; | ||
this._terminal.scroll(); | ||
this._terminal.scroll(true); | ||
} | ||
@@ -79,4 +79,5 @@ } else { | ||
&& this._terminal.lines.get(row)[this._terminal.cols - 2] | ||
&& this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2) | ||
&& this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2) { | ||
this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1]; | ||
} | ||
@@ -908,3 +909,4 @@ // insert empty cell at cursor | ||
this._terminal.mouseEvents = true; | ||
this._terminal.element.style.cursor = 'default'; | ||
this._terminal.element.classList.add('enable-mouse-events'); | ||
this._terminal.selectionManager.disable(); | ||
this._terminal.log('Binding to mouse events.'); | ||
@@ -1102,3 +1104,4 @@ break; | ||
this._terminal.mouseEvents = false; | ||
this._terminal.element.style.cursor = ''; | ||
this._terminal.element.classList.remove('enable-mouse-events'); | ||
this._terminal.selectionManager.enable(); | ||
break; | ||
@@ -1134,2 +1137,4 @@ case 1004: // send focusin/focusout events | ||
this._terminal.normal = null; | ||
// Ensure the selection manager has the correct buffer | ||
this._terminal.selectionManager.setBuffer(this._terminal.lines); | ||
// if (params === 1049) { | ||
@@ -1136,0 +1141,0 @@ // this.x = this.savedX; |
@@ -23,2 +23,4 @@ /** | ||
rowContainer: HTMLElement; | ||
selectionContainer: HTMLElement; | ||
charMeasure: ICharMeasure; | ||
textarea: HTMLTextAreaElement; | ||
@@ -51,2 +53,6 @@ ybase: number; | ||
export interface ISelectionManager { | ||
selectionText: string; | ||
} | ||
export interface ICharMeasure { | ||
@@ -53,0 +59,0 @@ width: number; |
@@ -122,2 +122,8 @@ /** | ||
}); | ||
it('should not duplicate text after a unicode character (wrapped in a span)', done => { | ||
// This is a regression test for an issue that came about when using | ||
// an oh-my-zsh theme that added the large blue diamond unicode | ||
// character (U+1F537) which caused the path to be duplicated. See #642. | ||
assertLinkifiesRow('echo \'<span class="xterm-normal-char">🔷</span>foo\'', /foo/, 'echo \'<span class="xterm-normal-char">🔷</span><a>foo</a>\'', done); | ||
}); | ||
}); | ||
@@ -124,0 +130,0 @@ |
@@ -203,3 +203,3 @@ /** | ||
* @param {LinkMatcher} matcher The link matcher for this line. | ||
* @return The link element if it was added, otherwise undefined. | ||
* @return The link element(s) that were added. | ||
*/ | ||
@@ -239,4 +239,17 @@ private _doLinkifyRow(row: HTMLElement, matcher: LinkMatcher): HTMLElement[] { | ||
} | ||
} else if (node.childNodes.length > 1) { | ||
// Matches part of string in an element with multiple child nodes | ||
for (let j = 0; j < node.childNodes.length; j++) { | ||
const childNode = node.childNodes[j]; | ||
const childSearchIndex = childNode.textContent.indexOf(uri); | ||
if (childSearchIndex !== -1) { | ||
// Match found in currentNode | ||
this._replaceNodeSubstringWithNode(childNode, linkElement, uri, childSearchIndex); | ||
// Don't need to count nodesAdded by replacing the node as this | ||
// is a child node, not a top-level node. | ||
break; | ||
} | ||
} | ||
} else { | ||
// Matches part of string | ||
// Matches part of string in a single text node | ||
const nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex); | ||
@@ -313,5 +326,6 @@ // No need to consider the new nodes | ||
private _replaceNodeSubstringWithNode(targetNode: Node, newNode: Node, substring: string, substringIndex: number): number { | ||
let node = targetNode; | ||
if (node.nodeType !== 3/*Node.TEXT_NODE*/) { | ||
node = node.childNodes[0]; | ||
// If the targetNode is a non-text node with a single child, make the child | ||
// the new targetNode. | ||
if (targetNode.childNodes.length === 1) { | ||
targetNode = targetNode.childNodes[0]; | ||
} | ||
@@ -322,7 +336,7 @@ | ||
// text nodes potentially on either side. | ||
if (node.childNodes.length === 0 && node.nodeType !== 3/*Node.TEXT_NODE*/) { | ||
if (targetNode.nodeType !== 3/*Node.TEXT_NODE*/) { | ||
throw new Error('targetNode must be a text node or only contain a single text node'); | ||
} | ||
const fullText = node.textContent; | ||
const fullText = targetNode.textContent; | ||
@@ -333,3 +347,3 @@ if (substringIndex === 0) { | ||
const rightTextNode = this._document.createTextNode(rightText); | ||
this._replaceNode(node, newNode, rightTextNode); | ||
this._replaceNode(targetNode, newNode, rightTextNode); | ||
return 0; | ||
@@ -342,3 +356,3 @@ } | ||
const leftTextNode = this._document.createTextNode(leftText); | ||
this._replaceNode(node, leftTextNode, newNode); | ||
this._replaceNode(targetNode, leftTextNode, newNode); | ||
return 0; | ||
@@ -352,5 +366,5 @@ } | ||
const rightTextNode = this._document.createTextNode(rightText); | ||
this._replaceNode(node, leftTextNode, newNode, rightTextNode); | ||
this._replaceNode(targetNode, leftTextNode, newNode, rightTextNode); | ||
return 1; | ||
} | ||
} |
@@ -203,3 +203,4 @@ /** | ||
if (data === -1) { | ||
currentElement.classList.add('reverse-video', 'terminal-cursor'); | ||
currentElement.classList.add('reverse-video'); | ||
currentElement.classList.add('terminal-cursor'); | ||
} else { | ||
@@ -322,2 +323,62 @@ let bg = data & 0x1ff; | ||
}; | ||
/** | ||
* Refreshes the selection in the DOM. | ||
* @param start The selection start. | ||
* @param end The selection end. | ||
*/ | ||
public refreshSelection(start: [number, number], end: [number, number]) { | ||
// Remove all selections | ||
while (this._terminal.selectionContainer.children.length) { | ||
this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]); | ||
} | ||
// Selection does not exist | ||
if (!start || !end) { | ||
return; | ||
} | ||
// Translate from buffer position to viewport position | ||
const viewportStartRow = start[1] - this._terminal.ydisp; | ||
const viewportEndRow = end[1] - this._terminal.ydisp; | ||
const viewportCappedStartRow = Math.max(viewportStartRow, 0); | ||
const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1); | ||
// No need to draw the selection | ||
if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) { | ||
return; | ||
} | ||
// Create the selections | ||
const documentFragment = document.createDocumentFragment(); | ||
// Draw first row | ||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; | ||
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; | ||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); | ||
// Draw middle rows | ||
const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; | ||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); | ||
// Draw final row | ||
if (viewportCappedStartRow !== viewportCappedEndRow) { | ||
// Only draw viewportEndRow if it's not the same as viewporttartRow | ||
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; | ||
documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol)); | ||
} | ||
this._terminal.selectionContainer.appendChild(documentFragment); | ||
} | ||
/** | ||
* Creates a selection element at the specified position. | ||
* @param row The row of the selection. | ||
* @param colStart The start column. | ||
* @param colEnd The end columns. | ||
*/ | ||
private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement { | ||
const element = document.createElement('div'); | ||
element.style.height = `${rowCount * this._terminal.charMeasure.height}px`; | ||
element.style.top = `${row * this._terminal.charMeasure.height}px`; | ||
element.style.left = `${colStart * this._terminal.charMeasure.width}px`; | ||
element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`; | ||
return element; | ||
} | ||
} | ||
@@ -324,0 +385,0 @@ |
@@ -58,2 +58,7 @@ var assert = require('chai').assert; | ||
}); | ||
it('should not allow scrollback less than number of rows', function() { | ||
let setOptionCall = xterm.setOption.bind(xterm, 'scrollback', xterm.rows - 1); | ||
assert.equal(setOptionCall(), false); | ||
}); | ||
}); | ||
@@ -375,3 +380,3 @@ | ||
describe('attachCustomEventHandler', function () { | ||
describe('attachCustomKeyEventHandler', function () { | ||
var evKeyDown = { | ||
@@ -382,2 +387,7 @@ preventDefault: function() {}, | ||
} | ||
var evKeyPress = { | ||
preventDefault: function() {}, | ||
stopPropagation: function() {}, | ||
type: 'keypress' | ||
} | ||
@@ -393,2 +403,7 @@ beforeEach(function() { | ||
} | ||
}, | ||
keypress: { | ||
bind: function() { | ||
return function () { return true; } | ||
} | ||
} | ||
@@ -398,21 +413,26 @@ } | ||
it('should process the keydown event based on what the handler returns', function () { | ||
it('should process the keydown/keypress event based on what the handler returns', function () { | ||
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true); | ||
xterm.attachCustomKeydownHandler(function (ev) { | ||
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), true); | ||
xterm.attachCustomKeyEventHandler(function (ev) { | ||
return ev.keyCode === 77; | ||
}); | ||
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true); | ||
xterm.attachCustomKeydownHandler(function (ev) { | ||
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), true); | ||
xterm.attachCustomKeyEventHandler(function (ev) { | ||
return ev.keyCode !== 77; | ||
}); | ||
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); | ||
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false); | ||
}); | ||
it('should alive after reset(ESC c Full Reset (RIS))', function () { | ||
xterm.attachCustomKeydownHandler(function (ev) { | ||
xterm.attachCustomKeyEventHandler(function (ev) { | ||
return ev.keyCode !== 77; | ||
}); | ||
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); | ||
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false); | ||
xterm.reset(); | ||
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); | ||
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false); | ||
}); | ||
@@ -419,0 +439,0 @@ }); |
@@ -23,1 +23,2 @@ /** | ||
export const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform); | ||
export const isLinux = platform.indexOf('Linux') >= 0; |
@@ -7,3 +7,5 @@ /** | ||
*/ | ||
export class CircularList<T> { | ||
import { EventEmitter } from '../EventEmitter'; | ||
export class CircularList<T> extends EventEmitter { | ||
private _array: T[]; | ||
@@ -14,2 +16,3 @@ private _startIndex: number; | ||
constructor(maxLength: number) { | ||
super(); | ||
this._array = new Array<T>(maxLength); | ||
@@ -48,4 +51,10 @@ this._startIndex = 0; | ||
public get forEach(): (callbackfn: (value: T, index: number, array: T[]) => void) => void { | ||
return this._array.forEach; | ||
public get forEach(): (callbackfn: (value: T, index: number) => void) => void { | ||
return (callbackfn: (value: T, index: number) => void) => { | ||
let i = 0; | ||
let length = this.length; | ||
for (let i = 0; i < length; i++) { | ||
callbackfn(this.get(i), i); | ||
} | ||
}; | ||
} | ||
@@ -89,2 +98,3 @@ | ||
} | ||
this.emit('trim', 1); | ||
} else { | ||
@@ -113,2 +123,3 @@ this._length++; | ||
public splice(start: number, deleteCount: number, ...items: T[]): void { | ||
// Delete items | ||
if (deleteCount) { | ||
@@ -120,3 +131,5 @@ for (let i = start; i < this._length - deleteCount; i++) { | ||
} | ||
if (items && items.length) { | ||
// Add items | ||
for (let i = this._length - 1; i >= start; i--) { | ||
@@ -129,5 +142,8 @@ this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)]; | ||
// Adjust length as needed | ||
if (this._length + items.length > this.maxLength) { | ||
this._startIndex += (this._length + items.length) - this.maxLength; | ||
const countToTrim = (this._length + items.length) - this.maxLength; | ||
this._startIndex += countToTrim; | ||
this._length = this.maxLength; | ||
this.emit('trim', countToTrim); | ||
} else { | ||
@@ -149,2 +165,3 @@ this._length += items.length; | ||
this._length -= count; | ||
this.emit('trim', count); | ||
} | ||
@@ -173,2 +190,3 @@ | ||
this._startIndex++; | ||
this.emit('trim', 1); | ||
} | ||
@@ -175,0 +193,0 @@ } |
@@ -7,11 +7,3 @@ /** | ||
/** | ||
* 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] { | ||
export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLElement): [number, number] { | ||
// Ignore browsers that don't support MouseEvent.pageX | ||
@@ -24,17 +16,38 @@ if (event.pageX == null) { | ||
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; | ||
while (element && element !== self.document.documentElement) { | ||
x -= element.offsetLeft; | ||
y -= element.offsetTop; | ||
element = 'offsetParent' in element ? <HTMLElement>element.offsetParent : <HTMLElement>element.parentElement; | ||
} | ||
return [x, y]; | ||
} | ||
// Convert to cols/rows | ||
x = Math.ceil(x / charMeasure.width); | ||
y = Math.ceil(y / charMeasure.height); | ||
/** | ||
* 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. | ||
* @param colCount The number of columns in the terminal. | ||
* @param rowCount The number of rows n the terminal. | ||
* @param isSelection Whether the request is for the selection or not. This will | ||
* apply an offset to the x value such that the left half of the cell will | ||
* select that cell and the right half will select the next cell. | ||
*/ | ||
export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { | ||
const coords = getCoordsRelativeToElement(event, rowContainer); | ||
return [x, y]; | ||
// Convert to cols/rows. | ||
coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width); | ||
coords[1] = Math.ceil(coords[1] / charMeasure.height); | ||
// Ensure coordinates are within the terminal viewport. | ||
coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1); | ||
coords[1] = Math.min(Math.max(coords[1], 1), rowCount + 1); | ||
return coords; | ||
} | ||
@@ -53,10 +66,6 @@ | ||
export function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number): { x: number, y: number } { | ||
const coords = getCoords(event, rowContainer, charMeasure); | ||
const coords = getCoords(event, rowContainer, charMeasure, colCount, rowCount); | ||
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. | ||
@@ -63,0 +72,0 @@ x += 32; |
@@ -7,2 +7,3 @@ import { assert } from 'chai'; | ||
let viewportElement; | ||
let selectionContainer; | ||
let charMeasure; | ||
@@ -24,2 +25,7 @@ let viewport; | ||
} | ||
}, | ||
selectionContainer: { | ||
style: { | ||
height: 0 | ||
} | ||
} | ||
@@ -26,0 +32,0 @@ }; |
@@ -16,2 +16,3 @@ /** | ||
private lastRecordedViewportHeight: number; | ||
private lastTouchY: number; | ||
@@ -61,2 +62,3 @@ /** | ||
this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px'; | ||
this.terminal.selectionContainer.style.height = this.viewportElement.style.height; | ||
} | ||
@@ -125,2 +127,24 @@ this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px'; | ||
}; | ||
/** | ||
* Handles the touchstart event, recording the touch occurred. | ||
* @param ev The touch event. | ||
*/ | ||
public onTouchStart(ev: TouchEvent) { | ||
this.lastTouchY = ev.touches[0].pageY; | ||
}; | ||
/** | ||
* Handles the touchmove event, scrolling the viewport if the position shifted. | ||
* @param ev The touch event. | ||
*/ | ||
public onTouchMove(ev: TouchEvent) { | ||
let deltaY = this.lastTouchY - ev.touches[0].pageY; | ||
this.lastTouchY = ev.touches[0].pageY; | ||
if (deltaY === 0) { | ||
return; | ||
} | ||
this.viewportElement.scrollTop += deltaY; | ||
ev.preventDefault(); | ||
}; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1543010
147
27950
141