wip
This commit is contained in:
@@ -29,6 +29,42 @@ func TestViewportRendererSwallowsAltScreenToggles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewportRendererSwallowsOriginModeToggles(t *testing.T) {
|
||||
vr := newViewportRenderer(newTerminalLayout(120, 40))
|
||||
got := string(vr.Render([]byte("a\x1b[?6hb\x1b[?6lc")))
|
||||
if strings.Contains(got, "\x1b[?6h") || strings.Contains(got, "\x1b[?6l") {
|
||||
t.Fatalf("origin-mode toggles leaked to host: %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "a") || !strings.Contains(got, "b") || !strings.Contains(got, "c") {
|
||||
t.Fatalf("origin-mode toggles should not drop surrounding text: got %q", got)
|
||||
}
|
||||
if strings.Count(got, "\x1b[3;1H") != 2 {
|
||||
t.Fatalf("origin-mode set/reset should home inside the viewport twice: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewportRendererSwallowsLeftRightMarginMode(t *testing.T) {
|
||||
vr := newViewportRenderer(newTerminalLayout(120, 40))
|
||||
got := string(vr.Render([]byte("a\x1b[?69h\x1b[10;80sb\x1b[?69lc")))
|
||||
if strings.Contains(got, "\x1b[?69h") || strings.Contains(got, "\x1b[10;80s") || strings.Contains(got, "\x1b[?69l") {
|
||||
t.Fatalf("left/right margin controls leaked to host: %q", got)
|
||||
}
|
||||
if got != "abc" {
|
||||
t.Fatalf("left/right margin controls should be swallowed without dropping text: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewportRendererOriginModeCUPUsesScrollTop(t *testing.T) {
|
||||
vr := newViewportRenderer(newTerminalLayout(120, 40))
|
||||
got := string(vr.Render([]byte("\x1b[5;10r\x1b[?6h\x1b[1;1H")))
|
||||
if strings.Contains(got, "\x1b[?6h") {
|
||||
t.Fatalf("origin-mode set leaked to host: %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "\x1b[7;1H") {
|
||||
t.Fatalf("CUP row 1 in origin mode should land at scrollTop row 5 shifted to host row 7: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewportRendererClearScreenIsViewportOnly(t *testing.T) {
|
||||
// hostRows=7 leaves four viewport rows after the 2-row tab bar and
|
||||
// 1-row status reservation.
|
||||
@@ -239,6 +275,73 @@ func TestViewportRendererFlagsLineFeedAtCustomScrollBottom(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Long claude sessions can leave the child cursor at viewport row 1 and
|
||||
// then emit CSI A (cursor up) with a large step before redrawing. The
|
||||
// raw CSI A would walk the host cursor into the tab bar; the next
|
||||
// printable would then write into row 1 / row 2. Clamp the step at the
|
||||
// viewport top so the host cursor stays inside the viewport.
|
||||
func TestViewportRendererClampsCUUAtViewportTop(t *testing.T) {
|
||||
vr := newViewportRenderer(newTerminalLayout(120, 40))
|
||||
// CUP to viewport row 1 then CUU by 50.
|
||||
got := string(vr.Render([]byte("\x1b[1;1H\x1b[50ACLOBBER")))
|
||||
if !strings.Contains(got, "\x1b[3;1H") {
|
||||
t.Fatalf("expected CUP shifted to mainTop: got %q", got)
|
||||
}
|
||||
// The CUU should have been swallowed (n clamped to 0 from row 1).
|
||||
if strings.Contains(got, "\x1b[50A") {
|
||||
t.Fatalf("CUU 50 from viewport row 1 leaked: got %q", got)
|
||||
}
|
||||
// And the subsequent printables should land inside the viewport,
|
||||
// not above it.
|
||||
if !strings.Contains(got, "CLOBBER") {
|
||||
t.Fatalf("printables should still be emitted after clamped CUU: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewportRendererClampsCUUPartial(t *testing.T) {
|
||||
vr := newViewportRenderer(newTerminalLayout(120, 40))
|
||||
// CUP to viewport row 5, then CUU by 50 → safe step is 4.
|
||||
got := string(vr.Render([]byte("\x1b[5;1H\x1b[50A")))
|
||||
if !strings.Contains(got, "\x1b[4A") {
|
||||
t.Fatalf("CUU 50 from row 5 should clamp to 4: got %q", got)
|
||||
}
|
||||
if strings.Contains(got, "\x1b[50A") {
|
||||
t.Fatalf("unclamped CUU leaked: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewportRendererClampsCUDAtViewportBottom(t *testing.T) {
|
||||
// childRows=37 for layout(120, 40). Park cursor at row 37, ask for
|
||||
// 10 down → safe step is 0.
|
||||
vr := newViewportRenderer(newTerminalLayout(120, 40))
|
||||
got := string(vr.Render([]byte("\x1b[37;1H\x1b[10B")))
|
||||
if strings.Contains(got, "\x1b[10B") {
|
||||
t.Fatalf("CUD past viewport bottom should be dropped: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewportRendererClampsCPLAndHomesColumn(t *testing.T) {
|
||||
vr := newViewportRenderer(newTerminalLayout(120, 40))
|
||||
// CUP to row 1 col 50 then CPL by 5 → step clamped to 0, but col
|
||||
// must still reset to 1 (CR emitted).
|
||||
got := string(vr.Render([]byte("\x1b[1;50H\x1b[5F")))
|
||||
if strings.Contains(got, "\x1b[5F") {
|
||||
t.Fatalf("CPL 5 from row 1 should not leak: got %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "\r") {
|
||||
t.Fatalf("CPL should home column to 1 with CR: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewportRendererClampsCNL(t *testing.T) {
|
||||
vr := newViewportRenderer(newTerminalLayout(120, 40))
|
||||
// CUP to row 35 then CNL by 50 → safe step is 2 (childRows-35).
|
||||
got := string(vr.Render([]byte("\x1b[35;10H\x1b[50E")))
|
||||
if !strings.Contains(got, "\x1b[2E") {
|
||||
t.Fatalf("CNL 50 from row 35 should clamp to 2: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewportRendererForwardsRIVerbatim(t *testing.T) {
|
||||
// We rely on the host terminal performing the scroll inside the
|
||||
// DECSTBM region; the renderer must not eat or transform RI. If a
|
||||
|
||||
Reference in New Issue
Block a user