From 7c9c8f7293f7651cae7fe564a9f47353c3749da0 Mon Sep 17 00:00:00 2001 From: Peter Ebden Date: Mon, 2 Nov 2026 13:23:27 +0000 Subject: [PATCH 1/2] Improve word wrapping in interactive output --- src/output/interactive_display.go | 16 ++++++++++++++-- src/output/interactive_display_test.go | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/output/interactive_display.go b/src/output/interactive_display.go index 1b442912cd..eee33fdfea 100644 --- a/src/output/interactive_display.go +++ b/src/output/interactive_display.go @@ -265,25 +265,37 @@ func (d *interactiveDisplay) flush() { } func (d *interactiveDisplay) lprintfPrepare(cols int, s string) string { - if len(s) < cols { + if len(s) < cols && !strings.ContainsRune(s, '\t') { return s // it's short enough, nice and simple } // Okay, it's too long. Tricky thing: ANSI escape codes don't count for width // so we need to count without those. Bonus: make an effort to be unicode-aware. written := 0 inAnsiCode := false + ansiStart := false d.lineBuf.Reset() for _, rune := range s { if inAnsiCode { d.lineBuf.WriteRune(rune) - if rune == 'm' { + // CSI sequences (e.g. \x1b[1m, \x1b[K) end on any byte in the range 0x40-0x7E, + // excluding the opening [. + if !(ansiStart && rune == '[') && rune >= 0x40 && rune <= 0x7E { inAnsiCode = false } + ansiStart = false } else if rune == '\x1b' { d.lineBuf.WriteRune(rune) inAnsiCode = true + ansiStart = true } else if rune == '\n' { d.lineBuf.WriteRune(rune) + written = 0 + } else if rune == '\t' { + // Tabs are one character but can take up to 8 columns. Expand them so we know exactly how wide they are. + for n := 8 - written%8; n > 0 && written < cols-3; n-- { + d.lineBuf.WriteByte(' ') + written++ + } } else if written == cols-3 { d.lineBuf.WriteString("...") written += 3 diff --git a/src/output/interactive_display_test.go b/src/output/interactive_display_test.go index 0d8bc70cc9..0d8b795d90 100644 --- a/src/output/interactive_display_test.go +++ b/src/output/interactive_display_test.go @@ -32,3 +32,19 @@ func TestNewlinesStillWritte(t *testing.T) { // Test that newline still gets written (it doesn't count as horizontal space and is Very Important) assert.Equal(t, "\x1b[30mwibble ...\x1b[1m\n", d.lprintfPrepare(10, "\x1b[30mwibble wobble\x1b[1m\n")) } + +func TestLimitedPrintfEraseAfter(t *testing.T) { + var d interactiveDisplay + assert.Equal(t, "\x1b[Kmmmmmmm...\n", d.lprintfPrepare(10, "\x1b[Kmmmmmmmmmmmmmmmmmmmm\n")) +} + +func TestLimitedPrintfTabsExpanded(t *testing.T) { + var d interactiveDisplay + assert.Equal(t, "a b", d.lprintfPrepare(20, "a\tb")) + assert.Equal(t, "a b...", d.lprintfPrepare(12, "a\tbcdefghijk")) +} + +func TestLimitedPrintfNewlineResetsWidth(t *testing.T) { + var d interactiveDisplay + assert.Equal(t, "wibble ...\nwibble ...\n", d.lprintfPrepare(10, "wibble wobble\nwibble wobble\n")) +} From d3887bf58af63d9a99287891180b309ce3211c6f Mon Sep 17 00:00:00 2001 From: Peter Ebden Date: Mon, 2 Nov 2026 16:12:35 +0000 Subject: [PATCH 2/2] okay apparently this is better --- src/output/interactive_display.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output/interactive_display.go b/src/output/interactive_display.go index eee33fdfea..14af289aa0 100644 --- a/src/output/interactive_display.go +++ b/src/output/interactive_display.go @@ -279,7 +279,7 @@ func (d *interactiveDisplay) lprintfPrepare(cols int, s string) string { d.lineBuf.WriteRune(rune) // CSI sequences (e.g. \x1b[1m, \x1b[K) end on any byte in the range 0x40-0x7E, // excluding the opening [. - if !(ansiStart && rune == '[') && rune >= 0x40 && rune <= 0x7E { + if (!ansiStart || rune != '[') && rune >= 0x40 && rune <= 0x7E { inAnsiCode = false } ansiStart = false