xterm-addon-serialize
Advanced tools
Comparing version
@@ -1,2 +0,2 @@ | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SerializeAddon=t():e.SerializeAddon=t()}(window,(function(){return function(e){var t={};function i(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}return i.m=e,i.c=t,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=0)}([function(e,t,i){"use strict";var n,r=this&&this.__extends||(n=function(e,t){return(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)});Object.defineProperty(t,"__esModule",{value:!0}),t.SerializeAddon=void 0;var o=function(e){function t(t){var i=e.call(this,t)||this;return i._rowIndex=0,i._allRows=new Array,i._currentRow="",i._nullCellCount=0,i}return r(t,e),t.prototype._beforeSerialize=function(e){this._allRows=new Array(e)},t.prototype._rowEnd=function(e){this._allRows[this._rowIndex++]=this._currentRow,this._currentRow="",this._nullCellCount=0},t.prototype._nextCell=function(e,t,i,n){var r,o,l=[],s=(o=t,!((r=e).getFgColorMode()===o.getFgColorMode()&&r.getFgColor()===o.getFgColor())),u=!function(e,t){return e.getBgColorMode()===t.getBgColorMode()&&e.getBgColor()===t.getBgColor()}(e,t),a=!function(e,t){return e.isInverse()===t.isInverse()&&e.isBold()===t.isBold()&&e.isUnderline()===t.isUnderline()&&e.isBlink()===t.isBlink()&&e.isInvisible()===t.isInvisible()&&e.isItalic()===t.isItalic()&&e.isDim()===t.isDim()}(e,t);if(s||u||a)if(e.isAttributeDefault())this._currentRow+="[0m";else{if(s){var f=e.getFgColor();e.isFgRGB()?l.push(38,2,f>>>16&255,f>>>8&255,255&f):e.isFgPalette()?f>=16?l.push(38,5,f):l.push(8&f?90+(7&f):30+(7&f)):l.push(39)}if(u){f=e.getBgColor();e.isBgRGB()?l.push(48,2,f>>>16&255,f>>>8&255,255&f):e.isBgPalette()?f>=16?l.push(48,5,f):l.push(8&f?100+(7&f):40+(7&f)):l.push(49)}a&&(e.isInverse()!==t.isInverse()&&l.push(e.isInverse()?7:27),e.isBold()!==t.isBold()&&l.push(e.isBold()?1:22),e.isUnderline()!==t.isUnderline()&&l.push(e.isUnderline()?4:24),e.isBlink()!==t.isBlink()&&l.push(e.isBlink()?5:25),e.isInvisible()!==t.isInvisible()&&l.push(e.isInvisible()?8:28),e.isItalic()!==t.isItalic()&&l.push(e.isItalic()?3:23),e.isDim()!==t.isDim()&&l.push(e.isDim()?2:22))}l.length&&(this._currentRow+="["+l.join(";")+"m"),""===e.getChars()?this._nullCellCount+=e.getWidth():this._nullCellCount>0&&(this._currentRow+="["+this._nullCellCount+"C",this._nullCellCount=0),this._currentRow+=e.getChars()},t.prototype._serializeString=function(){for(var e=this._allRows.length;e>0&&!this._allRows[e-1];e--);return this._allRows.slice(0,e).join("\r\n")},t}(function(){function e(e){this._buffer=e}return e.prototype.serialize=function(e,t){var i=this._buffer.getNullCell(),n=this._buffer.getNullCell(),r=i;this._beforeSerialize(t-e);for(var o=e;o<t;o++){var l=this._buffer.getLine(o);if(l)for(var s=0;s<l.length;s++){var u=l.getCell(s,r===i?n:i);u?(this._nextCell(u,r,o,s),r=u):console.warn("Can't get cell at row="+o+", col="+s)}this._rowEnd(o)}return this._afterSerialize(),this._serializeString()},e.prototype._nextCell=function(e,t,i,n){},e.prototype._rowEnd=function(e){},e.prototype._beforeSerialize=function(e){},e.prototype._afterSerialize=function(){},e.prototype._serializeString=function(){return""},e}()),l=function(){function e(){}return e.prototype.activate=function(e){this._terminal=e},e.prototype.serialize=function(e){if(!this._terminal)throw new Error("Cannot use addon until it has been loaded");var t,i,n,r=this._terminal.buffer.active.length,l=new o(this._terminal.buffer.active);return e=void 0===e?r:(t=e,i=0,n=r,Math.max(i,Math.min(t,n))),l.serialize(r-e,r)},e.prototype.dispose=function(){},e}();t.SerializeAddon=l}])})); | ||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SerializeAddon=e():t.SerializeAddon=e()}(self,(function(){return(()=>{"use strict";var t={44:function(t,e){var r,i=this&&this.__extends||(r=function(t,e){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])})(t,e)},function(t,e){function i(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(i.prototype=e.prototype,new i)});function s(t,e){return t.getBgColorMode()===e.getBgColorMode()&&t.getBgColor()===e.getBgColor()}Object.defineProperty(e,"__esModule",{value:!0}),e.SerializeAddon=void 0;var o=function(t){function e(e,r){var i=t.call(this,e)||this;return i._buffer1=e,i._terminal=r,i._rowIndex=0,i._allRows=new Array,i._allRowSeparators=new Array,i._currentRow="",i._nullCellCount=0,i._cursorStyle=i._buffer1.getNullCell(),i._cursorStyleRow=0,i._cursorStyleCol=0,i._backgroundCell=i._buffer1.getNullCell(),i._firstRow=0,i._lastCursorRow=0,i._lastCursorCol=0,i._lastContentCursorRow=0,i._lastContentCursorCol=0,i._thisRowLastChar=i._buffer1.getNullCell(),i._thisRowLastSecondChar=i._buffer1.getNullCell(),i._nextRowFirstChar=i._buffer1.getNullCell(),i}return i(e,t),e.prototype._beforeSerialize=function(t,e,r){this._allRows=new Array(t),this._lastContentCursorRow=e,this._lastCursorRow=e,this._firstRow=e},e.prototype._rowEnd=function(t,e){var r;this._nullCellCount>0&&!s(this._cursorStyle,this._backgroundCell)&&(this._currentRow+="["+this._nullCellCount+"X");var i="";if(!e){t-this._firstRow>=this._terminal.rows&&(null===(r=this._buffer1.getLine(this._cursorStyleRow))||void 0===r||r.getCell(this._cursorStyleCol,this._backgroundCell));var o=this._buffer1.getLine(t),l=this._buffer1.getLine(t+1);if(l.isWrapped){i="";var n=o.getCell(o.length-1,this._thisRowLastChar),u=o.getCell(o.length-2,this._thisRowLastSecondChar),h=l.getCell(0,this._nextRowFirstChar),_=h.getWidth()>1,a=!1;(h.getChars()&&_?this._nullCellCount<=1:this._nullCellCount<=0)&&((n.getChars()||0===n.getWidth())&&s(n,h)&&(a=!0),_&&(u.getChars()||0===u.getWidth())&&s(n,h)&&s(u,h)&&(a=!0)),a||(i="-".repeat(this._nullCellCount+1),i+="[1D[1X",this._nullCellCount>0&&(i+="[A",i+="["+(o.length-this._nullCellCount)+"C",i+="["+this._nullCellCount+"X",i+="["+(o.length-this._nullCellCount)+"D",i+="[B"),this._lastContentCursorRow=t+1,this._lastContentCursorCol=0,this._lastCursorRow=t+1,this._lastCursorCol=0)}else i="\r\n",this._lastCursorRow=t+1,this._lastCursorCol=0}this._allRows[this._rowIndex]=this._currentRow,this._allRowSeparators[this._rowIndex++]=i,this._currentRow="",this._nullCellCount=0},e.prototype._diffStyle=function(t,e){var r,i,o=[],l=(i=e,!((r=t).getFgColorMode()===i.getFgColorMode()&&r.getFgColor()===i.getFgColor())),n=!s(t,e),u=!function(t,e){return t.isInverse()===e.isInverse()&&t.isBold()===e.isBold()&&t.isUnderline()===e.isUnderline()&&t.isBlink()===e.isBlink()&&t.isInvisible()===e.isInvisible()&&t.isItalic()===e.isItalic()&&t.isDim()===e.isDim()}(t,e);if(l||n||u)if(t.isAttributeDefault())e.isAttributeDefault()||o.push(0);else{if(l){var h=t.getFgColor();t.isFgRGB()?o.push(38,2,h>>>16&255,h>>>8&255,255&h):t.isFgPalette()?h>=16?o.push(38,5,h):o.push(8&h?90+(7&h):30+(7&h)):o.push(39)}n&&(h=t.getBgColor(),t.isBgRGB()?o.push(48,2,h>>>16&255,h>>>8&255,255&h):t.isBgPalette()?h>=16?o.push(48,5,h):o.push(8&h?100+(7&h):40+(7&h)):o.push(49)),u&&(t.isInverse()!==e.isInverse()&&o.push(t.isInverse()?7:27),t.isBold()!==e.isBold()&&o.push(t.isBold()?1:22),t.isUnderline()!==e.isUnderline()&&o.push(t.isUnderline()?4:24),t.isBlink()!==e.isBlink()&&o.push(t.isBlink()?5:25),t.isInvisible()!==e.isInvisible()&&o.push(t.isInvisible()?8:28),t.isItalic()!==e.isItalic()&&o.push(t.isItalic()?3:23),t.isDim()!==e.isDim()&&o.push(t.isDim()?2:22))}return o},e.prototype._nextCell=function(t,e,r,i){if(0!==t.getWidth()){var o=""===t.getChars(),l=this._diffStyle(t,this._cursorStyle);if(o?!s(this._cursorStyle,t):l.length>0){this._nullCellCount>0&&(s(this._cursorStyle,this._backgroundCell)||(this._currentRow+="["+this._nullCellCount+"X"),this._currentRow+="["+this._nullCellCount+"C",this._nullCellCount=0),this._lastContentCursorRow=this._lastCursorRow=r,this._lastContentCursorCol=this._lastCursorCol=i,this._currentRow+="["+l.join(";")+"m";var n=this._buffer1.getLine(r);void 0!==n&&(n.getCell(i,this._cursorStyle),this._cursorStyleRow=r,this._cursorStyleCol=i)}o?this._nullCellCount+=t.getWidth():(this._nullCellCount>0&&(s(this._cursorStyle,this._backgroundCell)||(this._currentRow+="["+this._nullCellCount+"X"),this._currentRow+="["+this._nullCellCount+"C",this._nullCellCount=0),this._currentRow+=t.getChars(),this._lastContentCursorRow=this._lastCursorRow=r,this._lastContentCursorCol=this._lastCursorCol=i+t.getWidth())}},e.prototype._serializeString=function(){var t=this._allRows.length;this._buffer1.length-this._firstRow<=this._terminal.rows&&(t=this._lastContentCursorRow+1-this._firstRow,this._lastCursorCol=this._lastContentCursorCol,this._lastCursorRow=this._lastContentCursorRow);for(var e="",r=0;r<t;r++)e+=this._allRows[r],r+1<t&&(e+=this._allRowSeparators[r]);var i,s=this._buffer1.baseY+this._buffer1.cursorY,o=this._buffer1.cursorX;return(s!==this._lastCursorRow||o!==this._lastCursorCol)&&((i=s-this._lastCursorRow)>0?e+="["+i+"B":i<0&&(e+="["+-i+"A"),function(t){t>0?e+="["+t+"C":t<0&&(e+="["+-t+"D")}(o-this._lastCursorCol)),e},e}(function(){function t(t){this._buffer=t}return t.prototype.serialize=function(t,e){var r=this._buffer.getNullCell(),i=this._buffer.getNullCell(),s=r;this._beforeSerialize(e-t,t,e);for(var o=t;o<e;o++){var l=this._buffer.getLine(o);if(l)for(var n=0;n<l.length;n++){var u=l.getCell(n,s===r?i:r);u?(this._nextCell(u,s,o,n),s=u):console.warn("Can't get cell at row="+o+", col="+n)}this._rowEnd(o,o===e-1)}return this._afterSerialize(),this._serializeString()},t.prototype._nextCell=function(t,e,r,i){},t.prototype._rowEnd=function(t,e){},t.prototype._beforeSerialize=function(t,e,r){},t.prototype._afterSerialize=function(){},t.prototype._serializeString=function(){return""},t}()),l=function(){function t(){}return t.prototype.activate=function(t){this._terminal=t},t.prototype._getString=function(t,e){var r,i,s=t.length,l=new o(t,this._terminal),n=void 0===e?s:(r=e+this._terminal.rows,0,i=s,Math.max(0,Math.min(r,i)));return l.serialize(s-n,s)},t.prototype.serialize=function(t){if(!this._terminal)throw new Error("Cannot use addon until it has been loaded");return"normal"===this._terminal.buffer.active.type?this._getString(this._terminal.buffer.active,t):this._getString(this._terminal.buffer.normal,t)+"[?1049h[H"+this._getString(this._terminal.buffer.alternate,void 0)},t.prototype.dispose=function(){},t}();e.SerializeAddon=l}},e={};return function r(i){if(e[i])return e[i].exports;var s=e[i]={exports:{}};return t[i].call(s.exports,s,s.exports,r),s.exports}(44)})()})); | ||
//# sourceMappingURL=xterm-addon-serialize.js.map |
@@ -23,3 +23,3 @@ "use strict"; | ||
let oldCell = cell1; | ||
this._beforeSerialize(endRow - startRow); | ||
this._beforeSerialize(endRow - startRow, startRow, endRow); | ||
for (let row = startRow; row < endRow; row++) { | ||
@@ -38,3 +38,3 @@ const line = this._buffer.getLine(row); | ||
} | ||
this._rowEnd(row); | ||
this._rowEnd(row, row === endRow - 1); | ||
} | ||
@@ -45,4 +45,4 @@ this._afterSerialize(); | ||
_nextCell(cell, oldCell, row, col) { } | ||
_rowEnd(row) { } | ||
_beforeSerialize(rows) { } | ||
_rowEnd(row, isLastRow) { } | ||
_beforeSerialize(rows, startRow, endRow) { } | ||
_afterSerialize() { } | ||
@@ -69,18 +69,126 @@ _serializeString() { return ''; } | ||
class StringSerializeHandler extends BaseSerializeHandler { | ||
constructor(buffer) { | ||
super(buffer); | ||
constructor(_buffer1, _terminal) { | ||
super(_buffer1); | ||
this._buffer1 = _buffer1; | ||
this._terminal = _terminal; | ||
this._rowIndex = 0; | ||
this._allRows = new Array(); | ||
this._allRowSeparators = new Array(); | ||
this._currentRow = ''; | ||
this._nullCellCount = 0; | ||
// we can see a full colored cell and a null cell that only have background the same style | ||
// but the information isn't preserved by null cell itself | ||
// so wee need to record it when required. | ||
this._cursorStyle = this._buffer1.getNullCell(); | ||
// where exact the cursor styles comes from | ||
// because we can't copy the cell directly | ||
// so we remember where the content comes from instead | ||
this._cursorStyleRow = 0; | ||
this._cursorStyleCol = 0; | ||
// this is a null cell for reference for checking whether background is empty or not | ||
this._backgroundCell = this._buffer1.getNullCell(); | ||
this._firstRow = 0; | ||
this._lastCursorRow = 0; | ||
this._lastCursorCol = 0; | ||
this._lastContentCursorRow = 0; | ||
this._lastContentCursorCol = 0; | ||
this._thisRowLastChar = this._buffer1.getNullCell(); | ||
this._thisRowLastSecondChar = this._buffer1.getNullCell(); | ||
this._nextRowFirstChar = this._buffer1.getNullCell(); | ||
} | ||
_beforeSerialize(rows) { | ||
_beforeSerialize(rows, start, end) { | ||
this._allRows = new Array(rows); | ||
this._lastContentCursorRow = start; | ||
this._lastCursorRow = start; | ||
this._firstRow = start; | ||
} | ||
_rowEnd(row) { | ||
this._allRows[this._rowIndex++] = this._currentRow; | ||
_rowEnd(row, isLastRow) { | ||
var _a; | ||
// if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing | ||
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) { | ||
// use clear right to set background. | ||
this._currentRow += `\x1b[${this._nullCellCount}X`; | ||
} | ||
let rowSeparator = ''; | ||
// handle row separator | ||
if (!isLastRow) { | ||
// Enable BCE | ||
if (row - this._firstRow >= this._terminal.rows) { | ||
(_a = this._buffer1.getLine(this._cursorStyleRow)) === null || _a === void 0 ? void 0 : _a.getCell(this._cursorStyleCol, this._backgroundCell); | ||
} | ||
// Fetch current line | ||
const currentLine = this._buffer1.getLine(row); | ||
// Fetch next line | ||
const nextLine = this._buffer1.getLine(row + 1); | ||
if (!nextLine.isWrapped) { | ||
// just insert the line break | ||
rowSeparator = '\r\n'; | ||
// we sended the enter | ||
this._lastCursorRow = row + 1; | ||
this._lastCursorCol = 0; | ||
} | ||
else { | ||
rowSeparator = ''; | ||
const thisRowLastChar = currentLine.getCell(currentLine.length - 1, this._thisRowLastChar); | ||
const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2, this._thisRowLastSecondChar); | ||
const nextRowFirstChar = nextLine.getCell(0, this._nextRowFirstChar); | ||
const isNextRowFirstCharDoubleWidth = nextRowFirstChar.getWidth() > 1; | ||
// validate whether this line wrap is ever possible | ||
// which mean whether cursor can placed at a overflow position (x === row) naturally | ||
let isValid = false; | ||
if ( | ||
// you must output character to cause overflow, control sequence can't do this | ||
nextRowFirstChar.getChars() && | ||
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0) { | ||
if ( | ||
// the last character can't be null, | ||
// you can't use control sequence to move cursor to (x === row) | ||
(thisRowLastChar.getChars() || thisRowLastChar.getWidth() === 0) && | ||
// change background of the first wrapped cell also affects BCE | ||
// so we mark it as invalid to simply the process to determine line separator | ||
equalBg(thisRowLastChar, nextRowFirstChar)) { | ||
isValid = true; | ||
} | ||
if ( | ||
// the second to last character can't be null if the next line starts with CJK, | ||
// you can't use control sequence to move cursor to (x === row) | ||
isNextRowFirstCharDoubleWidth && | ||
(thisRowLastSecondChar.getChars() || thisRowLastSecondChar.getWidth() === 0) && | ||
// change background of the first wrapped cell also affects BCE | ||
// so we mark it as invalid to simply the process to determine line separator | ||
equalBg(thisRowLastChar, nextRowFirstChar) && | ||
equalBg(thisRowLastSecondChar, nextRowFirstChar)) { | ||
isValid = true; | ||
} | ||
} | ||
if (!isValid) { | ||
// force the wrap with magic | ||
// insert enough character to force the wrap | ||
rowSeparator = '-'.repeat(this._nullCellCount + 1); | ||
// move back and erase next line head | ||
rowSeparator += '\x1b[1D\x1b[1X'; | ||
if (this._nullCellCount > 0) { | ||
// do these because we filled the last several null slot, which we shouldn't | ||
rowSeparator += '\x1b[A'; | ||
rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}C`; | ||
rowSeparator += `\x1b[${this._nullCellCount}X`; | ||
rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}D`; | ||
rowSeparator += '\x1b[B'; | ||
} | ||
// This is content and need the be serialized even it is invisible. | ||
// without this, wrap will be missing from outputs. | ||
this._lastContentCursorRow = row + 1; | ||
this._lastContentCursorCol = 0; | ||
// force commit the cursor position | ||
this._lastCursorRow = row + 1; | ||
this._lastCursorCol = 0; | ||
} | ||
} | ||
} | ||
this._allRows[this._rowIndex] = this._currentRow; | ||
this._allRowSeparators[this._rowIndex++] = rowSeparator; | ||
this._currentRow = ''; | ||
this._nullCellCount = 0; | ||
} | ||
_nextCell(cell, oldCell, row, col) { | ||
_diffStyle(cell, oldCell) { | ||
const sgrSeq = []; | ||
@@ -92,3 +200,5 @@ const fgChanged = !equalFg(cell, oldCell); | ||
if (cell.isAttributeDefault()) { | ||
this._currentRow += '\x1b[0m'; | ||
if (!oldCell.isAttributeDefault()) { | ||
sgrSeq.push(0); | ||
} | ||
} | ||
@@ -155,24 +265,107 @@ else { | ||
} | ||
if (sgrSeq.length) { | ||
return sgrSeq; | ||
} | ||
_nextCell(cell, oldCell, row, col) { | ||
// a width 0 cell don't need to be count because it is just a placeholder after a CJK character; | ||
const isPlaceHolderCell = cell.getWidth() === 0; | ||
if (isPlaceHolderCell) { | ||
return; | ||
} | ||
// this cell don't have content | ||
const isEmptyCell = cell.getChars() === ''; | ||
const sgrSeq = this._diffStyle(cell, this._cursorStyle); | ||
// the empty cell style is only assumed to be changed when background changed, because foreground is always 0. | ||
const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0; | ||
/** | ||
* handles style change | ||
*/ | ||
if (styleChanged) { | ||
// before update the style, we need to fill empty cell back | ||
if (this._nullCellCount > 0) { | ||
// use clear right to set background. | ||
if (!equalBg(this._cursorStyle, this._backgroundCell)) { | ||
this._currentRow += `\x1b[${this._nullCellCount}X`; | ||
} | ||
// use move right to move cursor. | ||
this._currentRow += `\x1b[${this._nullCellCount}C`; | ||
this._nullCellCount = 0; | ||
} | ||
this._lastContentCursorRow = this._lastCursorRow = row; | ||
this._lastContentCursorCol = this._lastCursorCol = col; | ||
this._currentRow += `\x1b[${sgrSeq.join(';')}m`; | ||
// update the last cursor style | ||
const line = this._buffer1.getLine(row); | ||
if (line !== undefined) { | ||
line.getCell(col, this._cursorStyle); | ||
this._cursorStyleRow = row; | ||
this._cursorStyleCol = col; | ||
} | ||
} | ||
// Count number of null cells encountered after the last non-null cell and move the cursor | ||
// if a non-null cell is found (eg. \t or cursor move) | ||
if (cell.getChars() === '') { | ||
/** | ||
* handles actual content | ||
*/ | ||
if (isEmptyCell) { | ||
this._nullCellCount += cell.getWidth(); | ||
} | ||
else if (this._nullCellCount > 0) { | ||
this._currentRow += `\x1b[${this._nullCellCount}C`; | ||
this._nullCellCount = 0; | ||
else { | ||
if (this._nullCellCount > 0) { | ||
// we can just assume we have same style with previous one here | ||
// because style change is handled by previous stage | ||
// use move right when background is empty, use clear right when there is background. | ||
if (equalBg(this._cursorStyle, this._backgroundCell)) { | ||
this._currentRow += `\x1b[${this._nullCellCount}C`; | ||
} | ||
else { | ||
this._currentRow += `\x1b[${this._nullCellCount}X`; | ||
this._currentRow += `\x1b[${this._nullCellCount}C`; | ||
} | ||
this._nullCellCount = 0; | ||
} | ||
this._currentRow += cell.getChars(); | ||
// update cursor | ||
this._lastContentCursorRow = this._lastCursorRow = row; | ||
this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth(); | ||
} | ||
this._currentRow += cell.getChars(); | ||
} | ||
_serializeString() { | ||
let rowEnd = this._allRows.length; | ||
for (; rowEnd > 0; rowEnd--) { | ||
if (this._allRows[rowEnd - 1]) { | ||
break; | ||
// the fixup is only required for data without scrollback | ||
// because it will always be placed at last line otherwise | ||
if (this._buffer1.length - this._firstRow <= this._terminal.rows) { | ||
rowEnd = this._lastContentCursorRow + 1 - this._firstRow; | ||
this._lastCursorCol = this._lastContentCursorCol; | ||
this._lastCursorRow = this._lastContentCursorRow; | ||
} | ||
let content = ''; | ||
for (let i = 0; i < rowEnd; i++) { | ||
content += this._allRows[i]; | ||
if (i + 1 < rowEnd) { | ||
content += this._allRowSeparators[i]; | ||
} | ||
} | ||
return this._allRows.slice(0, rowEnd).join('\r\n'); | ||
// restore the cursor | ||
const realCursorRow = this._buffer1.baseY + this._buffer1.cursorY; | ||
const realCursorCol = this._buffer1.cursorX; | ||
const cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol); | ||
const moveRight = (offset) => { | ||
if (offset > 0) { | ||
content += `\u001b[${offset}C`; | ||
} | ||
else if (offset < 0) { | ||
content += `\u001b[${-offset}D`; | ||
} | ||
}; | ||
const moveDown = (offset) => { | ||
if (offset > 0) { | ||
content += `\u001b[${offset}B`; | ||
} | ||
else if (offset < 0) { | ||
content += `\u001b[${-offset}A`; | ||
} | ||
}; | ||
if (cursorMoved) { | ||
moveDown(realCursorRow - this._lastCursorRow); | ||
moveRight(realCursorCol - this._lastCursorCol); | ||
} | ||
return content; | ||
} | ||
@@ -185,5 +378,10 @@ } | ||
} | ||
serialize(rows) { | ||
// TODO: Add re-position cursor support | ||
// TODO: Add word wrap mode support | ||
_getString(buffer, scrollback) { | ||
const maxRows = buffer.length; | ||
const handler = new StringSerializeHandler(buffer, this._terminal); | ||
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + this._terminal.rows, 0, maxRows); | ||
const result = handler.serialize(maxRows - correctRows, maxRows); | ||
return result; | ||
} | ||
serialize(scrollback) { | ||
// TODO: Add combinedData support | ||
@@ -193,6 +391,11 @@ if (!this._terminal) { | ||
} | ||
const maxRows = this._terminal.buffer.active.length; | ||
const handler = new StringSerializeHandler(this._terminal.buffer.active); | ||
rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows); | ||
return handler.serialize(maxRows - rows, maxRows); | ||
if (this._terminal.buffer.active.type === 'normal') { | ||
return this._getString(this._terminal.buffer.active, scrollback); | ||
} | ||
const normalScreenContent = this._getString(this._terminal.buffer.normal, scrollback); | ||
// alt screen don't have scrollback | ||
const alternativeScreenContent = this._getString(this._terminal.buffer.alternate, undefined); | ||
return normalScreenContent | ||
+ '\u001b[?1049h\u001b[H' | ||
+ alternativeScreenContent; | ||
} | ||
@@ -199,0 +402,0 @@ dispose() { } |
@@ -28,3 +28,3 @@ "use strict"; | ||
var oldCell = cell1; | ||
this._beforeSerialize(endRow - startRow); | ||
this._beforeSerialize(endRow - startRow, startRow, endRow); | ||
for (var row = startRow; row < endRow; row++) { | ||
@@ -43,3 +43,3 @@ var line = this._buffer.getLine(row); | ||
} | ||
this._rowEnd(row); | ||
this._rowEnd(row, row === endRow - 1); | ||
} | ||
@@ -50,4 +50,4 @@ this._afterSerialize(); | ||
BaseSerializeHandler.prototype._nextCell = function (cell, oldCell, row, col) { }; | ||
BaseSerializeHandler.prototype._rowEnd = function (row) { }; | ||
BaseSerializeHandler.prototype._beforeSerialize = function (rows) { }; | ||
BaseSerializeHandler.prototype._rowEnd = function (row, isLastRow) { }; | ||
BaseSerializeHandler.prototype._beforeSerialize = function (rows, startRow, endRow) { }; | ||
BaseSerializeHandler.prototype._afterSerialize = function () { }; | ||
@@ -76,19 +76,91 @@ BaseSerializeHandler.prototype._serializeString = function () { return ''; }; | ||
__extends(StringSerializeHandler, _super); | ||
function StringSerializeHandler(buffer) { | ||
var _this = _super.call(this, buffer) || this; | ||
function StringSerializeHandler(_buffer1, _terminal) { | ||
var _this = _super.call(this, _buffer1) || this; | ||
_this._buffer1 = _buffer1; | ||
_this._terminal = _terminal; | ||
_this._rowIndex = 0; | ||
_this._allRows = new Array(); | ||
_this._allRowSeparators = new Array(); | ||
_this._currentRow = ''; | ||
_this._nullCellCount = 0; | ||
_this._cursorStyle = _this._buffer1.getNullCell(); | ||
_this._cursorStyleRow = 0; | ||
_this._cursorStyleCol = 0; | ||
_this._backgroundCell = _this._buffer1.getNullCell(); | ||
_this._firstRow = 0; | ||
_this._lastCursorRow = 0; | ||
_this._lastCursorCol = 0; | ||
_this._lastContentCursorRow = 0; | ||
_this._lastContentCursorCol = 0; | ||
_this._thisRowLastChar = _this._buffer1.getNullCell(); | ||
_this._thisRowLastSecondChar = _this._buffer1.getNullCell(); | ||
_this._nextRowFirstChar = _this._buffer1.getNullCell(); | ||
return _this; | ||
} | ||
StringSerializeHandler.prototype._beforeSerialize = function (rows) { | ||
StringSerializeHandler.prototype._beforeSerialize = function (rows, start, end) { | ||
this._allRows = new Array(rows); | ||
this._lastContentCursorRow = start; | ||
this._lastCursorRow = start; | ||
this._firstRow = start; | ||
}; | ||
StringSerializeHandler.prototype._rowEnd = function (row) { | ||
this._allRows[this._rowIndex++] = this._currentRow; | ||
StringSerializeHandler.prototype._rowEnd = function (row, isLastRow) { | ||
var _a; | ||
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) { | ||
this._currentRow += "\u001B[" + this._nullCellCount + "X"; | ||
} | ||
var rowSeparator = ''; | ||
if (!isLastRow) { | ||
if (row - this._firstRow >= this._terminal.rows) { | ||
(_a = this._buffer1.getLine(this._cursorStyleRow)) === null || _a === void 0 ? void 0 : _a.getCell(this._cursorStyleCol, this._backgroundCell); | ||
} | ||
var currentLine = this._buffer1.getLine(row); | ||
var nextLine = this._buffer1.getLine(row + 1); | ||
if (!nextLine.isWrapped) { | ||
rowSeparator = '\r\n'; | ||
this._lastCursorRow = row + 1; | ||
this._lastCursorCol = 0; | ||
} | ||
else { | ||
rowSeparator = ''; | ||
var thisRowLastChar = currentLine.getCell(currentLine.length - 1, this._thisRowLastChar); | ||
var thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2, this._thisRowLastSecondChar); | ||
var nextRowFirstChar = nextLine.getCell(0, this._nextRowFirstChar); | ||
var isNextRowFirstCharDoubleWidth = nextRowFirstChar.getWidth() > 1; | ||
var isValid = false; | ||
if (nextRowFirstChar.getChars() && | ||
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0) { | ||
if ((thisRowLastChar.getChars() || thisRowLastChar.getWidth() === 0) && | ||
equalBg(thisRowLastChar, nextRowFirstChar)) { | ||
isValid = true; | ||
} | ||
if (isNextRowFirstCharDoubleWidth && | ||
(thisRowLastSecondChar.getChars() || thisRowLastSecondChar.getWidth() === 0) && | ||
equalBg(thisRowLastChar, nextRowFirstChar) && | ||
equalBg(thisRowLastSecondChar, nextRowFirstChar)) { | ||
isValid = true; | ||
} | ||
} | ||
if (!isValid) { | ||
rowSeparator = '-'.repeat(this._nullCellCount + 1); | ||
rowSeparator += '\x1b[1D\x1b[1X'; | ||
if (this._nullCellCount > 0) { | ||
rowSeparator += '\x1b[A'; | ||
rowSeparator += "\u001B[" + (currentLine.length - this._nullCellCount) + "C"; | ||
rowSeparator += "\u001B[" + this._nullCellCount + "X"; | ||
rowSeparator += "\u001B[" + (currentLine.length - this._nullCellCount) + "D"; | ||
rowSeparator += '\x1b[B'; | ||
} | ||
this._lastContentCursorRow = row + 1; | ||
this._lastContentCursorCol = 0; | ||
this._lastCursorRow = row + 1; | ||
this._lastCursorCol = 0; | ||
} | ||
} | ||
} | ||
this._allRows[this._rowIndex] = this._currentRow; | ||
this._allRowSeparators[this._rowIndex++] = rowSeparator; | ||
this._currentRow = ''; | ||
this._nullCellCount = 0; | ||
}; | ||
StringSerializeHandler.prototype._nextCell = function (cell, oldCell, row, col) { | ||
StringSerializeHandler.prototype._diffStyle = function (cell, oldCell) { | ||
var sgrSeq = []; | ||
@@ -100,3 +172,5 @@ var fgChanged = !equalFg(cell, oldCell); | ||
if (cell.isAttributeDefault()) { | ||
this._currentRow += '\x1b[0m'; | ||
if (!oldCell.isAttributeDefault()) { | ||
sgrSeq.push(0); | ||
} | ||
} | ||
@@ -163,22 +237,87 @@ else { | ||
} | ||
if (sgrSeq.length) { | ||
return sgrSeq; | ||
}; | ||
StringSerializeHandler.prototype._nextCell = function (cell, oldCell, row, col) { | ||
var isPlaceHolderCell = cell.getWidth() === 0; | ||
if (isPlaceHolderCell) { | ||
return; | ||
} | ||
var isEmptyCell = cell.getChars() === ''; | ||
var sgrSeq = this._diffStyle(cell, this._cursorStyle); | ||
var styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0; | ||
if (styleChanged) { | ||
if (this._nullCellCount > 0) { | ||
if (!equalBg(this._cursorStyle, this._backgroundCell)) { | ||
this._currentRow += "\u001B[" + this._nullCellCount + "X"; | ||
} | ||
this._currentRow += "\u001B[" + this._nullCellCount + "C"; | ||
this._nullCellCount = 0; | ||
} | ||
this._lastContentCursorRow = this._lastCursorRow = row; | ||
this._lastContentCursorCol = this._lastCursorCol = col; | ||
this._currentRow += "\u001B[" + sgrSeq.join(';') + "m"; | ||
var line = this._buffer1.getLine(row); | ||
if (line !== undefined) { | ||
line.getCell(col, this._cursorStyle); | ||
this._cursorStyleRow = row; | ||
this._cursorStyleCol = col; | ||
} | ||
} | ||
if (cell.getChars() === '') { | ||
if (isEmptyCell) { | ||
this._nullCellCount += cell.getWidth(); | ||
} | ||
else if (this._nullCellCount > 0) { | ||
this._currentRow += "\u001B[" + this._nullCellCount + "C"; | ||
this._nullCellCount = 0; | ||
else { | ||
if (this._nullCellCount > 0) { | ||
if (equalBg(this._cursorStyle, this._backgroundCell)) { | ||
this._currentRow += "\u001B[" + this._nullCellCount + "C"; | ||
} | ||
else { | ||
this._currentRow += "\u001B[" + this._nullCellCount + "X"; | ||
this._currentRow += "\u001B[" + this._nullCellCount + "C"; | ||
} | ||
this._nullCellCount = 0; | ||
} | ||
this._currentRow += cell.getChars(); | ||
this._lastContentCursorRow = this._lastCursorRow = row; | ||
this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth(); | ||
} | ||
this._currentRow += cell.getChars(); | ||
}; | ||
StringSerializeHandler.prototype._serializeString = function () { | ||
var rowEnd = this._allRows.length; | ||
for (; rowEnd > 0; rowEnd--) { | ||
if (this._allRows[rowEnd - 1]) { | ||
break; | ||
if (this._buffer1.length - this._firstRow <= this._terminal.rows) { | ||
rowEnd = this._lastContentCursorRow + 1 - this._firstRow; | ||
this._lastCursorCol = this._lastContentCursorCol; | ||
this._lastCursorRow = this._lastContentCursorRow; | ||
} | ||
var content = ''; | ||
for (var i = 0; i < rowEnd; i++) { | ||
content += this._allRows[i]; | ||
if (i + 1 < rowEnd) { | ||
content += this._allRowSeparators[i]; | ||
} | ||
} | ||
return this._allRows.slice(0, rowEnd).join('\r\n'); | ||
var realCursorRow = this._buffer1.baseY + this._buffer1.cursorY; | ||
var realCursorCol = this._buffer1.cursorX; | ||
var cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol); | ||
var moveRight = function (offset) { | ||
if (offset > 0) { | ||
content += "\u001B[" + offset + "C"; | ||
} | ||
else if (offset < 0) { | ||
content += "\u001B[" + -offset + "D"; | ||
} | ||
}; | ||
var moveDown = function (offset) { | ||
if (offset > 0) { | ||
content += "\u001B[" + offset + "B"; | ||
} | ||
else if (offset < 0) { | ||
content += "\u001B[" + -offset + "A"; | ||
} | ||
}; | ||
if (cursorMoved) { | ||
moveDown(realCursorRow - this._lastCursorRow); | ||
moveRight(realCursorCol - this._lastCursorCol); | ||
} | ||
return content; | ||
}; | ||
@@ -193,10 +332,21 @@ return StringSerializeHandler; | ||
}; | ||
SerializeAddon.prototype.serialize = function (rows) { | ||
SerializeAddon.prototype._getString = function (buffer, scrollback) { | ||
var maxRows = buffer.length; | ||
var handler = new StringSerializeHandler(buffer, this._terminal); | ||
var correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + this._terminal.rows, 0, maxRows); | ||
var result = handler.serialize(maxRows - correctRows, maxRows); | ||
return result; | ||
}; | ||
SerializeAddon.prototype.serialize = function (scrollback) { | ||
if (!this._terminal) { | ||
throw new Error('Cannot use addon until it has been loaded'); | ||
} | ||
var maxRows = this._terminal.buffer.active.length; | ||
var handler = new StringSerializeHandler(this._terminal.buffer.active); | ||
rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows); | ||
return handler.serialize(maxRows - rows, maxRows); | ||
if (this._terminal.buffer.active.type === 'normal') { | ||
return this._getString(this._terminal.buffer.active, scrollback); | ||
} | ||
var normalScreenContent = this._getString(this._terminal.buffer.normal, scrollback); | ||
var alternativeScreenContent = this._getString(this._terminal.buffer.alternate, undefined); | ||
return normalScreenContent | ||
+ '\u001b[?1049h\u001b[H' | ||
+ alternativeScreenContent; | ||
}; | ||
@@ -203,0 +353,0 @@ SerializeAddon.prototype.dispose = function () { }; |
{ | ||
"name": "xterm-addon-serialize", | ||
"version": "0.5.0-beta.5", | ||
"version": "0.5.0-beta.6", | ||
"author": { | ||
@@ -5,0 +5,0 @@ "name": "The xterm.js authors", |
@@ -24,3 +24,3 @@ /** | ||
this._beforeSerialize(endRow - startRow); | ||
this._beforeSerialize(endRow - startRow, startRow, endRow); | ||
@@ -40,3 +40,3 @@ for (let row = startRow; row < endRow; row++) { | ||
} | ||
this._rowEnd(row); | ||
this._rowEnd(row, row === endRow - 1); | ||
} | ||
@@ -50,4 +50,4 @@ | ||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { } | ||
protected _rowEnd(row: number): void { } | ||
protected _beforeSerialize(rows: number): void { } | ||
protected _rowEnd(row: number, isLastRow: boolean): void { } | ||
protected _beforeSerialize(rows: number, startRow: number, endRow: number): void { } | ||
protected _afterSerialize(): void { } | ||
@@ -77,18 +77,143 @@ protected _serializeString(): string { return ''; } | ||
class StringSerializeHandler extends BaseSerializeHandler { | ||
private _rowIndex: number = 0; | ||
private _allRows: string[] = new Array<string>(); | ||
private _allRowSeparators: string[] = new Array<string>(); | ||
private _currentRow: string = ''; | ||
private _nullCellCount: number = 0; | ||
constructor(buffer: IBuffer) { | ||
super(buffer); | ||
// we can see a full colored cell and a null cell that only have background the same style | ||
// but the information isn't preserved by null cell itself | ||
// so wee need to record it when required. | ||
private _cursorStyle: IBufferCell = this._buffer1.getNullCell(); | ||
// where exact the cursor styles comes from | ||
// because we can't copy the cell directly | ||
// so we remember where the content comes from instead | ||
private _cursorStyleRow: number = 0; | ||
private _cursorStyleCol: number = 0; | ||
// this is a null cell for reference for checking whether background is empty or not | ||
private _backgroundCell: IBufferCell = this._buffer1.getNullCell(); | ||
private _firstRow: number = 0; | ||
private _lastCursorRow: number = 0; | ||
private _lastCursorCol: number = 0; | ||
private _lastContentCursorRow: number = 0; | ||
private _lastContentCursorCol: number = 0; | ||
constructor(private _buffer1: IBuffer, private _terminal: Terminal) { | ||
super(_buffer1); | ||
} | ||
protected _beforeSerialize(rows: number): void { | ||
protected _beforeSerialize(rows: number, start: number, end: number): void { | ||
this._allRows = new Array<string>(rows); | ||
this._lastContentCursorRow = start; | ||
this._lastCursorRow = start; | ||
this._firstRow = start; | ||
} | ||
protected _rowEnd(row: number): void { | ||
this._allRows[this._rowIndex++] = this._currentRow; | ||
private _thisRowLastChar: IBufferCell = this._buffer1.getNullCell(); | ||
private _thisRowLastSecondChar: IBufferCell = this._buffer1.getNullCell(); | ||
private _nextRowFirstChar: IBufferCell = this._buffer1.getNullCell(); | ||
protected _rowEnd(row: number, isLastRow: boolean): void { | ||
// if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing | ||
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) { | ||
// use clear right to set background. | ||
this._currentRow += `\x1b[${this._nullCellCount}X`; | ||
} | ||
let rowSeparator = ''; | ||
// handle row separator | ||
if (!isLastRow) { | ||
// Enable BCE | ||
if (row - this._firstRow >= this._terminal.rows) { | ||
this._buffer1.getLine(this._cursorStyleRow)?.getCell(this._cursorStyleCol, this._backgroundCell); | ||
} | ||
// Fetch current line | ||
const currentLine = this._buffer1.getLine(row)!; | ||
// Fetch next line | ||
const nextLine = this._buffer1.getLine(row + 1)!; | ||
if (!nextLine.isWrapped) { | ||
// just insert the line break | ||
rowSeparator = '\r\n'; | ||
// we sended the enter | ||
this._lastCursorRow = row + 1; | ||
this._lastCursorCol = 0; | ||
} else { | ||
rowSeparator = ''; | ||
const thisRowLastChar = currentLine.getCell(currentLine.length - 1, this._thisRowLastChar)!; | ||
const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2, this._thisRowLastSecondChar)!; | ||
const nextRowFirstChar = nextLine.getCell(0, this._nextRowFirstChar)!; | ||
const isNextRowFirstCharDoubleWidth = nextRowFirstChar.getWidth() > 1; | ||
// validate whether this line wrap is ever possible | ||
// which mean whether cursor can placed at a overflow position (x === row) naturally | ||
let isValid = false; | ||
if ( | ||
// you must output character to cause overflow, control sequence can't do this | ||
nextRowFirstChar.getChars() && | ||
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0 | ||
) { | ||
if ( | ||
// the last character can't be null, | ||
// you can't use control sequence to move cursor to (x === row) | ||
(thisRowLastChar.getChars() || thisRowLastChar.getWidth() === 0) && | ||
// change background of the first wrapped cell also affects BCE | ||
// so we mark it as invalid to simply the process to determine line separator | ||
equalBg(thisRowLastChar, nextRowFirstChar) | ||
) { | ||
isValid = true; | ||
} | ||
if ( | ||
// the second to last character can't be null if the next line starts with CJK, | ||
// you can't use control sequence to move cursor to (x === row) | ||
isNextRowFirstCharDoubleWidth && | ||
(thisRowLastSecondChar.getChars() || thisRowLastSecondChar.getWidth() === 0) && | ||
// change background of the first wrapped cell also affects BCE | ||
// so we mark it as invalid to simply the process to determine line separator | ||
equalBg(thisRowLastChar, nextRowFirstChar) && | ||
equalBg(thisRowLastSecondChar, nextRowFirstChar) | ||
) { | ||
isValid = true; | ||
} | ||
} | ||
if (!isValid) { | ||
// force the wrap with magic | ||
// insert enough character to force the wrap | ||
rowSeparator = '-'.repeat(this._nullCellCount + 1); | ||
// move back and erase next line head | ||
rowSeparator += '\x1b[1D\x1b[1X'; | ||
if (this._nullCellCount > 0) { | ||
// do these because we filled the last several null slot, which we shouldn't | ||
rowSeparator += '\x1b[A'; | ||
rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}C`; | ||
rowSeparator += `\x1b[${this._nullCellCount}X`; | ||
rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}D`; | ||
rowSeparator += '\x1b[B'; | ||
} | ||
// This is content and need the be serialized even it is invisible. | ||
// without this, wrap will be missing from outputs. | ||
this._lastContentCursorRow = row + 1; | ||
this._lastContentCursorCol = 0; | ||
// force commit the cursor position | ||
this._lastCursorRow = row + 1; | ||
this._lastCursorCol = 0; | ||
} | ||
} | ||
} | ||
this._allRows[this._rowIndex] = this._currentRow; | ||
this._allRowSeparators[this._rowIndex++] = rowSeparator; | ||
this._currentRow = ''; | ||
@@ -98,3 +223,3 @@ this._nullCellCount = 0; | ||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { | ||
private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] { | ||
const sgrSeq: number[] = []; | ||
@@ -107,3 +232,5 @@ const fgChanged = !equalFg(cell, oldCell); | ||
if (cell.isAttributeDefault()) { | ||
this._currentRow += '\x1b[0m'; | ||
if (!oldCell.isAttributeDefault()) { | ||
sgrSeq.push(0); | ||
} | ||
} else { | ||
@@ -140,16 +267,75 @@ if (fgChanged) { | ||
if (sgrSeq.length) { | ||
return sgrSeq; | ||
} | ||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { | ||
// a width 0 cell don't need to be count because it is just a placeholder after a CJK character; | ||
const isPlaceHolderCell = cell.getWidth() === 0; | ||
if (isPlaceHolderCell) { | ||
return; | ||
} | ||
// this cell don't have content | ||
const isEmptyCell = cell.getChars() === ''; | ||
const sgrSeq = this._diffStyle(cell, this._cursorStyle); | ||
// the empty cell style is only assumed to be changed when background changed, because foreground is always 0. | ||
const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0; | ||
/** | ||
* handles style change | ||
*/ | ||
if (styleChanged) { | ||
// before update the style, we need to fill empty cell back | ||
if (this._nullCellCount > 0) { | ||
// use clear right to set background. | ||
if (!equalBg(this._cursorStyle, this._backgroundCell)) { | ||
this._currentRow += `\x1b[${this._nullCellCount}X`; | ||
} | ||
// use move right to move cursor. | ||
this._currentRow += `\x1b[${this._nullCellCount}C`; | ||
this._nullCellCount = 0; | ||
} | ||
this._lastContentCursorRow = this._lastCursorRow = row; | ||
this._lastContentCursorCol = this._lastCursorCol = col; | ||
this._currentRow += `\x1b[${sgrSeq.join(';')}m`; | ||
// update the last cursor style | ||
const line = this._buffer1.getLine(row); | ||
if (line !== undefined) { | ||
line.getCell(col, this._cursorStyle); | ||
this._cursorStyleRow = row; | ||
this._cursorStyleCol = col; | ||
} | ||
} | ||
// Count number of null cells encountered after the last non-null cell and move the cursor | ||
// if a non-null cell is found (eg. \t or cursor move) | ||
if (cell.getChars() === '') { | ||
/** | ||
* handles actual content | ||
*/ | ||
if (isEmptyCell) { | ||
this._nullCellCount += cell.getWidth(); | ||
} else if (this._nullCellCount > 0) { | ||
this._currentRow += `\x1b[${this._nullCellCount}C`; | ||
this._nullCellCount = 0; | ||
} else { | ||
if (this._nullCellCount > 0) { | ||
// we can just assume we have same style with previous one here | ||
// because style change is handled by previous stage | ||
// use move right when background is empty, use clear right when there is background. | ||
if (equalBg(this._cursorStyle, this._backgroundCell)) { | ||
this._currentRow += `\x1b[${this._nullCellCount}C`; | ||
} else { | ||
this._currentRow += `\x1b[${this._nullCellCount}X`; | ||
this._currentRow += `\x1b[${this._nullCellCount}C`; | ||
} | ||
this._nullCellCount = 0; | ||
} | ||
this._currentRow += cell.getChars(); | ||
// update cursor | ||
this._lastContentCursorRow = this._lastCursorRow = row; | ||
this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth(); | ||
} | ||
this._currentRow += cell.getChars(); | ||
} | ||
@@ -159,8 +345,48 @@ | ||
let rowEnd = this._allRows.length; | ||
for (; rowEnd > 0; rowEnd--) { | ||
if (this._allRows[rowEnd - 1]) { | ||
break; | ||
// the fixup is only required for data without scrollback | ||
// because it will always be placed at last line otherwise | ||
if (this._buffer1.length - this._firstRow <= this._terminal.rows) { | ||
rowEnd = this._lastContentCursorRow + 1 - this._firstRow; | ||
this._lastCursorCol = this._lastContentCursorCol; | ||
this._lastCursorRow = this._lastContentCursorRow; | ||
} | ||
let content = ''; | ||
for (let i = 0; i < rowEnd; i++) { | ||
content += this._allRows[i]; | ||
if (i + 1 < rowEnd) { | ||
content += this._allRowSeparators[i]; | ||
} | ||
} | ||
return this._allRows.slice(0, rowEnd).join('\r\n'); | ||
// restore the cursor | ||
const realCursorRow = this._buffer1.baseY + this._buffer1.cursorY; | ||
const realCursorCol = this._buffer1.cursorX; | ||
const cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol); | ||
const moveRight = (offset: number): void => { | ||
if (offset > 0) { | ||
content += `\u001b[${offset}C`; | ||
} else if (offset < 0) { | ||
content += `\u001b[${-offset}D`; | ||
} | ||
}; | ||
const moveDown = (offset: number): void => { | ||
if (offset > 0) { | ||
content += `\u001b[${offset}B`; | ||
} else if (offset < 0) { | ||
content += `\u001b[${-offset}A`; | ||
} | ||
}; | ||
if (cursorMoved) { | ||
moveDown(realCursorRow - this._lastCursorRow); | ||
moveRight(realCursorCol - this._lastCursorCol); | ||
} | ||
return content; | ||
} | ||
@@ -178,5 +404,13 @@ } | ||
public serialize(rows?: number): string { | ||
// TODO: Add re-position cursor support | ||
// TODO: Add word wrap mode support | ||
private _getString(buffer: IBuffer, scrollback?: number): string { | ||
const maxRows = buffer.length; | ||
const handler = new StringSerializeHandler(buffer, this._terminal!); | ||
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + this!._terminal!.rows, 0, maxRows); | ||
const result = handler.serialize(maxRows - correctRows, maxRows); | ||
return result; | ||
} | ||
public serialize(scrollback?: number): string { | ||
// TODO: Add combinedData support | ||
@@ -187,8 +421,13 @@ if (!this._terminal) { | ||
const maxRows = this._terminal.buffer.active.length; | ||
const handler = new StringSerializeHandler(this._terminal.buffer.active); | ||
if (this._terminal.buffer.active.type === 'normal') { | ||
return this._getString(this._terminal.buffer.active, scrollback); | ||
} | ||
rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows); | ||
const normalScreenContent = this._getString(this._terminal.buffer.normal, scrollback); | ||
// alt screen don't have scrollback | ||
const alternativeScreenContent = this._getString(this._terminal.buffer.alternate, undefined); | ||
return handler.serialize(maxRows - rows, maxRows); | ||
return normalScreenContent | ||
+ '\u001b[?1049h\u001b[H' | ||
+ alternativeScreenContent; | ||
} | ||
@@ -195,0 +434,0 @@ |
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
124424
70.75%1308
75.34%