From 4e99467d8b64e7dcdbe018b8ba561f8127cf353f Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 9 Sep 2026 15:34:39 -0400 Subject: [PATCH 1/2] fix(renderer): don't trim final newline (because it can be intentional) --- cursed_renderer.go | 32 +++++--- cursed_renderer_test.go | 73 +++++++++++++++++++ testdata/TestClearMsg/bg_fg_cur_color.golden | 2 +- testdata/TestClearMsg/clear_screen.golden | 2 +- .../TestClearMsg/read_set_clipboard.golden | 2 +- testdata/TestViewModel/altscreen.golden | 2 +- testdata/TestViewModel/bg_set_color.golden | 2 +- testdata/TestViewModel/bp_stop_start.golden | 2 +- testdata/TestViewModel/cursor_hide.golden | 2 +- .../kitty_stop_startreleases.golden | 2 +- testdata/TestViewModel/mouse_allmotion.golden | 2 +- .../TestViewModel/mouse_cellmotion.golden | 2 +- testdata/TestViewModel/mouse_disable.golden | 2 +- 13 files changed, 106 insertions(+), 21 deletions(-) diff --git a/cursed_renderer.go b/cursed_renderer.go index 0ad3e602b2..e80f9d6d91 100644 --- a/cursed_renderer.go +++ b/cursed_renderer.go @@ -514,16 +514,28 @@ func (s *cursedRenderer) flush(closing bool) error { // cursor position might get updated during rendering. s.scr.MoveTo(view.Cursor.X, view.Cursor.Y) } else if !view.AltScreen { - // We don't want the cursor to be dangling at the end of the line in - // inline mode because it can cause unwanted line wraps in some - // terminals. So we move it to the beginning of the next line if - // necessary. - // This is only needed when the cursor is hidden because when it's - // visible, we already set its position above. - x, y := s.scr.Position() - if x >= s.width-1 { - s.scr.MoveTo(0, y) - } + // Park the cursor at the beginning of the frame's last line. The + // cursor is hidden in this branch (a visible cursor is positioned + // above), so parking it never moves anything the user can see, and + // like before, it keeps the cursor from dangling at the end of the + // last drawn line, which can cause unwanted line wraps in some + // terminals. + // + // The frame's height counts the view's trailing blank lines, so + // parking here also commits the renderer to them: a frame ending + // in intentional blank lines keeps its bottom margin, and output + // printed after the program exits starts below the frame rather + // than on its last visible line. Frames taller than the screen + // have their top rows dropped, so their last visible row is the + // screen's. + y := frameArea.Dy() - 1 + if y >= s.height { + y = s.height - 1 + } + if y < 0 { + y = 0 + } + s.scr.MoveTo(0, y) } if err := s.scr.Flush(); err != nil { diff --git a/cursed_renderer_test.go b/cursed_renderer_test.go index 8b2efa8d4f..6256f51086 100644 --- a/cursed_renderer_test.go +++ b/cursed_renderer_test.go @@ -95,6 +95,79 @@ func assertInOrder(t *testing.T, got string, wants ...string) { } } +// Views ending in blank lines should keep them: the frame's height counts +// the view's trailing blank lines, and the cursor must be parked below the +// frame after rendering. That way the blank lines survive the closing +// erase, and output printed after the program exits starts below the frame +// instead of on its last visible line. +func TestCursedRenderer_trailingBlankLines(t *testing.T) { + t.Parallel() + + var out bytes.Buffer + r := newCursedRenderer(&out, []string{"TERM=xterm-256color"}, 80, 24) + r.start() + + // A 3-line frame: one visible line, two intentional blank lines. The + // cursor must be parked on the third row (0-indexed row 2). + r.render(NewView("hello\n\n")) + if err := r.flush(false); err != nil { + t.Fatal(err) + } + if x, y := r.scr.Position(); x != 0 || y != 2 { + t.Fatalf("cursor parked at (%d, %d), want (0, 2)", x, y) + } + + // The parked position must survive a repaint that redraws the same + // content (e.g. after a suspend/resize clears the buffer), so the + // frame keeps its trailing blank lines across flushes. + r.pendingErase = true + r.render(NewView("hello\n\n")) + if err := r.flush(false); err != nil { + t.Fatal(err) + } + if x, y := r.scr.Position(); x != 0 || y != 2 { + t.Fatalf("cursor parked at (%d, %d) after repaint, want (0, 2)", x, y) + } + + // Frames without trailing blank lines park the cursor at the start of + // their last line, matching the previous renderer's behavior. + r.render(NewView("hello")) + if err := r.flush(false); err != nil { + t.Fatal(err) + } + if x, y := r.scr.Position(); x != 0 || y != 0 { + t.Fatalf("cursor parked at (%d, %d), want (0, 0)", x, y) + } + if err := r.close(); err != nil { + t.Fatal(err) + } +} + +// Frames taller than the screen drop their top rows, so the parked cursor +// must land on the screen's last visible row, not beyond it. +func TestCursedRenderer_trailingBlankLinesTallFrame(t *testing.T) { + t.Parallel() + + var out bytes.Buffer + r := newCursedRenderer(&out, []string{"TERM=xterm-256color"}, 80, 24) + r.start() + + // A 30-line frame (26 content lines + 4 trailing blank lines) inside a + // 24-row terminal. The renderer drops the top 6 rows, leaving the + // screen's last visible row as the frame's last row. + content := strings.Repeat("line\n", 30) + r.render(NewView(content)) + if err := r.flush(false); err != nil { + t.Fatal(err) + } + if x, y := r.scr.Position(); x != 0 || y != 23 { + t.Fatalf("cursor parked at (%d, %d), want (0, 23)", x, y) + } + if err := r.close(); err != nil { + t.Fatal(err) + } +} + func TestCursedRenderer_restoresKittyKeyboardStack(t *testing.T) { t.Parallel() diff --git a/testdata/TestClearMsg/bg_fg_cur_color.golden b/testdata/TestClearMsg/bg_fg_cur_color.golden index 26f1238abf..9141b90398 100644 --- a/testdata/TestClearMsg/bg_fg_cur_color.golden +++ b/testdata/TestClearMsg/bg_fg_cur_color.golden @@ -1 +1 @@ -[?25l[?2004h[>4;2m[>1u success[>4m[<1u [?25h[?2004l[?2026$p[?2027$p]10;?]11;?]12;? \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p]10;?]11;?]12;? \ No newline at end of file diff --git a/testdata/TestClearMsg/clear_screen.golden b/testdata/TestClearMsg/clear_screen.golden index b78bf14687..be12631a28 100644 --- a/testdata/TestClearMsg/clear_screen.golden +++ b/testdata/TestClearMsg/clear_screen.golden @@ -1 +1 @@ -[?25l [?2004h[>4;2m[>1usuccess[>4m[<1u [?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l [?2004h[>4;2m[>1usuccess [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestClearMsg/read_set_clipboard.golden b/testdata/TestClearMsg/read_set_clipboard.golden index 4249ef1e4d..186c8ac68b 100644 --- a/testdata/TestClearMsg/read_set_clipboard.golden +++ b/testdata/TestClearMsg/read_set_clipboard.golden @@ -1 +1 @@ -[?25l[?2004h[>4;2m[>1u success[>4m[<1u [?25h[?2004l[?2026$p[?2027$p]52;c;?]52;c;c3VjY2Vzcw== \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p]52;c;?]52;c;c3VjY2Vzcw== \ No newline at end of file diff --git a/testdata/TestViewModel/altscreen.golden b/testdata/TestViewModel/altscreen.golden index 5df8d94fe2..fabd3b7e1a 100644 --- a/testdata/TestViewModel/altscreen.golden +++ b/testdata/TestViewModel/altscreen.golden @@ -1 +1 @@ -[?25l[?2004h[>4;2m[>1u success[>4m[<1u [?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/bg_set_color.golden b/testdata/TestViewModel/bg_set_color.golden index 31d49c9985..61704ec5e6 100644 --- a/testdata/TestViewModel/bg_set_color.golden +++ b/testdata/TestViewModel/bg_set_color.golden @@ -1 +1 @@ -[?25l[?2004h[>4;2m[>1u]11;#ffffff success[>4m[<1u [?25h[?2004l]111[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>1u]11;#ffffff success [>4m[<1u[?25h[?2004l]111[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/bp_stop_start.golden b/testdata/TestViewModel/bp_stop_start.golden index 5df8d94fe2..fabd3b7e1a 100644 --- a/testdata/TestViewModel/bp_stop_start.golden +++ b/testdata/TestViewModel/bp_stop_start.golden @@ -1 +1 @@ -[?25l[?2004h[>4;2m[>1u success[>4m[<1u [?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/cursor_hide.golden b/testdata/TestViewModel/cursor_hide.golden index 5df8d94fe2..fabd3b7e1a 100644 --- a/testdata/TestViewModel/cursor_hide.golden +++ b/testdata/TestViewModel/cursor_hide.golden @@ -1 +1 @@ -[?25l[?2004h[>4;2m[>1u success[>4m[<1u [?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/kitty_stop_startreleases.golden b/testdata/TestViewModel/kitty_stop_startreleases.golden index 280d919b75..db3f43baad 100644 --- a/testdata/TestViewModel/kitty_stop_startreleases.golden +++ b/testdata/TestViewModel/kitty_stop_startreleases.golden @@ -1 +1 @@ -[?25l[?2004h[>4;2m[>3u success[>4m[<1u [?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>3u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/mouse_allmotion.golden b/testdata/TestViewModel/mouse_allmotion.golden index 4d8076aa45..66fc56d8c9 100644 --- a/testdata/TestViewModel/mouse_allmotion.golden +++ b/testdata/TestViewModel/mouse_allmotion.golden @@ -1 +1 @@ -[?25l[?2004h[?1003h[?1006h[>4;2m[>1u success[>4m[<1u [?25h[?2004l[?1002l[?1003l[?1006l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[?1003h[?1006h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?1002l[?1003l[?1006l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/mouse_cellmotion.golden b/testdata/TestViewModel/mouse_cellmotion.golden index f9070c2766..d814d128ed 100644 --- a/testdata/TestViewModel/mouse_cellmotion.golden +++ b/testdata/TestViewModel/mouse_cellmotion.golden @@ -1 +1 @@ -[?25l[?2004h[?1002h[?1006h[>4;2m[>1u success[>4m[<1u [?25h[?2004l[?1002l[?1003l[?1006l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[?1002h[?1006h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?1002l[?1003l[?1006l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/mouse_disable.golden b/testdata/TestViewModel/mouse_disable.golden index 5df8d94fe2..fabd3b7e1a 100644 --- a/testdata/TestViewModel/mouse_disable.golden +++ b/testdata/TestViewModel/mouse_disable.golden @@ -1 +1 @@ -[?25l[?2004h[>4;2m[>1u success[>4m[<1u [?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file From 5617f4d7053e06522ecfce182b8e8155e9e36502 Mon Sep 17 00:00:00 2001 From: drew Date: Mon, 28 Sep 2026 16:17:16 +0400 Subject: [PATCH 2/2] fix: don't lose the last line of a view Signed-off-by: drew --- cursed_renderer.go | 48 +++++++----- cursed_renderer_test.go | 74 +++++++++++++++++++ testdata/TestClearMsg/bg_fg_cur_color.golden | 3 +- testdata/TestClearMsg/clear_screen.golden | 3 +- .../TestClearMsg/read_set_clipboard.golden | 3 +- testdata/TestViewModel/altscreen.golden | 3 +- testdata/TestViewModel/bg_set_color.golden | 3 +- testdata/TestViewModel/bp_stop_start.golden | 3 +- testdata/TestViewModel/cursor_hide.golden | 3 +- testdata/TestViewModel/cursor_hideshow.golden | 3 +- .../kitty_stop_startreleases.golden | 3 +- testdata/TestViewModel/mouse_allmotion.golden | 3 +- .../TestViewModel/mouse_cellmotion.golden | 3 +- testdata/TestViewModel/mouse_disable.golden | 3 +- 14 files changed, 129 insertions(+), 29 deletions(-) diff --git a/cursed_renderer.go b/cursed_renderer.go index e80f9d6d91..72e036b5ff 100644 --- a/cursed_renderer.go +++ b/cursed_renderer.go @@ -200,6 +200,16 @@ func (s *cursedRenderer) close() (err error) { if lv.AltScreen { enableAltScreen(s, false, true) } else { + // A view without a trailing newline has content on its last row, + // unlike a view ending in a newline whose last row is the + // intentional blank line below the frame. Move the cursor one row + // further down (scrolling the screen if the frame fills it, just + // like printing a trailing newline would) so the closing erase and + // the shell prompt that follows start below the frame instead of + // overwriting its last line. + if lv.Content != "" && !strings.HasSuffix(lv.Content, "\n") { + _, _ = s.scr.WriteString("\r\n") + } _, _ = s.scr.WriteString(ansi.EraseScreenBelow) } if lv.Cursor == nil { @@ -299,6 +309,7 @@ func (s *cursedRenderer) flush(closing bool) error { } content := uv.NewStyledString(view.Content) + contentHeight := content.Height() if !view.AltScreen { // We need to resizes the screen based on the frame height and // terminal width. This is because the frame height can change based on @@ -306,7 +317,13 @@ func (s *cursedRenderer) flush(closing bool) error { // of items, the height of the frame will be the number of items in the // list. This is different from the alt screen buffer, which has a // fixed height and width. - frameHeight := content.Height() + // + // Frames taller than the screen can only show their bottom rows, so + // the frame area is capped at the screen height. Keeping the frame + // area equal to what's actually on screen also keeps it equal to the + // screen buffer's bounds across flushes, which lets unchanged frames + // render as no-ops instead of forcing a full redraw every flush. + frameHeight := min(contentHeight, s.height) if frameHeight != frameArea.Dy() { frameArea.Max.Y = frameHeight } @@ -342,13 +359,16 @@ func (s *cursedRenderer) flush(closing bool) error { // Clear our screen buffer before copying the new frame into it to ensure // we erase any old content. s.cellbuf.Clear() - content.Draw(s.cellbuf, s.cellbuf.Bounds()) - - // If the frame height is greater than the screen height, we drop the - // lines from the top of the buffer. - if frameHeight := frameArea.Dy(); frameHeight > s.height { - s.cellbuf.Lines = s.cellbuf.Lines[frameHeight-s.height:] + // Frames taller than the screen are drawn with their top rows shifted + // above the buffer so they get clipped away, and the buffer keeps only + // the bottom, visible rows. Drawing the frame this way, instead of + // resizing the buffer to the full frame height and slicing its lines, + // keeps the buffer's bounds stable across flushes. + drawArea := s.cellbuf.Bounds() + if !view.AltScreen && contentHeight > drawArea.Dy() { + drawArea.Min.Y -= contentHeight - drawArea.Dy() } + content.Draw(s.cellbuf, drawArea) // Alt screen mode. shouldUpdateAltScreen := (s.lastView == nil && view.AltScreen) || (s.lastView != nil && s.lastView.AltScreen != view.AltScreen) @@ -525,16 +545,10 @@ func (s *cursedRenderer) flush(closing bool) error { // parking here also commits the renderer to them: a frame ending // in intentional blank lines keeps its bottom margin, and output // printed after the program exits starts below the frame rather - // than on its last visible line. Frames taller than the screen - // have their top rows dropped, so their last visible row is the - // screen's. - y := frameArea.Dy() - 1 - if y >= s.height { - y = s.height - 1 - } - if y < 0 { - y = 0 - } + // than on its last visible line. The frame area is capped at the + // screen height, so frames taller than the screen park on the + // screen's last row, which is their last visible row. + y := max(frameArea.Dy()-1, 0) s.scr.MoveTo(0, y) } diff --git a/cursed_renderer_test.go b/cursed_renderer_test.go index 6256f51086..e4770dc316 100644 --- a/cursed_renderer_test.go +++ b/cursed_renderer_test.go @@ -163,11 +163,85 @@ func TestCursedRenderer_trailingBlankLinesTallFrame(t *testing.T) { if x, y := r.scr.Position(); x != 0 || y != 23 { t.Fatalf("cursor parked at (%d, %d), want (0, 23)", x, y) } + + // An unchanged flush must be a no-op. The frame area is capped at the + // screen height, so it stays equal to the screen buffer's bounds across + // flushes. Otherwise every flush redraws the whole frame, which scrolls + // the screen each time in terminals where the frame exceeds the visible + // area, flooding the scrollback (notably in tmux). + out.Reset() + r.render(NewView(content)) + if err := r.flush(false); err != nil { + t.Fatal(err) + } + if got := out.String(); got != "" { + t.Fatalf("expected no output for unchanged tall frame, got %q", got) + } + + // A changed flush of a tall frame must redraw only the changed lines, + // not the whole frame. + out.Reset() + changed := strings.Repeat("line\n", 29) + "CHANGED\n" + r.render(NewView(changed)) + if err := r.flush(false); err != nil { + t.Fatal(err) + } + got := out.String() + if !strings.Contains(got, "CHANGED") { + t.Fatalf("expected changed line to be rendered, got %q", got) + } + if strings.Contains(got, ansi.EraseScreenBelow) || strings.Contains(got, "line\r\nline") { + t.Fatalf("expected partial redraw for tall frame, got %q", got) + } if err := r.close(); err != nil { t.Fatal(err) } } +// A view without a trailing newline has content on its last row. On exit +// the cursor must move below that row so the closing erase and the shell +// prompt that follows don't overwrite the frame's last line. +func TestCursedRenderer_closeBelowFrameWithoutTrailingNewline(t *testing.T) { + t.Parallel() + + var out bytes.Buffer + r := newCursedRenderer(&out, []string{"TERM=xterm-256color"}, 80, 24) + r.start() + + r.render(NewView("hello")) + if err := r.flush(false); err != nil { + t.Fatal(err) + } + out.Reset() + if err := r.close(); err != nil { + t.Fatal(err) + } + got := out.String() + // The cursor must go one row below the frame's last row before the + // closing erase-below, so the erase (and the shell prompt) start below + // the frame rather than on its last line. + assertInOrder(t, got, "\r\n", ansi.EraseScreenBelow) + if x, y := r.scr.Position(); x != 0 || y != 0 { + t.Fatalf("cursor parked at (%d, %d), want (0, 0)", x, y) + } + + // A view ending in a newline already parks the cursor on the blank row + // below the frame, so no extra newline is needed. + out.Reset() + r.start() + r.render(NewView("hello\n")) + if err := r.flush(false); err != nil { + t.Fatal(err) + } + out.Reset() + if err := r.close(); err != nil { + t.Fatal(err) + } + if got := out.String(); strings.Contains(got, "\r\n"+ansi.EraseScreenBelow) { + t.Fatalf("expected no extra newline before closing erase, got %q", got) + } +} + func TestCursedRenderer_restoresKittyKeyboardStack(t *testing.T) { t.Parallel() diff --git a/testdata/TestClearMsg/bg_fg_cur_color.golden b/testdata/TestClearMsg/bg_fg_cur_color.golden index 9141b90398..b5170031b0 100644 --- a/testdata/TestClearMsg/bg_fg_cur_color.golden +++ b/testdata/TestClearMsg/bg_fg_cur_color.golden @@ -1 +1,2 @@ -[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p]10;?]11;?]12;? \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u +[?25h[?2004l[?2026$p[?2027$p]10;?]11;?]12;? \ No newline at end of file diff --git a/testdata/TestClearMsg/clear_screen.golden b/testdata/TestClearMsg/clear_screen.golden index be12631a28..c52deacc99 100644 --- a/testdata/TestClearMsg/clear_screen.golden +++ b/testdata/TestClearMsg/clear_screen.golden @@ -1 +1,2 @@ -[?25l [?2004h[>4;2m[>1usuccess [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l [?2004h[>4;2m[>1usuccess [>4m[<1u +[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestClearMsg/read_set_clipboard.golden b/testdata/TestClearMsg/read_set_clipboard.golden index 186c8ac68b..2c5f75bdbb 100644 --- a/testdata/TestClearMsg/read_set_clipboard.golden +++ b/testdata/TestClearMsg/read_set_clipboard.golden @@ -1 +1,2 @@ -[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p]52;c;?]52;c;c3VjY2Vzcw== \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u +[?25h[?2004l[?2026$p[?2027$p]52;c;?]52;c;c3VjY2Vzcw== \ No newline at end of file diff --git a/testdata/TestViewModel/altscreen.golden b/testdata/TestViewModel/altscreen.golden index fabd3b7e1a..3a5ef836f4 100644 --- a/testdata/TestViewModel/altscreen.golden +++ b/testdata/TestViewModel/altscreen.golden @@ -1 +1,2 @@ -[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u +[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/bg_set_color.golden b/testdata/TestViewModel/bg_set_color.golden index 61704ec5e6..45b8979957 100644 --- a/testdata/TestViewModel/bg_set_color.golden +++ b/testdata/TestViewModel/bg_set_color.golden @@ -1 +1,2 @@ -[?25l[?2004h[>4;2m[>1u]11;#ffffff success [>4m[<1u[?25h[?2004l]111[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>1u]11;#ffffff success [>4m[<1u +[?25h[?2004l]111[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/bp_stop_start.golden b/testdata/TestViewModel/bp_stop_start.golden index fabd3b7e1a..3a5ef836f4 100644 --- a/testdata/TestViewModel/bp_stop_start.golden +++ b/testdata/TestViewModel/bp_stop_start.golden @@ -1 +1,2 @@ -[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u +[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/cursor_hide.golden b/testdata/TestViewModel/cursor_hide.golden index fabd3b7e1a..3a5ef836f4 100644 --- a/testdata/TestViewModel/cursor_hide.golden +++ b/testdata/TestViewModel/cursor_hide.golden @@ -1 +1,2 @@ -[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u +[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/cursor_hideshow.golden b/testdata/TestViewModel/cursor_hideshow.golden index 12021aee45..23f5de60e5 100644 --- a/testdata/TestViewModel/cursor_hideshow.golden +++ b/testdata/TestViewModel/cursor_hideshow.golden @@ -1 +1,2 @@ -[?2004h[>4;2m[>1u[1 q success [?25h[>4m[<1u[?2004l[?2026$p[?2027$p \ No newline at end of file +[?2004h[>4;2m[>1u[1 q success [?25h[>4m[<1u +[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/kitty_stop_startreleases.golden b/testdata/TestViewModel/kitty_stop_startreleases.golden index db3f43baad..d6b35d522e 100644 --- a/testdata/TestViewModel/kitty_stop_startreleases.golden +++ b/testdata/TestViewModel/kitty_stop_startreleases.golden @@ -1 +1,2 @@ -[?25l[?2004h[>4;2m[>3u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>3u success [>4m[<1u +[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/mouse_allmotion.golden b/testdata/TestViewModel/mouse_allmotion.golden index 66fc56d8c9..d732d9b825 100644 --- a/testdata/TestViewModel/mouse_allmotion.golden +++ b/testdata/TestViewModel/mouse_allmotion.golden @@ -1 +1,2 @@ -[?25l[?2004h[?1003h[?1006h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?1002l[?1003l[?1006l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[?1003h[?1006h[>4;2m[>1u success [>4m[<1u +[?25h[?2004l[?1002l[?1003l[?1006l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/mouse_cellmotion.golden b/testdata/TestViewModel/mouse_cellmotion.golden index d814d128ed..0b0f139a2d 100644 --- a/testdata/TestViewModel/mouse_cellmotion.golden +++ b/testdata/TestViewModel/mouse_cellmotion.golden @@ -1 +1,2 @@ -[?25l[?2004h[?1002h[?1006h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?1002l[?1003l[?1006l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[?1002h[?1006h[>4;2m[>1u success [>4m[<1u +[?25h[?2004l[?1002l[?1003l[?1006l[?2026$p[?2027$p \ No newline at end of file diff --git a/testdata/TestViewModel/mouse_disable.golden b/testdata/TestViewModel/mouse_disable.golden index fabd3b7e1a..3a5ef836f4 100644 --- a/testdata/TestViewModel/mouse_disable.golden +++ b/testdata/TestViewModel/mouse_disable.golden @@ -1 +1,2 @@ -[?25l[?2004h[>4;2m[>1u success [>4m[<1u[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file +[?25l[?2004h[>4;2m[>1u success [>4m[<1u +[?25h[?2004l[?2026$p[?2027$p \ No newline at end of file