mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-29 04:32:57 +00:00
* fix(app): restore terminal surface after tab hide so CJK cells redraw Retained panels use display:none. Coming back remasured without treating that as a surface restore, so Hangul kept 2-cell slots while glyphs painted at ~1 cell (extra gap). Reload WebGL if the context was lost and force a refresh when the host grows from zero. Closes #3434 * fix(server): keep wide-char continuation cells out of terminal snapshots Wide chars (CJK) occupy two xterm cells, and the second cell reports no chars. The snapshot capture collapsed that empty continuation to " ", so every restored Hangul syllable took three columns instead of two. The injected space was real: selecting and copying the restored row carried it into the clipboard. Capture the continuation as "" and have renderTerminalSnapshotToAnsi skip it. No schema change; snapshots from older daemons still replay verbatim. * fix(app): restore retained terminals on presentation Co-authored-by: seob717 <dev.seob717@gmail.com> * fix(terminal): limit restoration changes to confirmed CJK corruption Keep the independently reproduced snapshot fix. Surface recovery behavior remains unverified and belongs with the broader viewport report. Dispose the replay terminal after the regression test. --------- Co-authored-by: Jason <mildreins@gmail.com> Co-authored-by: seob717 <dev.seob717@gmail.com> Co-authored-by: liujin0506 <liujin0506@qq.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { renderTerminalSnapshotToAnsi } from "./terminal-snapshot";
|
|
import type { TerminalState } from "./messages";
|
|
|
|
function cells(text: string): TerminalState["grid"][number] {
|
|
return [...text].map((char) => ({ char }));
|
|
}
|
|
|
|
describe("renderTerminalSnapshotToAnsi", () => {
|
|
it("renders soft-wrapped rows as one contiguous logical line when wrap flags are present", () => {
|
|
// The server soft-wrapped one logical line "ABCDEFGHIJKLMNOP" at 10 cols into
|
|
// two grid rows. gridWrapped[0] = true marks row 0 as continuing into row 1.
|
|
const state: TerminalState = {
|
|
rows: 2,
|
|
cols: 10,
|
|
scrollback: [],
|
|
scrollbackWrapped: [],
|
|
grid: [cells("ABCDEFGHIJ"), cells("KLMNOP")],
|
|
gridWrapped: [true, false],
|
|
cursor: { row: 1, col: 6 },
|
|
};
|
|
|
|
const ansi = renderTerminalSnapshotToAnsi(state);
|
|
|
|
// The rows must arrive unbroken so xterm re-wraps them itself (and can later
|
|
// reflow them) — no hard newline injected between "...IJ" and "KL...".
|
|
expect(ansi).toContain("ABCDEFGHIJKLMNOP");
|
|
// Auto-wrap must stay enabled; disabling it (ESC[?7l) is what makes xterm mark
|
|
// the rows non-wrapped and refuse to reflow them on resize.
|
|
expect(ansi).not.toContain("[?7l");
|
|
});
|
|
|
|
it("falls back to verbatim per-row replay when wrap flags are absent (old daemon)", () => {
|
|
// No gridWrapped/scrollbackWrapped: the client cannot tell soft-wraps from hard
|
|
// newlines, so it must keep today's exact behaviour rather than guess.
|
|
const state: TerminalState = {
|
|
rows: 2,
|
|
cols: 10,
|
|
scrollback: [],
|
|
grid: [cells("ABCDEFGHIJ"), cells("KLMNOP")],
|
|
cursor: { row: 1, col: 6 },
|
|
};
|
|
|
|
const ansi = renderTerminalSnapshotToAnsi(state);
|
|
|
|
expect(ansi).toContain("[?7l");
|
|
expect(ansi).toContain("ABCDEFGHIJ\r\nKLMNOP");
|
|
});
|
|
});
|
|
|
|
describe("renderTerminalSnapshotToAnsi wide chars", () => {
|
|
it("skips wide-char continuation cells so the glyph keeps its two columns", () => {
|
|
// The daemon captures "통" as the glyph followed by an empty continuation
|
|
// cell. Emitting anything for that cell pushes the rest of the row right.
|
|
const state: TerminalState = {
|
|
rows: 1,
|
|
cols: 10,
|
|
scrollback: [],
|
|
grid: [[{ char: "통" }, { char: "" }, { char: "합" }, { char: "" }, { char: "!" }]],
|
|
cursor: { row: 0, col: 5 },
|
|
};
|
|
|
|
expect(renderTerminalSnapshotToAnsi(state)).toContain("통합!");
|
|
});
|
|
|
|
it("replays snapshots from an old daemon verbatim, including its continuation spaces", () => {
|
|
// Old daemons already collapsed the continuation to " ". The serializer
|
|
// cannot tell that apart from a real space, so it must not guess.
|
|
const state: TerminalState = {
|
|
rows: 1,
|
|
cols: 10,
|
|
scrollback: [],
|
|
grid: [cells("통 합 !")],
|
|
cursor: { row: 0, col: 6 },
|
|
};
|
|
|
|
expect(renderTerminalSnapshotToAnsi(state)).toContain("통 합 !");
|
|
});
|
|
});
|