From b05683edf04215a59e0d0317ce4d35444489b96f Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sun, 27 Sep 2026 21:02:34 +0900 Subject: [PATCH] Terminate only OSC strings with BEL DCS and APC end with ST. Stopping at a BEL in their payload framed just the first half and left the rest to be typed into the query. Reported by Copilot on #4926. --- src/tui/light.go | 14 +++++++++++--- src/tui/light_csi_test.go | 6 +++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/tui/light.go b/src/tui/light.go index 114dac0f..f91813ef 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -363,13 +363,21 @@ func csiEnd(buffer []byte) int { } // stringEnd returns the length of the string sequence (DCS, OSC or APC) at the -// start of the buffer, or 0 if its terminator has not arrived. The terminator is -// ST, and BEL as well because xterm has always allowed it. +// start of the buffer, or 0 if its terminator has not arrived. func stringEnd(buffer []byte) int { + if len(buffer) < 2 { + return 0 + } + // Every one of them ends with ST. BEL ends an OSC as well, because xterm has + // always allowed it, but stopping at a BEL inside a DCS or APC payload would + // frame only its first half and leave the rest to be typed into the query. + bel := buffer[1] == ']' for i := 2; i < len(buffer); i++ { switch buffer[i] { case '\a': - return i + 1 + if bel { + return i + 1 + } case Esc.Byte(): if i+1 < len(buffer) && buffer[i+1] == '\\' { return i + 2 diff --git a/src/tui/light_csi_test.go b/src/tui/light_csi_test.go index 7853994d..6d2831f0 100644 --- a/src/tui/light_csi_test.go +++ b/src/tui/light_csi_test.go @@ -28,7 +28,8 @@ func TestUnknownCSISequence(t *testing.T) { {"\x1b]11;rgb:4a4a/4a4a/4a4a\x1b\\", Invalid, 25}, // background color reply {"\x1b]0;a title\a", Invalid, 12}, // BEL terminated {"\x1bP>|kitty(0.48.2)\x1b\\", Invalid, 19}, // XTVERSION reply - {"\x1b_Gi=1;OK\x1b\\", Invalid, 11}, // kitty graphics reply + {"\x1b_Gi=1;OK\x1b\\", Invalid, 11}, + {"\x1bP\ax\x1b\\", Invalid, 6}, // BEL inside a DCS payload is not a terminator // kitty graphics reply // Left alone: this is how ALT-[ and ALT-] arrive {"\x1b[a", Alt, 2}, @@ -71,6 +72,9 @@ func TestStringEnd(t *testing.T) { want int }{ {"\x1b]0;t\a", 6}, + {"\x1bP\ax\x1b\\", 6}, // BEL in the payload ignored, ST ends it + {"\x1bP\a", 0}, // BEL does not terminate a DCS + {"\x1b_Gi=1\a", 0}, // nor an APC {"\x1b]0;t\x1b\\", 7}, {"\x1b_G\x1b\\", 5}, {"\x1b]0;t", 0}, // no terminator