Comparing version 3.5.1 to 3.6.0
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.winptyCompat = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var CHAR_DATA_CODE_INDEX = 3; | ||
var NULL_CELL_CODE = 32; | ||
function winptyCompatInit(terminal) { | ||
@@ -13,3 +15,3 @@ var addonTerminal = terminal; | ||
var lastChar = line[addonTerminal.cols - 1]; | ||
if (lastChar[3] !== 32) { | ||
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE) { | ||
var nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y); | ||
@@ -16,0 +18,0 @@ nextLine.isWrapped = true; |
@@ -49,3 +49,3 @@ "use strict"; | ||
_this.register(_this._renderRowsDebouncer); | ||
_this.register(_this._terminal.addDisposableListener('resize', function (data) { return _this._onResize(data.cols, data.rows); })); | ||
_this.register(_this._terminal.addDisposableListener('resize', function (data) { return _this._onResize(data.rows); })); | ||
_this.register(_this._terminal.addDisposableListener('refresh', function (data) { return _this._refreshRows(data.start, data.end); })); | ||
@@ -110,3 +110,3 @@ _this.register(_this._terminal.addDisposableListener('scroll', function (data) { return _this._refreshRows(); })); | ||
}; | ||
AccessibilityManager.prototype._onResize = function (cols, rows) { | ||
AccessibilityManager.prototype._onResize = function (rows) { | ||
this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener); | ||
@@ -194,2 +194,5 @@ for (var i = this._rowContainer.children.length; i < this._terminal.rows; i++) { | ||
} | ||
if (this._rowElements.length !== this._terminal.rows) { | ||
this._onResize(this._terminal.rows); | ||
} | ||
for (var i = 0; i < this._terminal.rows; i++) { | ||
@@ -196,0 +199,0 @@ this._refreshRowDimensions(this._rowElements[i]); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var CHAR_DATA_CODE_INDEX = 3; | ||
var NULL_CELL_CODE = 32; | ||
function winptyCompatInit(terminal) { | ||
@@ -12,3 +14,3 @@ var addonTerminal = terminal; | ||
var lastChar = line[addonTerminal.cols - 1]; | ||
if (lastChar[3] !== 32) { | ||
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE) { | ||
var nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y); | ||
@@ -15,0 +17,0 @@ nextLine.isWrapped = true; |
@@ -21,2 +21,5 @@ "use strict"; | ||
exports.MAX_BUFFER_SIZE = 4294967295; | ||
exports.NULL_CELL_CHAR = ' '; | ||
exports.NULL_CELL_WIDTH = 1; | ||
exports.NULL_CELL_CODE = 32; | ||
var Buffer = (function () { | ||
@@ -77,3 +80,3 @@ function Buffer(_terminal, _hasScrollback) { | ||
if (this._terminal.cols < newCols) { | ||
var ch = [exports.DEFAULT_ATTR, ' ', 1, 32]; | ||
var ch = [exports.DEFAULT_ATTR, exports.NULL_CELL_CHAR, exports.NULL_CELL_WIDTH, exports.NULL_CELL_CODE]; | ||
for (var i = 0; i < this.lines.length; i++) { | ||
@@ -80,0 +83,0 @@ while (this.lines.get(i).length < newCols) { |
@@ -6,4 +6,6 @@ "use strict"; | ||
this._disposables = []; | ||
this._isDisposed = false; | ||
} | ||
Disposable.prototype.dispose = function () { | ||
this._isDisposed = true; | ||
this._disposables.forEach(function (d) { return d.dispose(); }); | ||
@@ -15,2 +17,8 @@ this._disposables.length = 0; | ||
}; | ||
Disposable.prototype.unregister = function (d) { | ||
var index = this._disposables.indexOf(d); | ||
if (index !== -1) { | ||
this._disposables.splice(index, 1); | ||
} | ||
}; | ||
return Disposable; | ||
@@ -17,0 +25,0 @@ }()); |
@@ -286,4 +286,4 @@ "use strict"; | ||
do | ||
code = data.charCodeAt(++i); | ||
while (i < l && code > 0x1f && code < 0x80); | ||
i++; | ||
while (i < l && data.charCodeAt(i) > 0x1f && data.charCodeAt(i) < 0x80); | ||
i--; | ||
@@ -290,0 +290,0 @@ continue; |
@@ -21,3 +21,3 @@ "use strict"; | ||
if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) { | ||
this._terminal.send(this._arrowSequences()); | ||
this._terminal.handler(this._arrowSequences()); | ||
} | ||
@@ -24,0 +24,0 @@ }; |
@@ -31,3 +31,3 @@ "use strict"; | ||
RequestTerminfo.prototype.unhook = function () { | ||
this._terminal.send(EscapeSequences_1.C0.ESC + "P0+r" + this._data + EscapeSequences_1.C0.ESC + "\\"); | ||
this._terminal.handler(EscapeSequences_1.C0.ESC + "P0+r" + this._data + EscapeSequences_1.C0.ESC + "\\"); | ||
}; | ||
@@ -49,11 +49,11 @@ return RequestTerminfo; | ||
case '"q': | ||
return this._terminal.send(EscapeSequences_1.C0.ESC + "P1$r0\"q" + EscapeSequences_1.C0.ESC + "\\"); | ||
return this._terminal.handler(EscapeSequences_1.C0.ESC + "P1$r0\"q" + EscapeSequences_1.C0.ESC + "\\"); | ||
case '"p': | ||
return this._terminal.send(EscapeSequences_1.C0.ESC + "P1$r61\"p" + EscapeSequences_1.C0.ESC + "\\"); | ||
return this._terminal.handler(EscapeSequences_1.C0.ESC + "P1$r61\"p" + EscapeSequences_1.C0.ESC + "\\"); | ||
case 'r': | ||
var pt = '' + (this._terminal.buffer.scrollTop + 1) + | ||
';' + (this._terminal.buffer.scrollBottom + 1) + 'r'; | ||
return this._terminal.send(EscapeSequences_1.C0.ESC + "P1$r" + pt + EscapeSequences_1.C0.ESC + "\\"); | ||
return this._terminal.handler(EscapeSequences_1.C0.ESC + "P1$r" + pt + EscapeSequences_1.C0.ESC + "\\"); | ||
case 'm': | ||
return this._terminal.send(EscapeSequences_1.C0.ESC + "P1$r0m" + EscapeSequences_1.C0.ESC + "\\"); | ||
return this._terminal.handler(EscapeSequences_1.C0.ESC + "P1$r0m" + EscapeSequences_1.C0.ESC + "\\"); | ||
case ' q': | ||
@@ -63,6 +63,6 @@ var STYLES = { 'block': 2, 'underline': 4, 'bar': 6 }; | ||
style -= this._terminal.getOption('cursorBlink'); | ||
return this._terminal.send(EscapeSequences_1.C0.ESC + "P1$r" + style + " q" + EscapeSequences_1.C0.ESC + "\\"); | ||
return this._terminal.handler(EscapeSequences_1.C0.ESC + "P1$r" + style + " q" + EscapeSequences_1.C0.ESC + "\\"); | ||
default: | ||
this._terminal.error('Unknown DCS $q %s', this._data); | ||
this._terminal.send(EscapeSequences_1.C0.ESC + "P0$r" + this._data + EscapeSequences_1.C0.ESC + "\\"); | ||
this._terminal.handler(EscapeSequences_1.C0.ESC + "P0$r" + this._data + EscapeSequences_1.C0.ESC + "\\"); | ||
} | ||
@@ -82,12 +82,12 @@ }; | ||
_this._parser.setCsiHandlerFallback(function (collect, params, flag) { | ||
_this._terminal.error('Unknown CSI code: ', collect, params, String.fromCharCode(flag)); | ||
_this._terminal.error('Unknown CSI code: ', { collect: collect, params: params, flag: String.fromCharCode(flag) }); | ||
}); | ||
_this._parser.setEscHandlerFallback(function (collect, flag) { | ||
_this._terminal.error('Unknown ESC code: ', collect, String.fromCharCode(flag)); | ||
_this._terminal.error('Unknown ESC code: ', { collect: collect, flag: String.fromCharCode(flag) }); | ||
}); | ||
_this._parser.setExecuteHandlerFallback(function (code) { | ||
_this._terminal.error('Unknown EXECUTE code: ', code); | ||
_this._terminal.error('Unknown EXECUTE code: ', { code: code }); | ||
}); | ||
_this._parser.setOscHandlerFallback(function (identifier, data) { | ||
_this._terminal.error('Unknown OSC code: ', identifier, data); | ||
_this._terminal.error('Unknown OSC code: ', { identifier: identifier, data: data }); | ||
}); | ||
@@ -187,2 +187,5 @@ _this._parser.setPrintHandler(function (data, start, end) { return _this.print(data, start, end); }); | ||
InputHandler.prototype.parse = function (data) { | ||
if (!this._terminal) { | ||
return; | ||
} | ||
var buffer = this._terminal.buffer; | ||
@@ -281,5 +284,5 @@ var cursorStartX = buffer.x; | ||
&& bufferRow[this._terminal.cols - 2][Buffer_1.CHAR_DATA_WIDTH_INDEX] === 2) { | ||
bufferRow[this._terminal.cols - 2] = [curAttr, ' ', 1, 32]; | ||
bufferRow[this._terminal.cols - 2] = [curAttr, Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]; | ||
} | ||
bufferRow.splice(buffer.x, 0, [curAttr, ' ', 1, 32]); | ||
bufferRow.splice(buffer.x, 0, [curAttr, Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]); | ||
} | ||
@@ -340,3 +343,3 @@ } | ||
var j = buffer.x; | ||
var ch = [this._terminal.eraseAttr(), ' ', 1, 32]; | ||
var ch = [this._terminal.eraseAttr(), Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]; | ||
while (param-- && j < this._terminal.cols) { | ||
@@ -538,3 +541,3 @@ buffer.lines.get(row).splice(j++, 0, ch); | ||
var row = buffer.y + buffer.ybase; | ||
var ch = [this._terminal.eraseAttr(), ' ', 1, 32]; | ||
var ch = [this._terminal.eraseAttr(), Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]; | ||
while (param--) { | ||
@@ -576,3 +579,3 @@ buffer.lines.get(row).splice(buffer.x, 1); | ||
var j = buffer.x; | ||
var ch = [this._terminal.eraseAttr(), ' ', 1, 32]; | ||
var ch = [this._terminal.eraseAttr(), Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]; | ||
while (param-- && j < this._terminal.cols) { | ||
@@ -613,3 +616,3 @@ buffer.lines.get(row)[j++] = ch; | ||
var line = buffer.lines.get(buffer.ybase + buffer.y); | ||
var ch = line[buffer.x - 1] || [Buffer_1.DEFAULT_ATTR, ' ', 1, 32]; | ||
var ch = line[buffer.x - 1] || [Buffer_1.DEFAULT_ATTR, Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]; | ||
while (param--) { | ||
@@ -625,6 +628,6 @@ line[buffer.x++] = ch; | ||
if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { | ||
this._terminal.send(EscapeSequences_1.C0.ESC + '[?1;2c'); | ||
this._terminal.handler(EscapeSequences_1.C0.ESC + '[?1;2c'); | ||
} | ||
else if (this._terminal.is('linux')) { | ||
this._terminal.send(EscapeSequences_1.C0.ESC + '[?6c'); | ||
this._terminal.handler(EscapeSequences_1.C0.ESC + '[?6c'); | ||
} | ||
@@ -634,12 +637,12 @@ } | ||
if (this._terminal.is('xterm')) { | ||
this._terminal.send(EscapeSequences_1.C0.ESC + '[>0;276;0c'); | ||
this._terminal.handler(EscapeSequences_1.C0.ESC + '[>0;276;0c'); | ||
} | ||
else if (this._terminal.is('rxvt-unicode')) { | ||
this._terminal.send(EscapeSequences_1.C0.ESC + '[>85;95;0c'); | ||
this._terminal.handler(EscapeSequences_1.C0.ESC + '[>85;95;0c'); | ||
} | ||
else if (this._terminal.is('linux')) { | ||
this._terminal.send(params[0] + 'c'); | ||
this._terminal.handler(params[0] + 'c'); | ||
} | ||
else if (this._terminal.is('screen')) { | ||
this._terminal.send(EscapeSequences_1.C0.ESC + '[>83;40003;0c'); | ||
this._terminal.handler(EscapeSequences_1.C0.ESC + '[>83;40003;0c'); | ||
} | ||
@@ -974,10 +977,8 @@ } | ||
case 5: | ||
this._terminal.send(EscapeSequences_1.C0.ESC + '[0n'); | ||
this._terminal.emit('data', EscapeSequences_1.C0.ESC + "[0n"); | ||
break; | ||
case 6: | ||
this._terminal.send(EscapeSequences_1.C0.ESC + '[' | ||
+ (this._terminal.buffer.y + 1) | ||
+ ';' | ||
+ (this._terminal.buffer.x + 1) | ||
+ 'R'); | ||
var y = this._terminal.buffer.y + 1; | ||
var x = this._terminal.buffer.x + 1; | ||
this._terminal.emit('data', EscapeSequences_1.C0.ESC + "[" + y + ";" + x + "R"); | ||
break; | ||
@@ -989,7 +990,5 @@ } | ||
case 6: | ||
this._terminal.send(EscapeSequences_1.C0.ESC + '[?' | ||
+ (this._terminal.buffer.y + 1) | ||
+ ';' | ||
+ (this._terminal.buffer.x + 1) | ||
+ 'R'); | ||
var y = this._terminal.buffer.y + 1; | ||
var x = this._terminal.buffer.x + 1; | ||
this._terminal.emit('data', EscapeSequences_1.C0.ESC + "[?" + y + ";" + x + "R"); | ||
break; | ||
@@ -996,0 +995,0 @@ case 15: |
@@ -108,6 +108,11 @@ "use strict"; | ||
} | ||
var line = void 0; | ||
do { | ||
rowIndex--; | ||
absoluteRowIndex--; | ||
} while (this._terminal.buffer.lines.get(absoluteRowIndex).isWrapped); | ||
line = this._terminal.buffer.lines.get(absoluteRowIndex); | ||
if (!line) { | ||
break; | ||
} | ||
} while (line.isWrapped); | ||
} | ||
@@ -114,0 +119,0 @@ var text = this._terminal.buffer.translateBufferLineToString(absoluteRowIndex, false); |
@@ -70,2 +70,8 @@ "use strict"; | ||
}; | ||
Terminal.prototype.registerCharacterJoiner = function (handler) { | ||
return this._core.registerCharacterJoiner(handler); | ||
}; | ||
Terminal.prototype.deregisterCharacterJoiner = function (joinerId) { | ||
this._core.deregisterCharacterJoiner(joinerId); | ||
}; | ||
Terminal.prototype.addMarker = function (cursorYOffset) { | ||
@@ -72,0 +78,0 @@ return this._core.addMarker(cursorYOffset); |
@@ -27,3 +27,3 @@ "use strict"; | ||
var styleFlags = (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1); | ||
return glyph.bg + "_" + glyph.fg + "_" + styleFlags + glyph.char; | ||
return glyph.bg + "_" + glyph.fg + "_" + styleFlags + glyph.chars; | ||
} | ||
@@ -136,3 +136,3 @@ var DynamicCharAtlas = (function (_super) { | ||
} | ||
this._tmpCtx.fillText(glyph.char, 0, 0); | ||
this._tmpCtx.fillText(glyph.chars, 0, 0); | ||
this._tmpCtx.restore(); | ||
@@ -139,0 +139,0 @@ var imageData = this._tmpCtx.getImageData(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight); |
@@ -23,2 +23,5 @@ "use strict"; | ||
} | ||
BaseRenderLayer.prototype.dispose = function () { | ||
this._container.removeChild(this._canvas); | ||
}; | ||
BaseRenderLayer.prototype._initCanvas = function () { | ||
@@ -114,11 +117,11 @@ this._ctx = this._canvas.getContext('2d', { alpha: this._alpha }); | ||
}; | ||
BaseRenderLayer.prototype.drawChar = function (terminal, char, code, width, x, y, fg, bg, bold, dim, italic) { | ||
var drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8; | ||
BaseRenderLayer.prototype.drawChars = function (terminal, chars, code, width, x, y, fg, bg, bold, dim, italic) { | ||
var drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8 && fg !== Types_1.INVERTED_DEFAULT_COLOR; | ||
fg += drawInBrightColor ? 8 : 0; | ||
var atlasDidDraw = this._charAtlas && this._charAtlas.draw(this._ctx, { char: char, code: code, bg: bg, fg: fg, bold: bold && terminal.options.enableBold, dim: dim, italic: italic }, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); | ||
var atlasDidDraw = this._charAtlas && this._charAtlas.draw(this._ctx, { chars: chars, code: code, bg: bg, fg: fg, bold: bold && terminal.options.enableBold, dim: dim, italic: italic }, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); | ||
if (!atlasDidDraw) { | ||
this._drawUncachedChar(terminal, char, width, fg, x, y, bold && terminal.options.enableBold, dim, italic); | ||
this._drawUncachedChars(terminal, chars, width, fg, x, y, bold && terminal.options.enableBold, dim, italic); | ||
} | ||
}; | ||
BaseRenderLayer.prototype._drawUncachedChar = function (terminal, char, width, fg, x, y, bold, dim, italic) { | ||
BaseRenderLayer.prototype._drawUncachedChars = function (terminal, chars, width, fg, x, y, bold, dim, italic) { | ||
this._ctx.save(); | ||
@@ -140,3 +143,3 @@ this._ctx.font = this._getFont(terminal, bold, italic); | ||
} | ||
this._ctx.fillText(char, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); | ||
this._ctx.fillText(chars, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); | ||
this._ctx.restore(); | ||
@@ -143,0 +146,0 @@ }; |
@@ -38,3 +38,3 @@ "use strict"; | ||
_this._rowContainer.setAttribute('aria-hidden', 'true'); | ||
_this._refreshRowElements(_this._terminal.rows, _this._terminal.cols); | ||
_this._refreshRowElements(_this._terminal.cols, _this._terminal.rows); | ||
_this._selectionContainer = document.createElement('div'); | ||
@@ -65,2 +65,10 @@ _this._selectionContainer.classList.add(SELECTION_CLASS); | ||
} | ||
DomRenderer.prototype.dispose = function () { | ||
this._terminal.element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass); | ||
this._terminal.screenElement.removeChild(this._rowContainer); | ||
this._terminal.screenElement.removeChild(this._selectionContainer); | ||
this._terminal.screenElement.removeChild(this._themeStyleElement); | ||
this._terminal.screenElement.removeChild(this._dimensionsStyleElement); | ||
_super.prototype.dispose.call(this); | ||
}; | ||
DomRenderer.prototype._updateDimensions = function () { | ||
@@ -194,7 +202,7 @@ var _this = this; | ||
var documentFragment = document.createDocumentFragment(); | ||
var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; | ||
if (columnSelectMode) { | ||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, end[0], viewportCappedEndRow - viewportStartRow + 1)); | ||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, start[0], end[0], viewportCappedEndRow - viewportCappedStartRow + 1)); | ||
} | ||
else { | ||
var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; | ||
var endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; | ||
@@ -253,2 +261,4 @@ documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); | ||
}); | ||
DomRenderer.prototype.registerCharacterJoiner = function (handler) { return -1; }; | ||
DomRenderer.prototype.deregisterCharacterJoiner = function (joinerId) { return false; }; | ||
return DomRenderer; | ||
@@ -255,0 +265,0 @@ }(EventEmitter_1.EventEmitter)); |
@@ -21,2 +21,3 @@ "use strict"; | ||
var ScreenDprMonitor_1 = require("../ui/ScreenDprMonitor"); | ||
var CharacterJoinerRegistry_1 = require("../renderer/CharacterJoinerRegistry"); | ||
var Renderer = (function (_super) { | ||
@@ -31,2 +32,3 @@ __extends(Renderer, _super); | ||
_this.colorManager = new ColorManager_1.ColorManager(document, allowTransparency); | ||
_this._characterJoinerRegistry = new CharacterJoinerRegistry_1.CharacterJoinerRegistry(_terminal); | ||
if (theme) { | ||
@@ -36,3 +38,3 @@ _this.colorManager.setTheme(theme); | ||
_this._renderLayers = [ | ||
new TextRenderLayer_1.TextRenderLayer(_this._terminal.screenElement, 0, _this.colorManager.colors, allowTransparency), | ||
new TextRenderLayer_1.TextRenderLayer(_this._terminal.screenElement, 0, _this.colorManager.colors, _this._characterJoinerRegistry, allowTransparency), | ||
new SelectionRenderLayer_1.SelectionRenderLayer(_this._terminal.screenElement, 1, _this.colorManager.colors), | ||
@@ -70,2 +72,6 @@ new LinkRenderLayer_1.LinkRenderLayer(_this._terminal.screenElement, 2, _this.colorManager.colors, _this._terminal), | ||
} | ||
Renderer.prototype.dispose = function () { | ||
_super.prototype.dispose.call(this); | ||
this._renderLayers.forEach(function (l) { return l.dispose(); }); | ||
}; | ||
Renderer.prototype.onIntersectionChange = function (entry) { | ||
@@ -181,2 +187,8 @@ this._isPaused = entry.intersectionRatio === 0; | ||
}; | ||
Renderer.prototype.registerCharacterJoiner = function (handler) { | ||
return this._characterJoinerRegistry.registerCharacterJoiner(handler); | ||
}; | ||
Renderer.prototype.deregisterCharacterJoiner = function (joinerId) { | ||
return this._characterJoinerRegistry.deregisterCharacterJoiner(joinerId); | ||
}; | ||
return Renderer; | ||
@@ -183,0 +195,0 @@ }(EventEmitter_1.EventEmitter)); |
@@ -56,3 +56,3 @@ "use strict"; | ||
if (columnSelectMode) { | ||
var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; | ||
var startCol = start[0]; | ||
var width = end[0] - startCol; | ||
@@ -59,0 +59,0 @@ var height = viewportCappedEndRow - viewportCappedStartRow + 1; |
@@ -19,6 +19,7 @@ "use strict"; | ||
__extends(TextRenderLayer, _super); | ||
function TextRenderLayer(container, zIndex, colors, alpha) { | ||
function TextRenderLayer(container, zIndex, colors, characterJoinerRegistry, alpha) { | ||
var _this = _super.call(this, container, 'text', zIndex, alpha, colors) || this; | ||
_this._characterOverlapCache = {}; | ||
_this._state = new GridCache_1.GridCache(); | ||
_this._characterJoinerRegistry = characterJoinerRegistry; | ||
return _this; | ||
@@ -41,17 +42,28 @@ } | ||
}; | ||
TextRenderLayer.prototype._forEachCell = function (terminal, firstRow, lastRow, callback) { | ||
TextRenderLayer.prototype._forEachCell = function (terminal, firstRow, lastRow, joinerRegistry, callback) { | ||
for (var y = firstRow; y <= lastRow; y++) { | ||
var row = y + terminal.buffer.ydisp; | ||
var line = terminal.buffer.lines.get(row); | ||
var joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; | ||
for (var x = 0; x < terminal.cols; x++) { | ||
var charData = line[x]; | ||
var code = charData[Buffer_1.CHAR_DATA_CODE_INDEX]; | ||
var char = charData[Buffer_1.CHAR_DATA_CHAR_INDEX]; | ||
var chars = charData[Buffer_1.CHAR_DATA_CHAR_INDEX]; | ||
var attr = charData[Buffer_1.CHAR_DATA_ATTR_INDEX]; | ||
var width = charData[Buffer_1.CHAR_DATA_WIDTH_INDEX]; | ||
var isJoined = false; | ||
var lastCharX = x; | ||
if (width === 0) { | ||
continue; | ||
} | ||
if (this._isOverlapping(charData)) { | ||
if (x < line.length - 1 && line[x + 1][Buffer_1.CHAR_DATA_CODE_INDEX] === 32) { | ||
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) { | ||
isJoined = true; | ||
var range = joinedRanges.shift(); | ||
chars = terminal.buffer.translateBufferLineToString(row, true, range[0], range[1]); | ||
width = range[1] - range[0]; | ||
code = Infinity; | ||
lastCharX = range[1] - 1; | ||
} | ||
if (!isJoined && this._isOverlapping(charData)) { | ||
if (lastCharX < line.length - 1 && line[lastCharX + 1][Buffer_1.CHAR_DATA_CODE_INDEX] === Buffer_1.NULL_CELL_CODE) { | ||
width = 2; | ||
@@ -74,3 +86,4 @@ } | ||
} | ||
callback(code, char, width, x, y, fg, bg, flags); | ||
callback(code, chars, width, x, y, fg, bg, flags); | ||
x = lastCharX; | ||
} | ||
@@ -87,3 +100,3 @@ } | ||
ctx.save(); | ||
this._forEachCell(terminal, firstRow, lastRow, function (code, char, width, x, y, fg, bg, flags) { | ||
this._forEachCell(terminal, firstRow, lastRow, null, function (code, chars, width, x, y, fg, bg, flags) { | ||
var nextFillStyle = null; | ||
@@ -122,3 +135,3 @@ if (bg === Types_1.INVERTED_DEFAULT_COLOR) { | ||
var _this = this; | ||
this._forEachCell(terminal, firstRow, lastRow, function (code, char, width, x, y, fg, bg, flags) { | ||
this._forEachCell(terminal, firstRow, lastRow, this._characterJoinerRegistry, function (code, chars, width, x, y, fg, bg, flags) { | ||
if (flags & 16) { | ||
@@ -138,6 +151,6 @@ return; | ||
} | ||
_this.fillBottomLineAtCells(x, y); | ||
_this.fillBottomLineAtCells(x, y, width); | ||
_this._ctx.restore(); | ||
} | ||
_this.drawChar(terminal, char, code, width, x, y, fg, bg, !!(flags & 1), !!(flags & 32), !!(flags & 64)); | ||
_this.drawChars(terminal, chars, code, width, x, y, fg, bg, !!(flags & 1), !!(flags & 32), !!(flags & 64)); | ||
}); | ||
@@ -144,0 +157,0 @@ }; |
@@ -273,4 +273,6 @@ "use strict"; | ||
SelectionManager.prototype._removeMouseDownListeners = function () { | ||
this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); | ||
this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); | ||
if (this._terminal.element.ownerDocument) { | ||
this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); | ||
this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); | ||
} | ||
clearInterval(this._dragScrollIntervalTimer); | ||
@@ -342,8 +344,10 @@ this._dragScrollIntervalTimer = null; | ||
this._dragScrollAmount = this._getMouseEventScrollAmount(event); | ||
if (this._dragScrollAmount > 0) { | ||
this._model.selectionEnd[0] = this._terminal.cols; | ||
if (this._activeSelectionMode !== 3) { | ||
if (this._dragScrollAmount > 0) { | ||
this._model.selectionEnd[0] = this._terminal.cols; | ||
} | ||
else if (this._dragScrollAmount < 0) { | ||
this._model.selectionEnd[0] = 0; | ||
} | ||
} | ||
else if (this._dragScrollAmount < 0) { | ||
this._model.selectionEnd[0] = 0; | ||
} | ||
if (this._model.selectionEnd[1] < this._buffer.lines.length) { | ||
@@ -365,6 +369,12 @@ var char = this._buffer.lines.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]]; | ||
if (this._dragScrollAmount > 0) { | ||
this._model.selectionEnd = [this._terminal.cols - 1, Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1)]; | ||
if (this._activeSelectionMode !== 3) { | ||
this._model.selectionEnd[0] = this._terminal.cols; | ||
} | ||
this._model.selectionEnd[1] = Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1); | ||
} | ||
else { | ||
this._model.selectionEnd = [0, this._terminal.buffer.ydisp]; | ||
if (this._activeSelectionMode !== 3) { | ||
this._model.selectionEnd[0] = 0; | ||
} | ||
this._model.selectionEnd[1] = this._terminal.buffer.ydisp; | ||
} | ||
@@ -409,3 +419,5 @@ this.refresh(); | ||
}; | ||
SelectionManager.prototype._getWordAt = function (coords, allowWhitespaceOnlySelection) { | ||
SelectionManager.prototype._getWordAt = function (coords, allowWhitespaceOnlySelection, followWrappedLinesAbove, followWrappedLinesBelow) { | ||
if (followWrappedLinesAbove === void 0) { followWrappedLinesAbove = true; } | ||
if (followWrappedLinesBelow === void 0) { followWrappedLinesBelow = true; } | ||
if (coords[0] >= this._terminal.cols) { | ||
@@ -490,2 +502,26 @@ return null; | ||
} | ||
if (followWrappedLinesAbove) { | ||
if (start === 0 && bufferLine[0][Buffer_1.CHAR_DATA_CODE_INDEX] !== 32) { | ||
var previousBufferLine = this._buffer.lines.get(coords[1] - 1); | ||
if (previousBufferLine && bufferLine.isWrapped && previousBufferLine[this._terminal.cols - 1][Buffer_1.CHAR_DATA_CODE_INDEX] !== 32) { | ||
var previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false); | ||
if (previousLineWordPosition) { | ||
var offset = this._terminal.cols - previousLineWordPosition.start; | ||
start -= offset; | ||
length += offset; | ||
} | ||
} | ||
} | ||
} | ||
if (followWrappedLinesBelow) { | ||
if (start + length === this._terminal.cols && bufferLine[this._terminal.cols - 1][Buffer_1.CHAR_DATA_CODE_INDEX] !== 32) { | ||
var nextBufferLine = this._buffer.lines.get(coords[1] + 1); | ||
if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine[0][Buffer_1.CHAR_DATA_CODE_INDEX] !== 32) { | ||
var nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true); | ||
if (nextLineWordPosition) { | ||
length += nextLineWordPosition.length; | ||
} | ||
} | ||
} | ||
} | ||
return { start: start, length: length }; | ||
@@ -496,2 +532,6 @@ }; | ||
if (wordPosition) { | ||
while (wordPosition.start < 0) { | ||
wordPosition.start += this._terminal.cols; | ||
coords[1]--; | ||
} | ||
this._model.selectionStart = [wordPosition.start, coords[1]]; | ||
@@ -504,3 +544,14 @@ this._model.selectionStartLength = wordPosition.length; | ||
if (wordPosition) { | ||
this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; | ||
var endRow = coords[1]; | ||
while (wordPosition.start < 0) { | ||
wordPosition.start += this._terminal.cols; | ||
endRow--; | ||
} | ||
if (!this._model.areSelectionValuesReversed()) { | ||
while (wordPosition.start + wordPosition.length > this._terminal.cols) { | ||
wordPosition.length -= this._terminal.cols; | ||
endRow++; | ||
} | ||
} | ||
this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : wordPosition.start + wordPosition.length, endRow]; | ||
} | ||
@@ -507,0 +558,0 @@ }; |
@@ -36,3 +36,7 @@ "use strict"; | ||
if (!this.selectionEnd || this.areSelectionValuesReversed()) { | ||
return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]]; | ||
var startPlusLength = this.selectionStart[0] + this.selectionStartLength; | ||
if (startPlusLength > this._terminal.cols) { | ||
return [startPlusLength % this._terminal.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._terminal.cols)]; | ||
} | ||
return [startPlusLength, this.selectionStart[1]]; | ||
} | ||
@@ -39,0 +43,0 @@ if (this.selectionStartLength) { |
@@ -41,3 +41,3 @@ "use strict"; | ||
var WRITE_BATCH_SIZE = 300; | ||
var CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows', 'rendererType']; | ||
var CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows']; | ||
var DEFAULT_OPTIONS = { | ||
@@ -114,3 +114,2 @@ cols: 80, | ||
this.cursorHidden = false; | ||
this._sendDataQueue = ''; | ||
this._customKeyEventHandler = null; | ||
@@ -182,2 +181,5 @@ this.applicationKeypad = false; | ||
} | ||
if (this.options[key] === value) { | ||
return; | ||
} | ||
switch (key) { | ||
@@ -209,2 +211,7 @@ case 'bellStyle': | ||
} | ||
case 'rendererType': | ||
if (!value) { | ||
value = 'canvas'; | ||
} | ||
break; | ||
case 'tabStopWidth': | ||
@@ -264,2 +271,14 @@ if (value < 1) { | ||
} | ||
case 'rendererType': | ||
if (this.renderer) { | ||
this.unregister(this.renderer); | ||
this.renderer.dispose(); | ||
this.renderer = null; | ||
} | ||
this._setupRenderer(); | ||
this.renderer.onCharSizeChanged(); | ||
if (this._theme) { | ||
this.renderer.setTheme(this._theme); | ||
} | ||
break; | ||
case 'scrollback': | ||
@@ -294,3 +313,3 @@ this.buffers.resize(this.cols, this.rows); | ||
if (this.sendFocus) { | ||
this.send(EscapeSequences_1.C0.ESC + '[I'); | ||
this.handler(EscapeSequences_1.C0.ESC + '[I'); | ||
} | ||
@@ -308,3 +327,3 @@ this.element.classList.add('focus'); | ||
if (this.sendFocus) { | ||
this.send(EscapeSequences_1.C0.ESC + '[O'); | ||
this.handler(EscapeSequences_1.C0.ESC + '[O'); | ||
} | ||
@@ -426,12 +445,4 @@ this.element.classList.remove('focus'); | ||
this.element.appendChild(fragment); | ||
switch (this.options.rendererType) { | ||
case 'canvas': | ||
this.renderer = new Renderer_1.Renderer(this, this.options.theme); | ||
break; | ||
case 'dom': | ||
this.renderer = new DomRenderer_1.DomRenderer(this, this.options.theme); | ||
break; | ||
default: throw new Error("Unrecognized rendererType \"" + this.options.rendererType + "\""); | ||
} | ||
this.register(this.renderer); | ||
this._setupRenderer(); | ||
this._theme = this.options.theme; | ||
this.options.theme = null; | ||
@@ -471,3 +482,16 @@ this.viewport = new Viewport_1.Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure); | ||
}; | ||
Terminal.prototype._setupRenderer = function () { | ||
switch (this.options.rendererType) { | ||
case 'canvas': | ||
this.renderer = new Renderer_1.Renderer(this, this.options.theme); | ||
break; | ||
case 'dom': | ||
this.renderer = new DomRenderer_1.DomRenderer(this, this.options.theme); | ||
break; | ||
default: throw new Error("Unrecognized rendererType \"" + this.options.rendererType + "\""); | ||
} | ||
this.register(this.renderer); | ||
}; | ||
Terminal.prototype._setTheme = function (theme) { | ||
this._theme = theme; | ||
var colors = this.renderer.setTheme(theme); | ||
@@ -553,3 +577,3 @@ if (this.viewport) { | ||
data_1 += '~[' + pos.x + ',' + pos.y + ']\r'; | ||
self.send(data_1); | ||
self.handler(data_1); | ||
return; | ||
@@ -569,3 +593,3 @@ } | ||
button = 3; | ||
self.send(EscapeSequences_1.C0.ESC + '[' | ||
self.handler(EscapeSequences_1.C0.ESC + '[' | ||
+ button | ||
@@ -588,3 +612,3 @@ + ';' | ||
pos.y++; | ||
self.send(EscapeSequences_1.C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); | ||
self.handler(EscapeSequences_1.C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); | ||
return; | ||
@@ -595,3 +619,3 @@ } | ||
pos.y -= 32; | ||
self.send(EscapeSequences_1.C0.ESC + '[<' | ||
self.handler(EscapeSequences_1.C0.ESC + '[<' | ||
+ (((button & 3) === 3 ? button & ~3 : button) - 32) | ||
@@ -609,3 +633,3 @@ + ';' | ||
encode(data, pos.y); | ||
self.send(EscapeSequences_1.C0.ESC + '[M' + String.fromCharCode.apply(String, data)); | ||
self.handler(EscapeSequences_1.C0.ESC + '[M' + String.fromCharCode.apply(String, data)); | ||
} | ||
@@ -704,3 +728,3 @@ function getButton(ev) { | ||
} | ||
_this.send(data); | ||
_this.handler(data); | ||
} | ||
@@ -830,2 +854,5 @@ return; | ||
var _this = this; | ||
if (this._isDisposed) { | ||
return; | ||
} | ||
if (!data) { | ||
@@ -836,3 +863,3 @@ return; | ||
if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { | ||
this.send(EscapeSequences_1.C0.DC3); | ||
this.handler(EscapeSequences_1.C0.DC3); | ||
this._xoffSentToCatchUp = true; | ||
@@ -849,2 +876,5 @@ } | ||
var _this = this; | ||
if (this._isDisposed) { | ||
this.writeBuffer = []; | ||
} | ||
var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); | ||
@@ -854,3 +884,3 @@ while (writeBatch.length > 0) { | ||
if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { | ||
this.send(EscapeSequences_1.C0.DC1); | ||
this.handler(EscapeSequences_1.C0.DC1); | ||
this._xoffSentToCatchUp = false; | ||
@@ -887,2 +917,12 @@ } | ||
}; | ||
Terminal.prototype.registerCharacterJoiner = function (handler) { | ||
var joinerId = this.renderer.registerCharacterJoiner(handler); | ||
this.refresh(0, this.rows - 1); | ||
return joinerId; | ||
}; | ||
Terminal.prototype.deregisterCharacterJoiner = function (joinerId) { | ||
if (this.renderer.deregisterCharacterJoiner(joinerId)) { | ||
this.refresh(0, this.rows - 1); | ||
} | ||
}; | ||
Object.defineProperty(Terminal.prototype, "markers", { | ||
@@ -1006,12 +1046,2 @@ get: function () { | ||
}; | ||
Terminal.prototype.send = function (data) { | ||
var _this = this; | ||
if (!this._sendDataQueue) { | ||
setTimeout(function () { | ||
_this.handler(_this._sendDataQueue); | ||
_this._sendDataQueue = ''; | ||
}, 1); | ||
} | ||
this._sendDataQueue += data; | ||
}; | ||
Terminal.prototype.bell = function () { | ||
@@ -1084,3 +1114,3 @@ var _this = this; | ||
} | ||
var ch = [this.eraseAttr(), ' ', 1, 32]; | ||
var ch = [this.eraseAttr(), Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]; | ||
for (; x < this.cols; x++) { | ||
@@ -1096,3 +1126,3 @@ line[x] = ch; | ||
} | ||
var ch = [this.eraseAttr(), ' ', 1, 32]; | ||
var ch = [this.eraseAttr(), Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]; | ||
x++; | ||
@@ -1124,3 +1154,3 @@ while (x--) { | ||
var attr = cur ? this.eraseAttr() : Buffer_1.DEFAULT_ATTR; | ||
var ch = [attr, ' ', 1, 32]; | ||
var ch = [attr, Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]; | ||
var line = []; | ||
@@ -1138,5 +1168,5 @@ if (isWrapped) { | ||
if (cur) { | ||
return [this.eraseAttr(), ' ', 1, 32]; | ||
return [this.eraseAttr(), Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]; | ||
} | ||
return [Buffer_1.DEFAULT_ATTR, ' ', 1, 32]; | ||
return [Buffer_1.DEFAULT_ATTR, Buffer_1.NULL_CELL_CHAR, Buffer_1.NULL_CELL_WIDTH, Buffer_1.NULL_CELL_CODE]; | ||
}; | ||
@@ -1143,0 +1173,0 @@ Terminal.prototype.is = function (term) { |
{ | ||
"name": "xterm", | ||
"description": "Full xterm terminal, in your browser", | ||
"version": "3.5.1", | ||
"version": "3.6.0", | ||
"main": "lib/public/Terminal.js", | ||
@@ -38,2 +38,3 @@ "types": "typings/xterm.d.ts", | ||
"sorcery": "^0.10.0", | ||
"source-map-loader": "^0.2.3", | ||
"tslint": "^5.9.1", | ||
@@ -40,0 +41,0 @@ "tslint-consistent-codestyle": "^1.13.0", |
111
README.md
# [![xterm.js logo](logo-full.png)](https://xtermjs.org) | ||
[![Travis CI build status](https://api.travis-ci.org/xtermjs/xterm.js.svg)](https://travis-ci.org/xtermjs/xterm.js) [![VSTS Build status](https://xtermjs.visualstudio.com/_apis/public/build/definitions/3e323cf7-5760-460d-af64-ee5675baf366/1/badge)](https://xtermjs.visualstudio.com/xterm.js/_build/index?definitionId=1) [![Coverage Status](https://coveralls.io/repos/github/xtermjs/xterm.js/badge.svg?branch=master)](https://coveralls.io/github/xtermjs/xterm.js?branch=master) [![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/xterm/badge?style=rounded)](https://www.jsdelivr.com/package/npm/xterm) | ||
[![VSTS Build status](https://xtermjs.visualstudio.com/_apis/public/build/definitions/3e323cf7-5760-460d-af64-ee5675baf366/1/badge)](https://xtermjs.visualstudio.com/xterm.js/_build/index?definitionId=1) | ||
[![Coverage Status](https://coveralls.io/repos/github/xtermjs/xterm.js/badge.svg?branch=master)](https://coveralls.io/github/xtermjs/xterm.js?branch=master) | ||
[![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) | ||
[![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/xterm/badge?style=rounded)](https://www.jsdelivr.com/package/npm/xterm) | ||
@@ -60,2 +63,8 @@ Xterm.js is a terminal front-end component written in JavaScript that works in the browser. | ||
### API | ||
The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API. | ||
Note that some APIs are marked *experimental*, these are added so we can experiment with new ideas without committing to support it like a normal semver API. Note that these APIs can change radically between versions so be sure to read release notes if you plan on using experimental APIs. | ||
### Addons | ||
@@ -78,2 +87,4 @@ | ||
You will also need to include the addon's CSS file if it has one in the folder. | ||
#### Importing Addons in TypeScript | ||
@@ -112,2 +123,6 @@ | ||
## API | ||
The current full API documentation is available in the [TypeScript declaration file on the repository](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), switch the tag (press `w` when viewing the file) to point at the specific version tag you're using. | ||
## Real-world uses | ||
@@ -159,86 +174,8 @@ Xterm.js is used in several world-class applications to provide great terminal experiences. | ||
- [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js | ||
- [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP. | ||
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents) | ||
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. Note: Please add any new contributions to the end of the list only. | ||
## Demo | ||
Xterm.js ships with a barebones demo implementation, designed for the development and evaluation of the library only. Exposing the demo to the public as is would introduce security risks for the host. | ||
Below you can find instructions on how to run the demo on different platforms. | ||
### SourceLair | ||
SourceLair will run the demo and builder in parallel automatically. Just make sure to choose the "Node.js" project type, when cloning the xterm.js repo (or just use this shortcut; https://lair.io/xtermjs/xtermjs). | ||
Then open your project's [Public URL](https://help.sourcelair.com/projects/the-public-url/) to access the demo. | ||
### Docker | ||
First, make sure you have Docker Engine 1.13.0 (or newer) and Docker Compose 1.10.0 (or newer). | ||
Xterm.js [provides a pre-built Docker image](https://hub.docker.com/r/xtermjs/xterm.js/) to help run the demo easily (Git tags are built as [tagged Docker images](https://hub.docker.com/r/xtermjs/xterm.js/tags/) too). | ||
To run the just demo (with no editing access). run the following command in your terminal: | ||
``` | ||
docker run -p 3000:3000 xtermjs/xterm.js | ||
``` | ||
Then open http://0.0.0.0:3000 in a web browser to access the demo. | ||
To run the demo and builder in parallel, run the following command in your terminal: | ||
``` | ||
docker-compose up | ||
``` | ||
Then open http://0.0.0.0:3000 in a web browser to access the demo. If you prefer a different port than `3000` to access the xterm.js demo, then set the `XTERMJS_PORT` environment variable to the desired port. | ||
### Foreman (or other Procfile runner) | ||
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal: | ||
``` | ||
npm install | ||
foreman start # Replace foreman with "honcho", "forego" etc. depending on your runner | ||
``` | ||
Then open http://0.0.0.0:3000 in a web browser to access the demo. | ||
### Linux or macOS | ||
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal: | ||
``` | ||
npm install | ||
npm start # Run this in its own terminal | ||
npm run watch # Run this in its own terminal | ||
``` | ||
Then open http://0.0.0.0:3000 in a web browser to access the demo. | ||
### Windows | ||
First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run the following commands in your terminal: | ||
``` | ||
npm install | ||
npm start # Run this in its own terminal | ||
npm run watch # Run this in its own terminal | ||
``` | ||
Then open http://127.0.0.1:3000 in a web browser to access the demo. | ||
*Note: Do not use ConEmu, as it seems to break the demo for some reason.* | ||
## Testing | ||
Tests are run using the following npm scripts: | ||
- `npm test`: This will run both unit tests and the linter | ||
- `npm run test-suite <file>`: This will run all tests within a particular file, <file> is the test file name excluding the extension (eg. "Linkifier.test") | ||
- `npm run test-debug`: This will run unit tests with `--inspect-brk`, this can then be automatically debugged using [VS Code auto attach](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_auto-attach-feature) or manually attached to by a debugger | ||
- `npm run test-coverage`: This will run all unit tests and produce a coverage report | ||
- `npm run lint`: This will run the linter only | ||
## Releases | ||
@@ -250,12 +187,6 @@ | ||
## Development and Contribution | ||
## Contributing | ||
Xterm.js is maintained by [SourceLair](https://www.sourcelair.com/) and a few external contributors, but we would love to receive contributions from everyone! | ||
You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and setup xterm.js for development. | ||
To contribute either code, documentation or issues to xterm.js please read the [Contributing document](CONTRIBUTING.md) beforehand. The development of xterm.js does not require any special tool. All you need is an editor that supports JavaScript/TypeScript and a browser. You will need Node.js installed locally to get all the features working in the demo. | ||
### Code structure | ||
`src/` is roughly split up into areas of functionality such as `renderer/` that handles all rendering and `utils/` which provides general utility functions. The `shared/` folder contains code that can be used from either the main thread or a web worker thread, all code inside a `shared/` folder should only ever import other code from a `shared/` folder to minimize the amount of code ran when launching a web worker. | ||
## License Agreement | ||
@@ -262,0 +193,0 @@ |
@@ -75,3 +75,3 @@ /** | ||
this.register(this._renderRowsDebouncer); | ||
this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows))); | ||
this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.rows))); | ||
this.register(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end))); | ||
@@ -161,3 +161,3 @@ this.register(this._terminal.addDisposableListener('scroll', data => this._refreshRows())); | ||
private _onResize(cols: number, rows: number): void { | ||
private _onResize(rows: number): void { | ||
// Remove bottom boundary listener | ||
@@ -264,2 +264,5 @@ this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener); | ||
} | ||
if (this._rowElements.length !== this._terminal.rows) { | ||
this._onResize(this._terminal.rows); | ||
} | ||
for (let i = 0; i < this._terminal.rows; i++) { | ||
@@ -266,0 +269,0 @@ this._refreshRowDimensions(this._rowElements[i]); |
@@ -9,2 +9,5 @@ /** | ||
const CHAR_DATA_CODE_INDEX = 3; | ||
const NULL_CELL_CODE = 32; | ||
export function winptyCompatInit(terminal: Terminal): void { | ||
@@ -33,3 +36,3 @@ const addonTerminal = <IWinptyCompatAddonTerminal>terminal; | ||
if (lastChar[3] !== 32 /* ' ' */) { | ||
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE) { | ||
const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y); | ||
@@ -36,0 +39,0 @@ (<any>nextLine).isWrapped = true; |
@@ -18,2 +18,6 @@ /** | ||
export const NULL_CELL_CHAR = ' '; | ||
export const NULL_CELL_WIDTH = 1; | ||
export const NULL_CELL_CODE = 32; | ||
/** | ||
@@ -121,3 +125,3 @@ * This class represents a terminal buffer (an internal state of the terminal), where the | ||
if (this._terminal.cols < newCols) { | ||
const ch: CharData = [DEFAULT_ATTR, ' ', 1, 32]; // does xterm use the default attr? | ||
const ch: CharData = [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // does xterm use the default attr? | ||
for (let i = 0; i < this.lines.length; i++) { | ||
@@ -124,0 +128,0 @@ while (this.lines.get(i).length < newCols) { |
@@ -21,3 +21,3 @@ /** | ||
* Create a new BufferSet for the given terminal. | ||
* @param {Terminal} terminal - The terminal the BufferSet will belong to | ||
* @param _terminal - The terminal the BufferSet will belong to | ||
*/ | ||
@@ -39,3 +39,2 @@ constructor(private _terminal: ITerminal) { | ||
* Returns the alt Buffer of the BufferSet | ||
* @returns {Buffer} | ||
*/ | ||
@@ -48,3 +47,2 @@ public get alt(): Buffer { | ||
* Returns the normal Buffer of the BufferSet | ||
* @returns {Buffer} | ||
*/ | ||
@@ -57,3 +55,2 @@ public get active(): Buffer { | ||
* Returns the currently active Buffer of the BufferSet | ||
* @returns {Buffer} | ||
*/ | ||
@@ -60,0 +57,0 @@ public get normal(): Buffer { |
@@ -14,2 +14,3 @@ /** | ||
protected _disposables: IDisposable[] = []; | ||
protected _isDisposed: boolean = false; | ||
@@ -23,2 +24,3 @@ constructor() { | ||
public dispose(): void { | ||
this._isDisposed = true; | ||
this._disposables.forEach(d => d.dispose()); | ||
@@ -35,2 +37,14 @@ this._disposables.length = 0; | ||
} | ||
/** | ||
* Unregisters a disposable object if it has been registered, if not do | ||
* nothing. | ||
* @param d The disposable to unregister. | ||
*/ | ||
public unregister<T extends IDisposable>(d: T): void { | ||
const index = this._disposables.indexOf(d); | ||
if (index !== -1) { | ||
this._disposables.splice(index, 1); | ||
} | ||
} | ||
} |
@@ -64,3 +64,3 @@ /** | ||
* Handles the compositionupdate event, updating the composition view. | ||
* @param {CompositionEvent} ev The event. | ||
* @param ev The event. | ||
*/ | ||
@@ -67,0 +67,0 @@ public compositionupdate(ev: CompositionEvent): void { |
@@ -378,4 +378,4 @@ /** | ||
print = (~print) ? print : i; | ||
do code = data.charCodeAt(++i); | ||
while (i < l && code > 0x1f && code < 0x80); | ||
do i++; | ||
while (i < l && data.charCodeAt(i) > 0x1f && data.charCodeAt(i) < 0x80); | ||
i--; | ||
@@ -382,0 +382,0 @@ continue; |
@@ -53,3 +53,3 @@ /** | ||
if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) { | ||
this._terminal.send(this._arrowSequences()); | ||
this._terminal.handler(this._arrowSequences()); | ||
} | ||
@@ -56,0 +56,0 @@ } |
@@ -38,3 +38,3 @@ /** | ||
* Binds copy functionality to the given terminal. | ||
* @param {ClipboardEvent} ev The original copy event to be handled | ||
* @param ev The original copy event to be handled | ||
*/ | ||
@@ -54,4 +54,4 @@ export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void { | ||
* Redirect the clipboard's data to the terminal's input handler. | ||
* @param {ClipboardEvent} ev The original paste event to be handled | ||
* @param {Terminal} term The terminal on which to apply the handled paste event | ||
* @param ev The original paste event to be handled | ||
* @param term The terminal on which to apply the handled paste event | ||
*/ | ||
@@ -58,0 +58,0 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void { |
@@ -7,3 +7,3 @@ /** | ||
import { IMouseZoneManager } from './ui/Types'; | ||
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal } from './Types'; | ||
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, LineData } from './Types'; | ||
import { MouseZone } from './ui/MouseZoneManager'; | ||
@@ -173,6 +173,14 @@ import { EventEmitter } from './EventEmitter'; | ||
// If the first row is wrapped, backtrack to find the origin row and linkify that | ||
let line: LineData; | ||
do { | ||
rowIndex--; | ||
absoluteRowIndex--; | ||
} while ((<any>this._terminal.buffer.lines.get(absoluteRowIndex)).isWrapped); | ||
line = this._terminal.buffer.lines.get(absoluteRowIndex); | ||
if (!line) { | ||
break; | ||
} | ||
} while ((<any>line).isWrapped); | ||
} | ||
@@ -179,0 +187,0 @@ |
@@ -68,2 +68,8 @@ /** | ||
} | ||
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number { | ||
return this._core.registerCharacterJoiner(handler); | ||
} | ||
public deregisterCharacterJoiner(joinerId: number): void { | ||
this._core.deregisterCharacterJoiner(joinerId); | ||
} | ||
public addMarker(cursorYOffset: number): IMarker { | ||
@@ -70,0 +76,0 @@ return this._core.addMarker(cursorYOffset); |
@@ -39,3 +39,3 @@ /** | ||
const styleFlags = (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1); | ||
return `${glyph.bg}_${glyph.fg}_${styleFlags}${glyph.char}`; | ||
return `${glyph.bg}_${glyph.fg}_${styleFlags}${glyph.chars}`; | ||
} | ||
@@ -220,3 +220,3 @@ | ||
// Draw the character | ||
this._tmpCtx.fillText(glyph.char, 0, 0); | ||
this._tmpCtx.fillText(glyph.chars, 0, 0); | ||
this._tmpCtx.restore(); | ||
@@ -223,0 +223,0 @@ |
@@ -10,3 +10,3 @@ /** | ||
export interface IGlyphIdentifier { | ||
char: string; | ||
chars: string; | ||
code: number; | ||
@@ -13,0 +13,0 @@ bg: number; |
@@ -39,2 +39,6 @@ /** | ||
public dispose(): void { | ||
this._container.removeChild(this._canvas); | ||
} | ||
private _initCanvas(): void { | ||
@@ -228,8 +232,8 @@ this._ctx = this._canvas.getContext('2d', {alpha: this._alpha}); | ||
/** | ||
* Draws a character at a cell. If possible this will draw using the character | ||
* atlas to reduce draw time. | ||
* Draws one or more characters at a cell. If possible this will draw using | ||
* the character atlas to reduce draw time. | ||
* @param terminal The terminal. | ||
* @param char The character. | ||
* @param chars The character or characters. | ||
* @param code The character code. | ||
* @param width The width of the character. | ||
* @param width The width of the characters. | ||
* @param x The column to draw at. | ||
@@ -242,8 +246,9 @@ * @param y The row to draw at. | ||
*/ | ||
protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean, dim: boolean, italic: boolean): void { | ||
const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8; | ||
protected drawChars(terminal: ITerminal, chars: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean, dim: boolean, italic: boolean): void { | ||
const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; | ||
fg += drawInBrightColor ? 8 : 0; | ||
const atlasDidDraw = this._charAtlas && this._charAtlas.draw( | ||
this._ctx, | ||
{char, code, bg, fg, bold: bold && terminal.options.enableBold, dim, italic}, | ||
{chars, code, bg, fg, bold: bold && terminal.options.enableBold, dim, italic}, | ||
x * this._scaledCellWidth + this._scaledCharLeft, | ||
@@ -254,3 +259,3 @@ y * this._scaledCellHeight + this._scaledCharTop | ||
if (!atlasDidDraw) { | ||
this._drawUncachedChar(terminal, char, width, fg, x, y, bold && terminal.options.enableBold, dim, italic); | ||
this._drawUncachedChars(terminal, chars, width, fg, x, y, bold && terminal.options.enableBold, dim, italic); | ||
} | ||
@@ -260,7 +265,7 @@ } | ||
/** | ||
* Draws a character at a cell. The character will be clipped to | ||
* ensure that it fits with the cell, including the cell to the right if it's | ||
* a wide character. | ||
* Draws one or more characters at one or more cells. The character(s) will be | ||
* clipped to ensure that they fit with the cell(s), including the cell to the | ||
* right if the last character is a wide character. | ||
* @param terminal The terminal. | ||
* @param char The character. | ||
* @param chars The character. | ||
* @param width The width of the character. | ||
@@ -271,3 +276,3 @@ * @param fg The foreground color, in the format stored within the attributes. | ||
*/ | ||
private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean): void { | ||
private _drawUncachedChars(terminal: ITerminal, chars: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean): void { | ||
this._ctx.save(); | ||
@@ -294,3 +299,3 @@ this._ctx.font = this._getFont(terminal, bold, italic); | ||
this._ctx.fillText( | ||
char, | ||
chars, | ||
x * this._scaledCellWidth + this._scaledCharLeft, | ||
@@ -297,0 +302,0 @@ y * this._scaledCellHeight + this._scaledCharTop); |
@@ -7,3 +7,3 @@ /** | ||
import { IRenderer, IRenderDimensions, IColorSet } from '../Types'; | ||
import { ITerminal } from '../../Types'; | ||
import { ITerminal, CharacterJoinerHandler } from '../../Types'; | ||
import { ITheme } from 'xterm'; | ||
@@ -56,3 +56,3 @@ import { EventEmitter } from '../../EventEmitter'; | ||
this._rowContainer.setAttribute('aria-hidden', 'true'); | ||
this._refreshRowElements(this._terminal.rows, this._terminal.cols); | ||
this._refreshRowElements(this._terminal.cols, this._terminal.rows); | ||
this._selectionContainer = document.createElement('div'); | ||
@@ -86,2 +86,11 @@ this._selectionContainer.classList.add(SELECTION_CLASS); | ||
public dispose(): void { | ||
this._terminal.element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass); | ||
this._terminal.screenElement.removeChild(this._rowContainer); | ||
this._terminal.screenElement.removeChild(this._selectionContainer); | ||
this._terminal.screenElement.removeChild(this._themeStyleElement); | ||
this._terminal.screenElement.removeChild(this._dimensionsStyleElement); | ||
super.dispose(); | ||
} | ||
private _updateDimensions(): void { | ||
@@ -247,10 +256,10 @@ this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio; | ||
const documentFragment = document.createDocumentFragment(); | ||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; | ||
if (columnSelectMode) { | ||
documentFragment.appendChild( | ||
this._createSelectionElement(viewportCappedStartRow, startCol, end[0], viewportCappedEndRow - viewportStartRow + 1) | ||
this._createSelectionElement(viewportCappedStartRow, start[0], end[0], viewportCappedEndRow - viewportCappedStartRow + 1) | ||
); | ||
} else { | ||
// Draw first row | ||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; | ||
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; | ||
@@ -326,2 +335,5 @@ documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); | ||
} | ||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; } | ||
public deregisterCharacterJoiner(joinerId: number): boolean { return false; } | ||
} |
@@ -10,4 +10,4 @@ /** | ||
import { ColorManager } from './ColorManager'; | ||
import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Types'; | ||
import { ITerminal } from '../Types'; | ||
import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; | ||
import { ITerminal, CharacterJoinerHandler } from '../Types'; | ||
import { LinkRenderLayer } from './LinkRenderLayer'; | ||
@@ -18,2 +18,3 @@ import { EventEmitter } from '../EventEmitter'; | ||
import { ITheme } from 'xterm'; | ||
import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; | ||
@@ -28,2 +29,3 @@ export class Renderer extends EventEmitter implements IRenderer { | ||
private _needsFullRefresh: boolean = false; | ||
private _characterJoinerRegistry: ICharacterJoinerRegistry; | ||
@@ -37,2 +39,3 @@ public colorManager: ColorManager; | ||
this.colorManager = new ColorManager(document, allowTransparency); | ||
this._characterJoinerRegistry = new CharacterJoinerRegistry(_terminal); | ||
if (theme) { | ||
@@ -43,3 +46,3 @@ this.colorManager.setTheme(theme); | ||
this._renderLayers = [ | ||
new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, allowTransparency), | ||
new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, this._characterJoinerRegistry, allowTransparency), | ||
new SelectionRenderLayer(this._terminal.screenElement, 1, this.colorManager.colors), | ||
@@ -75,3 +78,3 @@ new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal), | ||
if ('IntersectionObserver' in window) { | ||
const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), {threshold: 0}); | ||
const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), { threshold: 0 }); | ||
observer.observe(this._terminal.element); | ||
@@ -82,2 +85,7 @@ this.register({ dispose: () => observer.disconnect() }); | ||
public dispose(): void { | ||
super.dispose(); | ||
this._renderLayers.forEach(l => l.dispose()); | ||
} | ||
public onIntersectionChange(entry: IntersectionObserverEntry): void { | ||
@@ -181,4 +189,4 @@ this._isPaused = entry.intersectionRatio === 0; | ||
* frame. | ||
* @param {number} start The start row. | ||
* @param {number} end The end row. | ||
* @param start The start row. | ||
* @param end The end row. | ||
*/ | ||
@@ -199,3 +207,3 @@ public refreshRows(start: number, end: number): void { | ||
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); | ||
this._terminal.emit('refresh', {start, end}); | ||
this._terminal.emit('refresh', { start, end }); | ||
} | ||
@@ -262,2 +270,10 @@ | ||
} | ||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number { | ||
return this._characterJoinerRegistry.registerCharacterJoiner(handler); | ||
} | ||
public deregisterCharacterJoiner(joinerId: number): boolean { | ||
return this._characterJoinerRegistry.deregisterCharacterJoiner(joinerId); | ||
} | ||
} |
@@ -75,3 +75,3 @@ /** | ||
if (columnSelectMode) { | ||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; | ||
const startCol = start[0]; | ||
const width = end[0] - startCol; | ||
@@ -78,0 +78,0 @@ const height = viewportCappedEndRow - viewportCappedStartRow + 1; |
@@ -6,4 +6,4 @@ /** | ||
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; | ||
import { FLAGS, IColorSet, IRenderDimensions } from './Types'; | ||
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE } from '../Buffer'; | ||
import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; | ||
import { CharData, ITerminal } from '../Types'; | ||
@@ -26,6 +26,8 @@ import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; | ||
private _characterOverlapCache: { [key: string]: boolean } = {}; | ||
private _characterJoinerRegistry: ICharacterJoinerRegistry; | ||
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, alpha: boolean) { | ||
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, characterJoinerRegistry: ICharacterJoinerRegistry, alpha: boolean) { | ||
super(container, 'text', zIndex, alpha, colors); | ||
this._state = new GridCache<CharData>(); | ||
this._characterJoinerRegistry = characterJoinerRegistry; | ||
} | ||
@@ -57,5 +59,6 @@ | ||
lastRow: number, | ||
joinerRegistry: ICharacterJoinerRegistry | null, | ||
callback: ( | ||
code: number, | ||
char: string, | ||
chars: string, | ||
width: number, | ||
@@ -72,9 +75,17 @@ x: number, | ||
const line = terminal.buffer.lines.get(row); | ||
const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; | ||
for (let x = 0; x < terminal.cols; x++) { | ||
const charData = line[x]; | ||
const code: number = <number>charData[CHAR_DATA_CODE_INDEX]; | ||
const char: string = charData[CHAR_DATA_CHAR_INDEX]; | ||
let code: number = <number>charData[CHAR_DATA_CODE_INDEX]; | ||
// Can either represent character(s) for a single cell or multiple cells | ||
// if indicated by a character joiner. | ||
let chars: string = charData[CHAR_DATA_CHAR_INDEX]; | ||
const attr: number = charData[CHAR_DATA_ATTR_INDEX]; | ||
let width: number = charData[CHAR_DATA_WIDTH_INDEX]; | ||
// If true, indicates that the current character(s) to draw were joined. | ||
let isJoined = false; | ||
let lastCharX = x; | ||
// The character to the left is a wide character, drawing is owned by | ||
@@ -86,5 +97,29 @@ // the char at x-1 | ||
// If the character is an overlapping char and the character to the right is a | ||
// space, take ownership of the cell to the right. | ||
if (this._isOverlapping(charData)) { | ||
// Process any joined character ranges as needed. Because of how the | ||
// ranges are produced, we know that they are valid for the characters | ||
// and attributes of our input. | ||
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) { | ||
isJoined = true; | ||
const range = joinedRanges.shift(); | ||
// We already know the exact start and end column of the joined range, | ||
// so we get the string and width representing it directly | ||
chars = terminal.buffer.translateBufferLineToString( | ||
row, | ||
true, | ||
range[0], | ||
range[1] | ||
); | ||
width = range[1] - range[0]; | ||
code = Infinity; | ||
// Skip over the cells occupied by this range in the loop | ||
lastCharX = range[1] - 1; | ||
} | ||
// If the character is an overlapping char and the character to the | ||
// right is a space, take ownership of the cell to the right. We skip | ||
// this check for joined characters because their rendering likely won't | ||
// yield the same result as rendering the last character individually. | ||
if (!isJoined && this._isOverlapping(charData)) { | ||
// If the character is overlapping, we want to force a re-render on every | ||
@@ -97,3 +132,3 @@ // frame. This is specifically to work around the case where two | ||
// this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; | ||
if (x < line.length - 1 && line[x + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) { | ||
if (lastCharX < line.length - 1 && line[lastCharX + 1][CHAR_DATA_CODE_INDEX] === NULL_CELL_CODE) { | ||
width = 2; | ||
@@ -125,3 +160,14 @@ // this._clearChar(x + 1, y); | ||
callback(code, char, width, x, y, fg, bg, flags); | ||
callback( | ||
code, | ||
chars, | ||
width, | ||
x, | ||
y, | ||
fg, | ||
bg, | ||
flags | ||
); | ||
x = lastCharX; | ||
} | ||
@@ -144,3 +190,3 @@ } | ||
this._forEachCell(terminal, firstRow, lastRow, (code, char, width, x, y, fg, bg, flags) => { | ||
this._forEachCell(terminal, firstRow, lastRow, null, (code, chars, width, x, y, fg, bg, flags) => { | ||
// libvte and xterm both draw the background (but not foreground) of invisible characters, | ||
@@ -187,3 +233,3 @@ // so we should too. | ||
private _drawForeground(terminal: ITerminal, firstRow: number, lastRow: number): void { | ||
this._forEachCell(terminal, firstRow, lastRow, (code, char, width, x, y, fg, bg, flags) => { | ||
this._forEachCell(terminal, firstRow, lastRow, this._characterJoinerRegistry, (code, chars, width, x, y, fg, bg, flags) => { | ||
if (flags & FLAGS.INVISIBLE) { | ||
@@ -202,7 +248,7 @@ return; | ||
} | ||
this.fillBottomLineAtCells(x, y); | ||
this.fillBottomLineAtCells(x, y, width); | ||
this._ctx.restore(); | ||
} | ||
this.drawChar( | ||
terminal, char, code, | ||
this.drawChars( | ||
terminal, chars, code, | ||
width, x, y, | ||
@@ -209,0 +255,0 @@ fg, bg, |
@@ -6,3 +6,3 @@ /** | ||
import { ITerminal } from '../Types'; | ||
import { ITerminal, CharacterJoinerHandler } from '../Types'; | ||
import { IEventEmitter, ITheme, IDisposable } from 'xterm'; | ||
@@ -44,2 +44,4 @@ import { IColorSet } from '../shared/Types'; | ||
refreshRows(start: number, end: number): void; | ||
registerCharacterJoiner(handler: CharacterJoinerHandler): number; | ||
deregisterCharacterJoiner(joinerId: number): boolean; | ||
} | ||
@@ -69,3 +71,3 @@ | ||
export interface IRenderLayer { | ||
export interface IRenderLayer extends IDisposable { | ||
/** | ||
@@ -108,2 +110,12 @@ * Called when the terminal loses focus. | ||
/** | ||
* Registers a handler to join characters to render as a group | ||
*/ | ||
registerCharacterJoiner?(joiner: ICharacterJoiner): void; | ||
/** | ||
* Deregisters the specified character joiner handler | ||
*/ | ||
deregisterCharacterJoiner?(joinerId: number): void; | ||
/** | ||
* Resize the render layer. | ||
@@ -118,1 +130,12 @@ */ | ||
} | ||
export interface ICharacterJoiner { | ||
id: number; | ||
handler: CharacterJoinerHandler; | ||
} | ||
export interface ICharacterJoinerRegistry { | ||
registerCharacterJoiner(handler: (text: string) => [number, number][]): number; | ||
deregisterCharacterJoiner(joinerId: number): boolean; | ||
getJoinedCharacters(row: number): [number, number][]; | ||
} |
@@ -12,3 +12,3 @@ /** | ||
import { SelectionModel } from './SelectionModel'; | ||
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; | ||
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer'; | ||
import { AltClickHandler } from './handlers/AltClickHandler'; | ||
@@ -455,4 +455,6 @@ | ||
private _removeMouseDownListeners(): void { | ||
this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); | ||
this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); | ||
if (this._terminal.element.ownerDocument) { | ||
this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); | ||
this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); | ||
} | ||
clearInterval(this._dragScrollIntervalTimer); | ||
@@ -579,7 +581,10 @@ this._dragScrollIntervalTimer = null; | ||
// If the cursor was above or below the viewport, make sure it's at the | ||
// start or end of the viewport respectively. | ||
if (this._dragScrollAmount > 0) { | ||
this._model.selectionEnd[0] = this._terminal.cols; | ||
} else if (this._dragScrollAmount < 0) { | ||
this._model.selectionEnd[0] = 0; | ||
// start or end of the viewport respectively. This should only happen when | ||
// NOT in column select mode. | ||
if (this._activeSelectionMode !== SelectionMode.COLUMN) { | ||
if (this._dragScrollAmount > 0) { | ||
this._model.selectionEnd[0] = this._terminal.cols; | ||
} else if (this._dragScrollAmount < 0) { | ||
this._model.selectionEnd[0] = 0; | ||
} | ||
} | ||
@@ -613,6 +618,15 @@ | ||
// Re-evaluate selection | ||
// If the cursor was above or below the viewport, make sure it's at the | ||
// start or end of the viewport respectively. This should only happen when | ||
// NOT in column select mode. | ||
if (this._dragScrollAmount > 0) { | ||
this._model.selectionEnd = [this._terminal.cols - 1, Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1)]; | ||
if (this._activeSelectionMode !== SelectionMode.COLUMN) { | ||
this._model.selectionEnd[0] = this._terminal.cols; | ||
} | ||
this._model.selectionEnd[1] = Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1); | ||
} else { | ||
this._model.selectionEnd = [0, this._terminal.buffer.ydisp]; | ||
if (this._activeSelectionMode !== SelectionMode.COLUMN) { | ||
this._model.selectionEnd[0] = 0; | ||
} | ||
this._model.selectionEnd[1] = this._terminal.buffer.ydisp; | ||
} | ||
@@ -684,3 +698,3 @@ this.refresh(); | ||
*/ | ||
private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): IWordPosition { | ||
private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition { | ||
// Ensure coords are within viewport (eg. not within scroll bar) | ||
@@ -780,3 +794,3 @@ if (coords[0] >= this._terminal.cols) { | ||
// column coordinates. | ||
const start = | ||
let start = | ||
startIndex // The index of the selection's start char in the line string | ||
@@ -789,3 +803,3 @@ + charOffset // The difference between the initial char's column and index | ||
// to column coordinates. | ||
const length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols | ||
let length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols | ||
endIndex // The index of the selection's end char in the line string | ||
@@ -802,2 +816,30 @@ - startIndex // The index of the selection's start char in the line string | ||
// Recurse upwards if the line is wrapped and the word wraps to the above line | ||
if (followWrappedLinesAbove) { | ||
if (start === 0 && bufferLine[0][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { | ||
const previousBufferLine = this._buffer.lines.get(coords[1] - 1); | ||
if (previousBufferLine && (<any>bufferLine).isWrapped && previousBufferLine[this._terminal.cols - 1][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { | ||
const previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false); | ||
if (previousLineWordPosition) { | ||
const offset = this._terminal.cols - previousLineWordPosition.start; | ||
start -= offset; | ||
length += offset; | ||
} | ||
} | ||
} | ||
} | ||
// Recurse downwards if the line is wrapped and the word wraps to the next line | ||
if (followWrappedLinesBelow) { | ||
if (start + length === this._terminal.cols && bufferLine[this._terminal.cols - 1][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { | ||
const nextBufferLine = this._buffer.lines.get(coords[1] + 1); | ||
if (nextBufferLine && (<any>nextBufferLine).isWrapped && nextBufferLine[0][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { | ||
const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true); | ||
if (nextLineWordPosition) { | ||
length += nextLineWordPosition.length; | ||
} | ||
} | ||
} | ||
} | ||
return { start, length }; | ||
@@ -814,2 +856,7 @@ } | ||
if (wordPosition) { | ||
// Adjust negative start value | ||
while (wordPosition.start < 0) { | ||
wordPosition.start += this._terminal.cols; | ||
coords[1]--; | ||
} | ||
this._model.selectionStart = [wordPosition.start, coords[1]]; | ||
@@ -827,3 +874,20 @@ this._model.selectionStartLength = wordPosition.length; | ||
if (wordPosition) { | ||
this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; | ||
let endRow = coords[1]; | ||
// Adjust negative start value | ||
while (wordPosition.start < 0) { | ||
wordPosition.start += this._terminal.cols; | ||
endRow--; | ||
} | ||
// Adjust wrapped length value, this only needs to happen when values are reversed as in that | ||
// case we're interested in the start of the word, not the end | ||
if (!this._model.areSelectionValuesReversed()) { | ||
while (wordPosition.start + wordPosition.length > this._terminal.cols) { | ||
wordPosition.length -= this._terminal.cols; | ||
endRow++; | ||
} | ||
} | ||
this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : wordPosition.start + wordPosition.length, endRow]; | ||
} | ||
@@ -830,0 +894,0 @@ } |
@@ -79,5 +79,9 @@ /** | ||
// Use the selection start if the end doesn't exist or they're reversed | ||
// Use the selection start + length if the end doesn't exist or they're reversed | ||
if (!this.selectionEnd || this.areSelectionValuesReversed()) { | ||
return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]]; | ||
const startPlusLength = this.selectionStart[0] + this.selectionStartLength; | ||
if (startPlusLength > this._terminal.cols) { | ||
return [startPlusLength % this._terminal.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._terminal.cols)]; | ||
} | ||
return [startPlusLength, this.selectionStart[1]]; | ||
} | ||
@@ -84,0 +88,0 @@ |
@@ -25,4 +25,4 @@ /** | ||
* Return if the given array contains the given element | ||
* @param {Array} array The array to search for the given element. | ||
* @param {Object} el The element to look for into the array | ||
* @param arr The array to search for the given element. | ||
* @param el The element to look for into the array | ||
*/ | ||
@@ -29,0 +29,0 @@ function contains(arr: any[], el: any): boolean { |
@@ -24,7 +24,7 @@ /** | ||
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData } from './Types'; | ||
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData, CharacterJoinerHandler } from './Types'; | ||
import { IMouseZoneManager } from './ui/Types'; | ||
import { IRenderer } from './renderer/Types'; | ||
import { BufferSet } from './BufferSet'; | ||
import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR } from './Buffer'; | ||
import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; | ||
import { CompositionHelper } from './CompositionHelper'; | ||
@@ -76,3 +76,3 @@ import { EventEmitter } from './EventEmitter'; | ||
*/ | ||
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows', 'rendererType']; | ||
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows']; | ||
@@ -140,3 +140,2 @@ const DEFAULT_OPTIONS: ITerminalOptions = { | ||
private _sendDataQueue: string; | ||
private _customKeyEventHandler: CustomKeyEventHandler; | ||
@@ -213,2 +212,3 @@ | ||
private _screenDprMonitor: ScreenDprMonitor; | ||
private _theme: ITheme; | ||
@@ -221,3 +221,3 @@ public cols: number; | ||
* | ||
* @param {object} options An object containing a set of options, the available options are: | ||
* @param options An object containing a set of options, the available options are: | ||
* - `cursorBlink` (boolean): Whether the terminal cursor blinks | ||
@@ -278,3 +278,2 @@ * - `cols` (number): The number of columns of the terminal (horizontal size) | ||
this.cursorHidden = false; | ||
this._sendDataQueue = ''; | ||
this._customKeyEventHandler = null; | ||
@@ -357,3 +356,3 @@ | ||
* Retrieves an option's value from the terminal. | ||
* @param {string} key The option key. | ||
* @param key The option key. | ||
*/ | ||
@@ -370,4 +369,4 @@ public getOption(key: string): any { | ||
* Sets an option on the terminal. | ||
* @param {string} key The option key. | ||
* @param {any} value The option value. | ||
* @param key The option key. | ||
* @param value The option value. | ||
*/ | ||
@@ -381,2 +380,5 @@ public setOption(key: string, value: any): void { | ||
} | ||
if (this.options[key] === value) { | ||
return; | ||
} | ||
switch (key) { | ||
@@ -408,2 +410,7 @@ case 'bellStyle': | ||
} | ||
case 'rendererType': | ||
if (!value) { | ||
value = 'canvas'; | ||
} | ||
break; | ||
case 'tabStopWidth': | ||
@@ -468,2 +475,14 @@ if (value < 1) { | ||
} | ||
case 'rendererType': | ||
if (this.renderer) { | ||
this.unregister(this.renderer); | ||
this.renderer.dispose(); | ||
this.renderer = null; | ||
} | ||
this._setupRenderer(); | ||
this.renderer.onCharSizeChanged(); | ||
if (this._theme) { | ||
this.renderer.setTheme(this._theme); | ||
} | ||
break; | ||
case 'scrollback': | ||
@@ -500,3 +519,3 @@ this.buffers.resize(this.cols, this.rows); | ||
if (this.sendFocus) { | ||
this.send(C0.ESC + '[I'); | ||
this.handler(C0.ESC + '[I'); | ||
} | ||
@@ -525,3 +544,3 @@ this.element.classList.add('focus'); | ||
if (this.sendFocus) { | ||
this.send(C0.ESC + '[O'); | ||
this.handler(C0.ESC + '[O'); | ||
} | ||
@@ -618,3 +637,3 @@ this.element.classList.remove('focus'); | ||
* | ||
* @param {HTMLElement} parent The element to create the terminal within. | ||
* @param parent The element to create the terminal within. | ||
*/ | ||
@@ -691,8 +710,4 @@ public open(parent: HTMLElement): void { | ||
switch (this.options.rendererType) { | ||
case 'canvas': this.renderer = new Renderer(this, this.options.theme); break; | ||
case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break; | ||
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); | ||
} | ||
this.register(this.renderer); | ||
this._setupRenderer(); | ||
this._theme = this.options.theme; | ||
this.options.theme = null; | ||
@@ -754,7 +769,17 @@ this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure); | ||
private _setupRenderer(): void { | ||
switch (this.options.rendererType) { | ||
case 'canvas': this.renderer = new Renderer(this, this.options.theme); break; | ||
case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break; | ||
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); | ||
} | ||
this.register(this.renderer); | ||
} | ||
/** | ||
* Sets the theme on the renderer. The renderer must have been initialized. | ||
* @param theme The theme to ste. | ||
* @param theme The theme to set. | ||
*/ | ||
private _setTheme(theme: ITheme): void { | ||
this._theme = theme; | ||
const colors = this.renderer.setTheme(theme); | ||
@@ -879,3 +904,3 @@ if (this.viewport) { | ||
data += '~[' + pos.x + ',' + pos.y + ']\r'; | ||
self.send(data); | ||
self.handler(data); | ||
return; | ||
@@ -893,3 +918,3 @@ } | ||
else if (button === 3) button = 3; | ||
self.send(C0.ESC + '[' | ||
self.handler(C0.ESC + '[' | ||
+ button | ||
@@ -914,3 +939,3 @@ + ';' | ||
pos.y++; | ||
self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); | ||
self.handler(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); | ||
return; | ||
@@ -922,3 +947,3 @@ } | ||
pos.y -= 32; | ||
self.send(C0.ESC + '[<' | ||
self.handler(C0.ESC + '[<' | ||
+ (((button & 3) === 3 ? button & ~3 : button) - 32) | ||
@@ -939,3 +964,3 @@ + ';' | ||
self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data)); | ||
self.handler(C0.ESC + '[M' + String.fromCharCode.apply(String, data)); | ||
} | ||
@@ -1089,3 +1114,3 @@ | ||
} | ||
this.send(data); | ||
this.handler(data); | ||
} | ||
@@ -1123,4 +1148,4 @@ return; | ||
* opportunity. | ||
* @param {number} start The row to start from (between 0 and this.rows - 1). | ||
* @param {number} end The row to end at (between start and this.rows - 1). | ||
* @param start The row to start from (between 0 and this.rows - 1). | ||
* @param end The row to end at (between start and this.rows - 1). | ||
*/ | ||
@@ -1135,4 +1160,4 @@ public refresh(start: number, end: number): void { | ||
* Queues linkification for the specified rows. | ||
* @param {number} start The row to start from (between 0 and this.rows - 1). | ||
* @param {number} end The row to end at (between start and this.rows - 1). | ||
* @param start The row to start from (between 0 and this.rows - 1). | ||
* @param end The row to end at (between start and this.rows - 1). | ||
*/ | ||
@@ -1229,4 +1254,4 @@ private _queueLinkification(start: number, end: number): void { | ||
* Scroll the display of the terminal | ||
* @param {number} disp The number of lines to scroll down (negative scroll up). | ||
* @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollLines. This is used | ||
* @param disp The number of lines to scroll down (negative scroll up). | ||
* @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used | ||
* to avoid unwanted events being handled by the viewport when the event was triggered from the | ||
@@ -1262,3 +1287,3 @@ * viewport originally. | ||
* Scroll the display of the terminal by a number of pages. | ||
* @param {number} pageCount The number of pages to scroll (negative scrolls up). | ||
* @param pageCount The number of pages to scroll (negative scrolls up). | ||
*/ | ||
@@ -1292,5 +1317,10 @@ public scrollPages(pageCount: number): void { | ||
* Writes text to the terminal. | ||
* @param {string} data The text to write to the terminal. | ||
* @param data The text to write to the terminal. | ||
*/ | ||
public write(data: string): void { | ||
// Ensure the terminal isn't disposed | ||
if (this._isDisposed) { | ||
return; | ||
} | ||
// Ignore falsy data values (including the empty string) | ||
@@ -1309,3 +1339,3 @@ if (!data) { | ||
// XON will be triggered by emulator before processing data chunk | ||
this.send(C0.DC3); | ||
this.handler(C0.DC3); | ||
this._xoffSentToCatchUp = true; | ||
@@ -1325,2 +1355,7 @@ } | ||
protected _innerWrite(): void { | ||
// Ensure the terminal isn't disposed | ||
if (this._isDisposed) { | ||
this.writeBuffer = []; | ||
} | ||
const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); | ||
@@ -1333,3 +1368,3 @@ while (writeBatch.length > 0) { | ||
if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { | ||
this.send(C0.DC1); | ||
this.handler(C0.DC1); | ||
this._xoffSentToCatchUp = false; | ||
@@ -1362,3 +1397,3 @@ } | ||
* Writes text to the terminal, followed by a break line character (\n). | ||
* @param {string} data The text to write to the terminal. | ||
* @param data The text to write to the terminal. | ||
*/ | ||
@@ -1408,2 +1443,14 @@ public writeln(data: string): void { | ||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number { | ||
const joinerId = this.renderer.registerCharacterJoiner(handler); | ||
this.refresh(0, this.rows - 1); | ||
return joinerId; | ||
} | ||
public deregisterCharacterJoiner(joinerId: number): void { | ||
if (this.renderer.deregisterCharacterJoiner(joinerId)) { | ||
this.refresh(0, this.rows - 1); | ||
} | ||
} | ||
public get markers(): IMarker[] { | ||
@@ -1465,3 +1512,3 @@ return this.buffer.markers; | ||
* - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent | ||
* @param {KeyboardEvent} ev The keydown event to be handled. | ||
* @param ev The keydown event to be handled. | ||
*/ | ||
@@ -1563,3 +1610,3 @@ protected _keyDown(event: KeyboardEvent): boolean { | ||
* - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent | ||
* @param {KeyboardEvent} ev The keypress event to be handled. | ||
* @param ev The keypress event to be handled. | ||
*/ | ||
@@ -1602,17 +1649,2 @@ protected _keyPress(ev: KeyboardEvent): boolean { | ||
/** | ||
* Send data for handling to the terminal | ||
* @param {string} data | ||
*/ | ||
public send(data: string): void { | ||
if (!this._sendDataQueue) { | ||
setTimeout(() => { | ||
this.handler(this._sendDataQueue); | ||
this._sendDataQueue = ''; | ||
}, 1); | ||
} | ||
this._sendDataQueue += data; | ||
} | ||
/** | ||
* Ring the bell. | ||
@@ -1657,4 +1689,4 @@ * Note: We could do sweet things with webaudio here | ||
* | ||
* @param {number} x The number of columns to resize to. | ||
* @param {number} y The number of rows to resize to. | ||
* @param x The number of columns to resize to. | ||
* @param y The number of rows to resize to. | ||
*/ | ||
@@ -1693,3 +1725,3 @@ public resize(x: number, y: number): void { | ||
* Updates the range of rows to refresh | ||
* @param {number} y The number of rows to refresh next. | ||
* @param y The number of rows to refresh next. | ||
*/ | ||
@@ -1717,4 +1749,4 @@ public updateRange(y: number): void { | ||
* Erase in the identified line everything from "x" to the end of the line (right). | ||
* @param {number} x The column from which to start erasing to the end of the line. | ||
* @param {number} y The line in which to operate. | ||
* @param x The column from which to start erasing to the end of the line. | ||
* @param y The line in which to operate. | ||
*/ | ||
@@ -1726,3 +1758,3 @@ public eraseRight(x: number, y: number): void { | ||
} | ||
const ch: CharData = [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; // xterm | ||
const ch: CharData = [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm | ||
for (; x < this.cols; x++) { | ||
@@ -1736,4 +1768,4 @@ line[x] = ch; | ||
* Erase in the identified line everything from "x" to the start of the line (left). | ||
* @param {number} x The column from which to start erasing to the start of the line. | ||
* @param {number} y The line in which to operate. | ||
* @param x The column from which to start erasing to the start of the line. | ||
* @param y The line in which to operate. | ||
*/ | ||
@@ -1745,3 +1777,3 @@ public eraseLeft(x: number, y: number): void { | ||
} | ||
const ch: CharData = [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; // xterm | ||
const ch: CharData = [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm | ||
x++; | ||
@@ -1776,3 +1808,3 @@ while (x--) { | ||
* Erase all content in the given line | ||
* @param {number} y The line to erase all of its contents. | ||
* @param y The line to erase all of its contents. | ||
*/ | ||
@@ -1785,5 +1817,5 @@ public eraseLine(y: number): void { | ||
* Return the data array of a blank line | ||
* @param {boolean} cur First bunch of data for each "blank" character. | ||
* @param {boolean} isWrapped Whether the new line is wrapped from the previous line. | ||
* @param {boolean} cols The number of columns in the terminal, if this is not | ||
* @param cur First bunch of data for each "blank" character. | ||
* @param isWrapped Whether the new line is wrapped from the previous line. | ||
* @param cols The number of columns in the terminal, if this is not | ||
* set, the terminal's current column count would be used. | ||
@@ -1794,3 +1826,3 @@ */ | ||
const ch: CharData = [attr, ' ', 1, 32 /* ' '.charCodeAt(0) */]; // width defaults to 1 halfwidth character | ||
const ch: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // width defaults to 1 halfwidth character | ||
const line: LineData = []; | ||
@@ -1818,5 +1850,5 @@ | ||
if (cur) { | ||
return [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; | ||
return [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; | ||
} | ||
return [DEFAULT_ATTR, ' ', 1, 32 /* ' '.charCodeAt(0) */]; | ||
return [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; | ||
} | ||
@@ -1834,3 +1866,3 @@ | ||
* Emit the 'data' event and populate the given data. | ||
* @param {string} data The data to populate in the event. | ||
* @param data The data to populate in the event. | ||
*/ | ||
@@ -1857,3 +1889,3 @@ public handler(data: string): void { | ||
* Emit the 'title' event and populate the given title. | ||
* @param {string} title The title to populate in the event. | ||
* @param title The title to populate in the event. | ||
*/ | ||
@@ -1860,0 +1892,0 @@ public handleTitle(title: string): void { |
@@ -21,2 +21,4 @@ /** | ||
export type CharacterJoinerHandler = (text: string) => [number, number][]; | ||
export const enum LinkHoverEventTypes { | ||
@@ -49,2 +51,3 @@ HOVER = 'linkhover', | ||
curAttr: number; | ||
savedCurAttr: number; | ||
savedCols: number; | ||
@@ -78,3 +81,2 @@ x10Mouse: boolean; | ||
is(term: string): boolean; | ||
send(data: string): void; | ||
setgCharset(g: number, charset: ICharset): void; | ||
@@ -90,2 +92,6 @@ resize(x: number, y: number): void; | ||
tabSet(): void; | ||
handler(data: string): void; | ||
handleTitle(title: string): void; | ||
index(): void; | ||
reverseIndex(): void; | ||
} | ||
@@ -230,3 +236,2 @@ | ||
handler(data: string): void; | ||
send(data: string): void; | ||
scrollLines(disp: number, suppressScrollEvent?: boolean): void; | ||
@@ -233,0 +238,0 @@ cancel(ev: Event, force?: boolean): boolean | void; |
@@ -419,2 +419,9 @@ /** | ||
/** | ||
* Emits an event on the terminal. | ||
* @param type The type of event | ||
* @param data data associated with the event. | ||
* @deprecated This is being removed from the API with no replacement, see | ||
* issue #1505. | ||
*/ | ||
emit(type: string, data?: any): void; | ||
@@ -475,2 +482,40 @@ | ||
/** | ||
* (EXPERIMENTAL) Registers a character joiner, allowing custom sequences of | ||
* characters to be rendered as a single unit. This is useful in particular | ||
* for rendering ligatures and graphemes, among other things. | ||
* | ||
* Each registered character joiner is called with a string of text | ||
* representing a portion of a line in the terminal that can be rendered as | ||
* a single unit. The joiner must return a sorted array, where each entry is | ||
* itself an array of length two, containing the start (inclusive) and end | ||
* (exclusive) index of a substring of the input that should be rendered as | ||
* a single unit. When multiple joiners are provided, the results of each | ||
* are collected. If there are any overlapping substrings between them, they | ||
* are combined into one larger unit that is drawn together. | ||
* | ||
* All character joiners that are registered get called every time a line is | ||
* rendered in the terminal, so it is essential for the handler function to | ||
* run as quickly as possible to avoid slowdowns when rendering. Similarly, | ||
* joiners should strive to return the smallest possible substrings to | ||
* render together, since they aren't drawn as optimally as individual | ||
* characters. | ||
* | ||
* NOTE: character joiners are only used by the canvas renderer. | ||
* | ||
* @param handler The function that determines character joins. It is called | ||
* with a string of text that is eligible for joining and returns an array | ||
* where each entry is an array containing the start (inclusive) and end | ||
* (exclusive) indexes of ranges that should be rendered as a single unit. | ||
* @return The ID of the new joiner, this can be used to deregister | ||
*/ | ||
registerCharacterJoiner(handler: (text: string) => [number, number][]): number; | ||
/** | ||
* (EXPERIMENTAL) Deregisters the character joiner if one was registered. | ||
* NOTE: character joiners are only used by the canvas renderer. | ||
* @param joinerId The character joiner's ID (returned after register) | ||
*/ | ||
deregisterCharacterJoiner(joinerId: number): void; | ||
/** | ||
* (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the | ||
@@ -477,0 +522,0 @@ * alt buffer is active, undefined is returned. |
@@ -9,3 +9,12 @@ const path = require('path'); | ||
}, | ||
devtool: 'source-map' | ||
devtool: 'source-map', | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.js$/, | ||
use: ["source-map-loader"], | ||
enforce: "pre" | ||
} | ||
] | ||
} | ||
}; |
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 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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
2153191
281
32758
37
194
2