Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions addons/addon-serialize/src/SerializeAddon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,49 @@ describe('SerializeAddon', () => {
assert.equal(terminal2.modes.showCursor, false);
});
});

describe('lines longer than cols', () => {
// A column shrink on the alt buffer leaves its lines at the old length, see
// https://github.com/xtermjs/xterm.js/issues/6228
let longLineTerminal: Terminal;
let longLineAddon: SerializeAddon;

beforeEach(async () => {
longLineTerminal = new Terminal({ cols: 20, rows: 4, allowProposedApi: true });
longLineAddon = new SerializeAddon();
longLineTerminal.loadAddon(longLineAddon);
await writeP(longLineTerminal, '\x1b[?1049h\x1b[1;1Hwide row zero\x1b[2;1Hwide row one\x1b[3;1Hwide row two\x1b[4;1Hwide row three');
longLineTerminal.resize(10, 4);
});

async function replay(): Promise<Terminal> {
const replayTerminal = new Terminal({ cols: 10, rows: 4, allowProposedApi: true });
await writeP(replayTerminal, longLineAddon.serialize());
return replayTerminal;
}

function getRows(target: Terminal): string[] {
const rows: string[] = [];
for (let y = 0; y < target.rows; y++) {
rows.push(target.buffer.active.getLine(target.buffer.active.viewportY + y)!.translateToString(false, 0, target.cols));
}
return rows;
}

it('should not serialize cells past cols', async () => {
await writeP(longLineTerminal, '\x1b[H' + 'row0 text ' + 'row1\x1b[K\r\n' + 'row2 more ' + 'row3\x1b[K');
const replayTerminal = await replay();
assert.deepEqual(getRows(replayTerminal), ['row0 text ', 'row1 ', 'row2 more ', 'row3 ']);
});

it('should erase the forced wrap filler within cols', async () => {
// Row 0 ends in empty cells and row 1 continues it by autowrap
await writeP(longLineTerminal, '\x1b[H' + 'abcdefghij' + 'k\x1b[K' + '\x1b[1;6H\x1b[K');
const replayTerminal = await replay();
assert.deepEqual(getRows(replayTerminal), ['abcde ', 'k ', 'wide row t', 'wide row t']);
assert.equal(replayTerminal.buffer.active.getLine(1)!.isWrapped, true);
});
});
});

describe('html', () => {
Expand All @@ -238,6 +281,17 @@ describe('SerializeAddon', () => {
assert.equal(output, '');
});

it('line longer than cols', async () => {
// A column shrink on the alt buffer leaves its lines at the old length, see
// https://github.com/xtermjs/xterm.js/issues/6228
terminal.resize(20, 2);
await writeP(terminal, '\x1b[?1049hwide row zero');
terminal.resize(10, 2);

const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<div><span>wide row z<\/span><\/div>/g) ?? []).length, 1, output);
});

it('basic terminal with selection', async () => {
await writeP(terminal, ' terminal ');
terminal.select(1, 0, 8);
Expand Down
31 changes: 14 additions & 17 deletions addons/addon-serialize/src/SerializeAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function escapeHTMLChar(c: string): string {
// TODO: Refine this template class later
abstract class BaseSerializeHandler {
constructor(
protected readonly _buffer: IBuffer
protected readonly _buffer: IBuffer,
protected readonly _terminal: Terminal
) {
}

Expand All @@ -48,7 +49,8 @@ abstract class BaseSerializeHandler {
const line = this._buffer.getLine(row);
if (line) {
const startLineColumn = row === range.start.y ? startColumn : 0;
const endLineColumn = row === range.end.y ? endColumn: line.length;
// A line can be longer than cols, so stop at the edge of the grid
const endLineColumn = row === range.end.y ? endColumn : Math.min(line.length, this._terminal.cols);
for (let col = startLineColumn; col < endLineColumn; col++) {
const c = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
if (!c) {
Expand Down Expand Up @@ -155,13 +157,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
private _lastContentCursorRow: number = 0;
private _lastContentCursorCol: number = 0;

constructor(
buffer: IBuffer,
private readonly _terminal: Terminal
) {
super(buffer);
}

protected _beforeSerialize(rows: number, start: number, end: number): void {
this._allRows = new Array<string>(rows);
this._lastContentCursorRow = start;
Expand Down Expand Up @@ -191,6 +186,8 @@ class StringSerializeHandler extends BaseSerializeHandler {

// Fetch current line
const currentLine = this._buffer.getLine(row)!;
// A line can be longer than cols, so its last cell on screen is at cols - 1
const currentLineLength = Math.min(currentLine.length, this._terminal.cols);
// Fetch next line
const nextLine = this._buffer.getLine(row + 1)!;

Expand All @@ -202,8 +199,8 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._lastCursorCol = 0;
} else {
rowSeparator = '';
const thisRowLastChar = currentLine.getCell(currentLine.length - 1, this._thisRowLastChar)!;
const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2, this._thisRowLastSecondChar)!;
const thisRowLastChar = currentLine.getCell(currentLineLength - 1, this._thisRowLastChar)!;
const thisRowLastSecondChar = currentLine.getCell(currentLineLength - 2, this._thisRowLastSecondChar)!;
const nextRowFirstChar = nextLine.getCell(0, this._nextRowFirstChar)!;
const isNextRowFirstCharDoubleWidth = nextRowFirstChar.getWidth() > 1;

Expand Down Expand Up @@ -251,9 +248,9 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (this._nullCellCount > 0) {
// do these because we filled the last several null slot, which we shouldn't
rowSeparator += '\u001b[A';
rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}C`;
rowSeparator += `\u001b[${currentLineLength - this._nullCellCount}C`;
rowSeparator += `\u001b[${this._nullCellCount}X`;
rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}D`;
rowSeparator += `\u001b[${currentLineLength - this._nullCellCount}D`;
rowSeparator += '\u001b[B';
}

Expand Down Expand Up @@ -639,14 +636,14 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {

constructor(
buffer: IBuffer,
private readonly _terminal: Terminal,
terminal: Terminal,
private readonly _options: Partial<IHTMLSerializeOptions>
) {
super(buffer);
super(buffer, terminal);

// For xterm headless: fallback to ansi colors
if ((_terminal as any)._core._themeService) {
this._ansiColors = (_terminal as any)._core._themeService.colors.ansi;
if ((terminal as any)._core._themeService) {
this._ansiColors = (terminal as any)._core._themeService.colors.ansi;
}
else {
this._ansiColors = DEFAULT_ANSI_COLORS;
Expand Down
Loading