|
@@ -157,6 +157,23 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
|
|
abstract class BaseSerializeHandler {
|
|
abstract class BaseSerializeHandler {
|
|
|
constructor(protected readonly _buffer: IBuffer) {}
|
|
constructor(protected readonly _buffer: IBuffer) {}
|
|
|
|
|
|
|
|
|
|
+ private _isRealContent(codepoint: number): boolean {
|
|
|
|
|
+ if (codepoint === 0) return false
|
|
|
|
|
+ if (codepoint >= 0xf000) return false
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private _findLastContentColumn(line: IBufferLine): number {
|
|
|
|
|
+ let lastContent = -1
|
|
|
|
|
+ for (let col = 0; col < line.length; col++) {
|
|
|
|
|
+ const cell = line.getCell(col)
|
|
|
|
|
+ if (cell && this._isRealContent(cell.getCode())) {
|
|
|
|
|
+ lastContent = col
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return lastContent + 1
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string {
|
|
public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string {
|
|
|
let oldCell = this._buffer.getNullCell()
|
|
let oldCell = this._buffer.getNullCell()
|
|
|
|
|
|
|
@@ -171,7 +188,8 @@ abstract class BaseSerializeHandler {
|
|
|
const line = this._buffer.getLine(row)
|
|
const line = this._buffer.getLine(row)
|
|
|
if (line) {
|
|
if (line) {
|
|
|
const startLineColumn = row === range.start.y ? startColumn : 0
|
|
const startLineColumn = row === range.start.y ? startColumn : 0
|
|
|
- const endLineColumn = row === range.end.y ? endColumn : line.length
|
|
|
|
|
|
|
+ const maxColumn = row === range.end.y ? endColumn : this._findLastContentColumn(line)
|
|
|
|
|
+ const endLineColumn = Math.min(maxColumn, line.length)
|
|
|
for (let col = startLineColumn; col < endLineColumn; col++) {
|
|
for (let col = startLineColumn; col < endLineColumn; col++) {
|
|
|
const c = line.getCell(col)
|
|
const c = line.getCell(col)
|
|
|
if (!c) {
|
|
if (!c) {
|
|
@@ -209,17 +227,11 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
private _currentRow: string = ""
|
|
private _currentRow: string = ""
|
|
|
private _nullCellCount: number = 0
|
|
private _nullCellCount: number = 0
|
|
|
private _cursorStyle: IBufferCell
|
|
private _cursorStyle: IBufferCell
|
|
|
- private _cursorStyleRow: number = 0
|
|
|
|
|
- private _cursorStyleCol: number = 0
|
|
|
|
|
- private _backgroundCell: IBufferCell
|
|
|
|
|
private _firstRow: number = 0
|
|
private _firstRow: number = 0
|
|
|
private _lastCursorRow: number = 0
|
|
private _lastCursorRow: number = 0
|
|
|
private _lastCursorCol: number = 0
|
|
private _lastCursorCol: number = 0
|
|
|
private _lastContentCursorRow: number = 0
|
|
private _lastContentCursorRow: number = 0
|
|
|
private _lastContentCursorCol: number = 0
|
|
private _lastContentCursorCol: number = 0
|
|
|
- private _thisRowLastChar: IBufferCell
|
|
|
|
|
- private _thisRowLastSecondChar: IBufferCell
|
|
|
|
|
- private _nextRowFirstChar: IBufferCell
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
constructor(
|
|
|
buffer: IBuffer,
|
|
buffer: IBuffer,
|
|
@@ -227,10 +239,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
) {
|
|
) {
|
|
|
super(buffer)
|
|
super(buffer)
|
|
|
this._cursorStyle = this._buffer.getNullCell()
|
|
this._cursorStyle = this._buffer.getNullCell()
|
|
|
- this._backgroundCell = this._buffer.getNullCell()
|
|
|
|
|
- this._thisRowLastChar = this._buffer.getNullCell()
|
|
|
|
|
- this._thisRowLastSecondChar = this._buffer.getNullCell()
|
|
|
|
|
- this._nextRowFirstChar = this._buffer.getNullCell()
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
protected _beforeSerialize(rows: number, start: number, _end: number): void {
|
|
protected _beforeSerialize(rows: number, start: number, _end: number): void {
|
|
@@ -241,82 +249,15 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
protected _rowEnd(row: number, isLastRow: boolean): void {
|
|
protected _rowEnd(row: number, isLastRow: boolean): void {
|
|
|
- // if there is colorful empty cell at line end, we must pad it back
|
|
|
|
|
- if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
|
|
|
|
|
- this._currentRow += `\u001b[${this._nullCellCount}X`
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
let rowSeparator = ""
|
|
let rowSeparator = ""
|
|
|
|
|
|
|
|
if (!isLastRow) {
|
|
if (!isLastRow) {
|
|
|
- // Enable BCE
|
|
|
|
|
- if (row - this._firstRow >= this._terminal.rows) {
|
|
|
|
|
- const line = this._buffer.getLine(this._cursorStyleRow)
|
|
|
|
|
- const cell = line?.getCell(this._cursorStyleCol)
|
|
|
|
|
- if (cell) {
|
|
|
|
|
- this._backgroundCell = cell
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const nextLine = this._buffer.getLine(row + 1)
|
|
|
|
|
|
|
|
- const currentLine = this._buffer.getLine(row)!
|
|
|
|
|
- const nextLine = this._buffer.getLine(row + 1)!
|
|
|
|
|
-
|
|
|
|
|
- if (!nextLine.isWrapped) {
|
|
|
|
|
|
|
+ if (!nextLine?.isWrapped) {
|
|
|
rowSeparator = "\r\n"
|
|
rowSeparator = "\r\n"
|
|
|
this._lastCursorRow = row + 1
|
|
this._lastCursorRow = row + 1
|
|
|
this._lastCursorCol = 0
|
|
this._lastCursorCol = 0
|
|
|
- } else {
|
|
|
|
|
- rowSeparator = ""
|
|
|
|
|
- const thisRowLastChar = currentLine.getCell(currentLine.length - 1)
|
|
|
|
|
- const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2)
|
|
|
|
|
- const nextRowFirstChar = nextLine.getCell(0)
|
|
|
|
|
-
|
|
|
|
|
- if (thisRowLastChar) this._thisRowLastChar = thisRowLastChar
|
|
|
|
|
- if (thisRowLastSecondChar) this._thisRowLastSecondChar = thisRowLastSecondChar
|
|
|
|
|
- if (nextRowFirstChar) this._nextRowFirstChar = nextRowFirstChar
|
|
|
|
|
-
|
|
|
|
|
- const isNextRowFirstCharDoubleWidth = this._nextRowFirstChar.getWidth() > 1
|
|
|
|
|
-
|
|
|
|
|
- let isValid = false
|
|
|
|
|
-
|
|
|
|
|
- if (
|
|
|
|
|
- this._nextRowFirstChar.getChars() &&
|
|
|
|
|
- (isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0)
|
|
|
|
|
- ) {
|
|
|
|
|
- if (
|
|
|
|
|
- (this._thisRowLastChar.getChars() || this._thisRowLastChar.getWidth() === 0) &&
|
|
|
|
|
- equalBg(this._thisRowLastChar, this._nextRowFirstChar)
|
|
|
|
|
- ) {
|
|
|
|
|
- isValid = true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (
|
|
|
|
|
- isNextRowFirstCharDoubleWidth &&
|
|
|
|
|
- (this._thisRowLastSecondChar.getChars() || this._thisRowLastSecondChar.getWidth() === 0) &&
|
|
|
|
|
- equalBg(this._thisRowLastChar, this._nextRowFirstChar) &&
|
|
|
|
|
- equalBg(this._thisRowLastSecondChar, this._nextRowFirstChar)
|
|
|
|
|
- ) {
|
|
|
|
|
- isValid = true
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!isValid) {
|
|
|
|
|
- rowSeparator = "-".repeat(this._nullCellCount + 1)
|
|
|
|
|
- rowSeparator += "\u001b[1D\u001b[1X"
|
|
|
|
|
-
|
|
|
|
|
- if (this._nullCellCount > 0) {
|
|
|
|
|
- rowSeparator += "\u001b[A"
|
|
|
|
|
- rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}C`
|
|
|
|
|
- rowSeparator += `\u001b[${this._nullCellCount}X`
|
|
|
|
|
- rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}D`
|
|
|
|
|
- rowSeparator += "\u001b[B"
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this._lastContentCursorRow = row + 1
|
|
|
|
|
- this._lastContentCursorCol = 0
|
|
|
|
|
- this._lastCursorRow = row + 1
|
|
|
|
|
- this._lastCursorCol = 0
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -338,11 +279,36 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
sgrSeq.push(0)
|
|
sgrSeq.push(0)
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ if (flagsChanged) {
|
|
|
|
|
+ if (!!cell.isInverse() !== !!oldCell.isInverse()) {
|
|
|
|
|
+ sgrSeq.push(cell.isInverse() ? 7 : 27)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!!cell.isBold() !== !!oldCell.isBold()) {
|
|
|
|
|
+ sgrSeq.push(cell.isBold() ? 1 : 22)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!!cell.isUnderline() !== !!oldCell.isUnderline()) {
|
|
|
|
|
+ sgrSeq.push(cell.isUnderline() ? 4 : 24)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!!cell.isBlink() !== !!oldCell.isBlink()) {
|
|
|
|
|
+ sgrSeq.push(cell.isBlink() ? 5 : 25)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!!cell.isInvisible() !== !!oldCell.isInvisible()) {
|
|
|
|
|
+ sgrSeq.push(cell.isInvisible() ? 8 : 28)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!!cell.isItalic() !== !!oldCell.isItalic()) {
|
|
|
|
|
+ sgrSeq.push(cell.isItalic() ? 3 : 23)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!!cell.isDim() !== !!oldCell.isDim()) {
|
|
|
|
|
+ sgrSeq.push(cell.isDim() ? 2 : 22)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!!cell.isStrikethrough() !== !!oldCell.isStrikethrough()) {
|
|
|
|
|
+ sgrSeq.push(cell.isStrikethrough() ? 9 : 29)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
if (fgChanged) {
|
|
if (fgChanged) {
|
|
|
const color = cell.getFgColor()
|
|
const color = cell.getFgColor()
|
|
|
const mode = cell.getFgColorMode()
|
|
const mode = cell.getFgColorMode()
|
|
|
- if (mode === 2) {
|
|
|
|
|
- // RGB
|
|
|
|
|
|
|
+ if (mode === 2 || mode === 3 || mode === -1) {
|
|
|
sgrSeq.push(38, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff)
|
|
sgrSeq.push(38, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff)
|
|
|
} else if (mode === 1) {
|
|
} else if (mode === 1) {
|
|
|
// Palette
|
|
// Palette
|
|
@@ -358,8 +324,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
if (bgChanged) {
|
|
if (bgChanged) {
|
|
|
const color = cell.getBgColor()
|
|
const color = cell.getBgColor()
|
|
|
const mode = cell.getBgColorMode()
|
|
const mode = cell.getBgColorMode()
|
|
|
- if (mode === 2) {
|
|
|
|
|
- // RGB
|
|
|
|
|
|
|
+ if (mode === 2 || mode === 3 || mode === -1) {
|
|
|
sgrSeq.push(48, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff)
|
|
sgrSeq.push(48, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff)
|
|
|
} else if (mode === 1) {
|
|
} else if (mode === 1) {
|
|
|
// Palette
|
|
// Palette
|
|
@@ -372,32 +337,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
sgrSeq.push(49)
|
|
sgrSeq.push(49)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- if (flagsChanged) {
|
|
|
|
|
- if (!!cell.isInverse() !== !!oldCell.isInverse()) {
|
|
|
|
|
- sgrSeq.push(cell.isInverse() ? 7 : 27)
|
|
|
|
|
- }
|
|
|
|
|
- if (!!cell.isBold() !== !!oldCell.isBold()) {
|
|
|
|
|
- sgrSeq.push(cell.isBold() ? 1 : 22)
|
|
|
|
|
- }
|
|
|
|
|
- if (!!cell.isUnderline() !== !!oldCell.isUnderline()) {
|
|
|
|
|
- sgrSeq.push(cell.isUnderline() ? 4 : 24)
|
|
|
|
|
- }
|
|
|
|
|
- if (!!cell.isBlink() !== !!oldCell.isBlink()) {
|
|
|
|
|
- sgrSeq.push(cell.isBlink() ? 5 : 25)
|
|
|
|
|
- }
|
|
|
|
|
- if (!!cell.isInvisible() !== !!oldCell.isInvisible()) {
|
|
|
|
|
- sgrSeq.push(cell.isInvisible() ? 8 : 28)
|
|
|
|
|
- }
|
|
|
|
|
- if (!!cell.isItalic() !== !!oldCell.isItalic()) {
|
|
|
|
|
- sgrSeq.push(cell.isItalic() ? 3 : 23)
|
|
|
|
|
- }
|
|
|
|
|
- if (!!cell.isDim() !== !!oldCell.isDim()) {
|
|
|
|
|
- sgrSeq.push(cell.isDim() ? 2 : 22)
|
|
|
|
|
- }
|
|
|
|
|
- if (!!cell.isStrikethrough() !== !!oldCell.isStrikethrough()) {
|
|
|
|
|
- sgrSeq.push(cell.isStrikethrough() ? 9 : 29)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -405,9 +344,31 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private _isAttributeDefault(cell: IBufferCell): boolean {
|
|
private _isAttributeDefault(cell: IBufferCell): boolean {
|
|
|
|
|
+ const mode = cell.getFgColorMode()
|
|
|
|
|
+ const bgMode = cell.getBgColorMode()
|
|
|
|
|
+
|
|
|
|
|
+ if (mode === 0 && bgMode === 0) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ !cell.isBold() &&
|
|
|
|
|
+ !cell.isItalic() &&
|
|
|
|
|
+ !cell.isUnderline() &&
|
|
|
|
|
+ !cell.isBlink() &&
|
|
|
|
|
+ !cell.isInverse() &&
|
|
|
|
|
+ !cell.isInvisible() &&
|
|
|
|
|
+ !cell.isDim() &&
|
|
|
|
|
+ !cell.isStrikethrough()
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const fgColor = cell.getFgColor()
|
|
|
|
|
+ const bgColor = cell.getBgColor()
|
|
|
|
|
+ const nullCell = this._buffer.getNullCell()
|
|
|
|
|
+ const nullFg = nullCell.getFgColor()
|
|
|
|
|
+ const nullBg = nullCell.getBgColor()
|
|
|
|
|
+
|
|
|
return (
|
|
return (
|
|
|
- cell.getFgColorMode() === 0 &&
|
|
|
|
|
- cell.getBgColorMode() === 0 &&
|
|
|
|
|
|
|
+ fgColor === nullFg &&
|
|
|
|
|
+ bgColor === nullBg &&
|
|
|
!cell.isBold() &&
|
|
!cell.isBold() &&
|
|
|
!cell.isItalic() &&
|
|
!cell.isItalic() &&
|
|
|
!cell.isUnderline() &&
|
|
!cell.isUnderline() &&
|
|
@@ -426,7 +387,9 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const isEmptyCell = cell.getChars() === ""
|
|
|
|
|
|
|
+ const codepoint = cell.getCode()
|
|
|
|
|
+ const isGarbage = codepoint >= 0xf000
|
|
|
|
|
+ const isEmptyCell = codepoint === 0 || cell.getChars() === "" || isGarbage
|
|
|
|
|
|
|
|
const sgrSeq = this._diffStyle(cell, this._cursorStyle)
|
|
const sgrSeq = this._diffStyle(cell, this._cursorStyle)
|
|
|
|
|
|
|
@@ -434,9 +397,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
|
|
|
|
|
if (styleChanged) {
|
|
if (styleChanged) {
|
|
|
if (this._nullCellCount > 0) {
|
|
if (this._nullCellCount > 0) {
|
|
|
- if (!equalBg(this._cursorStyle, this._backgroundCell)) {
|
|
|
|
|
- this._currentRow += `\u001b[${this._nullCellCount}X`
|
|
|
|
|
- }
|
|
|
|
|
this._currentRow += `\u001b[${this._nullCellCount}C`
|
|
this._currentRow += `\u001b[${this._nullCellCount}C`
|
|
|
this._nullCellCount = 0
|
|
this._nullCellCount = 0
|
|
|
}
|
|
}
|
|
@@ -450,8 +410,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
const cellFromLine = line?.getCell(col)
|
|
const cellFromLine = line?.getCell(col)
|
|
|
if (cellFromLine) {
|
|
if (cellFromLine) {
|
|
|
this._cursorStyle = cellFromLine
|
|
this._cursorStyle = cellFromLine
|
|
|
- this._cursorStyleRow = row
|
|
|
|
|
- this._cursorStyleCol = col
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -459,12 +417,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
this._nullCellCount += cell.getWidth()
|
|
this._nullCellCount += cell.getWidth()
|
|
|
} else {
|
|
} else {
|
|
|
if (this._nullCellCount > 0) {
|
|
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._currentRow += `\u001b[${this._nullCellCount}C`
|
|
|
this._nullCellCount = 0
|
|
this._nullCellCount = 0
|
|
|
}
|
|
}
|
|
|
|
|
|