fix(screen): keep REP eligibility across empty writes after a lone ESC
- commit
- f05313741739bc8875e186e4ed01788fec63d6a0
- author
- brice · · 2026-10-15 02:39 UTC
- parent
- 6209370
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +22 -7
M screen.go +5 -2
| @@ -184,8 +184,11 @@ func (s *Screen) Write(data []byte) { | |||
| 184 | 184 | if len(s.escapeBuf) == 0 { | |
| 185 | 185 | s.trimEscapeBuffers() | |
| 186 | 186 | } else if isKittyEscapeContinuation(s.escapeBuf, data) { | |
| 187 | - | // A Kitty APC sits between any earlier graphic and what follows. | |
| 188 | - | s.lastPrinted = 0 | |
| 187 | + | // A Kitty APC sits between any earlier graphic and what follows. An | |
| 188 | + | // empty write has not identified the buffered escape yet. | |
| 189 | + | if len(data) > 0 { | |
| 190 | + | s.lastPrinted = 0 | |
| 191 | + | } | |
| 189 | 192 | prefix := s.escapeBuf | |
| 190 | 193 | s.escapeBuf = nil | |
| 191 | 194 | data = s.continueKittyEscape(prefix, data) | |
| @@ -184,8 +184,11 @@ func (s *Screen) Write(data []byte) { | |||||
| 184 | if len(s.escapeBuf) == 0 { | 184 | if len(s.escapeBuf) == 0 { | ||
| 185 | s.trimEscapeBuffers() | 185 | s.trimEscapeBuffers() | ||
| 186 | } else if isKittyEscapeContinuation(s.escapeBuf, data) { | 186 | } else if isKittyEscapeContinuation(s.escapeBuf, data) { | ||
| 187 | - | // A Kitty APC sits between any earlier graphic and what follows. | 187 | + | // A Kitty APC sits between any earlier graphic and what follows. An |
| 188 | - | s.lastPrinted = 0 | 188 | + | // empty write has not identified the buffered escape yet. |
| 189 | + | if len(data) > 0 { | |||
| 190 | + | s.lastPrinted = 0 | |||
| 191 | + | } | |||
| 189 | prefix := s.escapeBuf | 192 | prefix := s.escapeBuf | ||
| 190 | s.escapeBuf = nil | 193 | s.escapeBuf = nil | ||
| 191 | data = s.continueKittyEscape(prefix, data) | 194 | data = s.continueKittyEscape(prefix, data) | ||
M screen_charset_test.go +17 -5
| @@ -60,11 +60,23 @@ func TestSoftResetCursorAppearance(t *testing.T) { | |||
| 60 | 60 | } | |
| 61 | 61 | ||
| 62 | 62 | func TestRepeatSplitAcrossWrites(t *testing.T) { | |
| 63 | - | s := NewScreen(10, 1) | |
| 64 | - | s.Write([]byte("a\x1b[")) | |
| 65 | - | s.Write([]byte("2b")) | |
| 66 | - | if got := strings.TrimRight(lineText(s, 0), " "); got != "aaa" { | |
| 67 | - | t.Fatalf("line = %q, want %q", got, "aaa") | |
| 63 | + | tests := []struct { | |
| 64 | + | name string | |
| 65 | + | writes []string | |
| 66 | + | }{ | |
| 67 | + | {name: "split inside CSI", writes: []string{"a\x1b[", "2b"}}, | |
| 68 | + | {name: "empty write after lone ESC", writes: []string{"a\x1b", "", "[2b"}}, | |
| 69 | + | } | |
| 70 | + | for _, tt := range tests { | |
| 71 | + | t.Run(tt.name, func(t *testing.T) { | |
| 72 | + | s := NewScreen(10, 1) | |
| 73 | + | for _, w := range tt.writes { | |
| 74 | + | s.Write([]byte(w)) | |
| 75 | + | } | |
| 76 | + | if got := strings.TrimRight(lineText(s, 0), " "); got != "aaa" { | |
| 77 | + | t.Fatalf("line = %q, want %q", got, "aaa") | |
| 78 | + | } | |
| 79 | + | }) | |
| 68 | 80 | } | |
| 69 | 81 | } | |
| 70 | 82 | ||
| @@ -60,11 +60,23 @@ func TestSoftResetCursorAppearance(t *testing.T) { | |||||
| 60 | } | 60 | } | ||
| 61 | 61 | ||||
| 62 | func TestRepeatSplitAcrossWrites(t *testing.T) { | 62 | func TestRepeatSplitAcrossWrites(t *testing.T) { | ||
| 63 | - | s := NewScreen(10, 1) | 63 | + | tests := []struct { |
| 64 | - | s.Write([]byte("a\x1b[")) | 64 | + | name string |
| 65 | - | s.Write([]byte("2b")) | 65 | + | writes []string |
| 66 | - | if got := strings.TrimRight(lineText(s, 0), " "); got != "aaa" { | 66 | + | }{ |
| 67 | - | t.Fatalf("line = %q, want %q", got, "aaa") | 67 | + | {name: "split inside CSI", writes: []string{"a\x1b[", "2b"}}, |
| 68 | + | {name: "empty write after lone ESC", writes: []string{"a\x1b", "", "[2b"}}, | |||
| 69 | + | } | |||
| 70 | + | for _, tt := range tests { | |||
| 71 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 72 | + | s := NewScreen(10, 1) | |||
| 73 | + | for _, w := range tt.writes { | |||
| 74 | + | s.Write([]byte(w)) | |||
| 75 | + | } | |||
| 76 | + | if got := strings.TrimRight(lineText(s, 0), " "); got != "aaa" { | |||
| 77 | + | t.Fatalf("line = %q, want %q", got, "aaa") | |||
| 78 | + | } | |||
| 79 | + | }) | |||
| 68 | } | 80 | } | ||
| 69 | } | 81 | } | ||
| 70 | 82 | ||||