From cb9fab9c0a330122191681deb901ef9dea5c3809 Mon Sep 17 00:00:00 2001 From: Peter Severin Rasmussen Date: Tue, 21 Jul 2026 21:08:47 +0200 Subject: [PATCH] fix(vt): handle SCO restore cursor (CSI u) vt registers a CSI 's' handler that saves the cursor for SCO console mode (ansi.SCOSC) when DECSLRM is not active, but there was no matching CSI 'u' handler for the SCO restore (ansi.SCORC). The sequence parsed fine and simply matched no handler, so ESC[u was silently dropped: the cursor never moved back and subsequent writes landed at the wrong column instead of overwriting. Register a CSI 'u' handler that calls scr.RestoreCursor(), mirroring the existing DECSC/DECRC pair (ESC 7 / ESC 8) already wired up in the same file. This does not collide with the kitty keyboard protocol sequences on the same final byte (CSI > u, CSI = ... u, CSI < u): those carry a prefix byte, which is packed into the command int separately from a bare final byte, so they resolve to distinct handler keys. Adds a table-driven test asserting that SCOSC/SCORC repositions the cursor, next to an equivalent DECSC/DECRC case for symmetry. --- vt/emulator_test.go | 32 ++++++++++++++++++++++++++++++++ vt/handlers.go | 6 ++++++ 2 files changed, 38 insertions(+) diff --git a/vt/emulator_test.go b/vt/emulator_test.go index 5f531355..cfd35071 100644 --- a/vt/emulator_test.go +++ b/vt/emulator_test.go @@ -1753,6 +1753,38 @@ var cases = []struct { pos: uv.Pos(0, 0), }, + // Save Cursor [ansi.DECSC] and Restore Cursor [ansi.DECRC] + { + name: "DECSC DECRC Restore Cursor Position", + w: 10, h: 1, + input: []string{ + "\x1b[1;1H", // move to top-left + "\x1b[2J", // clear screen + "\x1b7", // save cursor position + "AAAA", + "\x1b8", // restore cursor position + "BB", + }, + want: []string{"BBAA "}, + pos: uv.Pos(2, 0), + }, + + // Save Current Cursor Position [ansi.SCOSC] and Restore Current Cursor Position [ansi.SCORC] + { + name: "SCOSC SCORC Restore Cursor Position", + w: 10, h: 1, + input: []string{ + "\x1b[1;1H", // move to top-left + "\x1b[2J", // clear screen + "\x1b[s", // save cursor position (SCO) + "AAAA", + "\x1b[u", // restore cursor position (SCO) + "BB", + }, + want: []string{"BBAA "}, + pos: uv.Pos(2, 0), + }, + // Tab Clear [ansi.TBC] { name: "TBC Clear Single Tab Stop", diff --git a/vt/handlers.go b/vt/handlers.go index ca7ff54f..e750ae6e 100644 --- a/vt/handlers.go +++ b/vt/handlers.go @@ -920,4 +920,10 @@ func (e *Emulator) registerDefaultCsiHandlers() { return true }) + + e.RegisterCsiHandler('u', func(params ansi.Params) bool { + // Restore Current Cursor Position [ansi.SCORC] + e.scr.RestoreCursor() + return true + }) }