From 3d535f8360f35132faaeef093d61cf377be5e9cc Mon Sep 17 00:00:00 2001 From: t Date: Tue, 9 Jun 2026 10:13:26 -0600 Subject: finalize cursor on quit --- src/tui_engine.zig | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'src/tui_engine.zig') diff --git a/src/tui_engine.zig b/src/tui_engine.zig index c6ddf49..05b0b89 100644 --- a/src/tui_engine.zig +++ b/src/tui_engine.zig @@ -812,6 +812,25 @@ pub const Engine = struct { self.viewport_top = 0; } } + + /// On teardown, move the hardware cursor to the first row AFTER the + /// rendered content so the shell prompt appears below the footer, not at + /// the focused input cursor position we used for IME anchoring. + pub fn finalizeCursor(self: *Engine) Error!void { + if (self.total_lines == 0) { + try self.writer.writeAll(terminal.seq.carriage_return); + self.hw_cursor_row = 0; + return; + } + + const past_content_row = self.total_lines; + const current_row = self.hw_cursor_row; + if (past_content_row > current_row) { + try self.cursorDown(past_content_row - current_row); + } + try self.writer.writeAll(terminal.seq.carriage_return); + self.hw_cursor_row = past_content_row; + } }; // =========================================================================== @@ -1048,6 +1067,27 @@ test "first paint preserves the cursor-resting invariant (no trailing newline)" try testing.expectEqual(@as(usize, 2), std.mem.count(u8, out, "\r\n")); } +test "finalizeCursor moves below the footer before exit" { + var buf = std.Io.Writer.Allocating.init(testing.allocator); + defer buf.deinit(); + var eng = makeEngine(&buf, 80, 24); + defer eng.deinit(); + + var body = FakeComponent{ .scripts = &.{&.{ "welcome", "prompt", "footer" }}, .first_changed = &.{0} }; + try eng.addComponent(body.comp()); + try eng.render(); + + // Simulate the session parking the hardware cursor on the input row while + // footer content still exists below it. + eng.hw_cursor_row = 1; + + buf.clearRetainingCapacity(); + try eng.finalizeCursor(); + + try testing.expectEqualStrings("\x1b[2B\r", buf.written()); + try testing.expectEqual(@as(usize, 3), eng.hw_cursor_row); +} + test "cut is the min across ALL components (ticking footer below clean body)" { var buf = std.Io.Writer.Allocating.init(testing.allocator); defer buf.deinit(); -- cgit v1.3