- commit
- 6f881c7
- parent
- 7bb755b
- author
- Eric Bower
- date
- 2025-10-15 14:51:21 -0400 EDT
chore: cursor position and escape sequences
1 files changed,
+56,
-6
+56,
-6
1@@ -27,12 +27,19 @@ fn extractCellText(pin: ghostty.Pin, cell: *const ghostty.Cell, buf: *std.ArrayL
2 }
3 }
4
5-/// Render the current terminal viewport state as text (with escape sequences to come)
6+/// Render the current terminal viewport state as text with proper escape sequences
7 /// Returns owned slice that must be freed by caller
8 pub fn render(vt: *ghostty.Terminal, allocator: std.mem.Allocator) ![]u8 {
9 var output = try std.ArrayList(u8).initCapacity(allocator, 4096);
10 errdefer output.deinit(allocator);
11
12+ // Prepare terminal: hide cursor, reset scroll region, reset SGR, clear screen, home cursor
13+ try output.appendSlice(allocator, "\x1b[?25l"); // Hide cursor
14+ try output.appendSlice(allocator, "\x1b[r"); // Reset scroll region
15+ try output.appendSlice(allocator, "\x1b[0m"); // Reset SGR (colors/styles)
16+ try output.appendSlice(allocator, "\x1b[2J"); // Clear entire screen
17+ try output.appendSlice(allocator, "\x1b[H"); // Home cursor (1,1)
18+
19 // Get the terminal's page list
20 const pages = &vt.screen.pages;
21
22@@ -42,6 +49,13 @@ pub fn render(vt: *ghostty.Terminal, allocator: std.mem.Allocator) ![]u8 {
23 // Iterate through viewport rows
24 var row_idx: usize = 0;
25 while (row_it.next()) |pin| : (row_idx += 1) {
26+ // Position cursor at the start of this row (1-based indexing)
27+ const row_num = row_idx + 1;
28+ try std.fmt.format(output.writer(allocator), "\x1b[{d};1H", .{row_num});
29+
30+ // Clear the entire line to avoid stale content
31+ try output.appendSlice(allocator, "\x1b[2K");
32+
33 // Get row and cell data from pin
34 const rac = pin.rowAndCell();
35 const row = rac.row;
36@@ -70,14 +84,50 @@ pub fn render(vt: *ghostty.Terminal, allocator: std.mem.Allocator) ![]u8 {
37 col_idx += 1; // Skip the spacer cell that follows
38 }
39 }
40+ }
41+
42+ // Restore cursor position from terminal state
43+ const cursor = vt.screen.cursor;
44+ const cursor_row = cursor.y + 1; // Convert to 1-based
45+ var cursor_col: u16 = @intCast(cursor.x + 1); // Convert to 1-based
46+
47+ // If cursor is at x=0, try to find the actual end of content on that row
48+ // This handles race conditions where the cursor position wasn't updated yet
49+ if (cursor.x == 0) {
50+ const cursor_pin = pages.pin(.{ .active = .{ .x = 0, .y = cursor.y } });
51+ if (cursor_pin) |cpin| {
52+ const crac = cpin.rowAndCell();
53+ const crow = crac.row;
54+ const cpage = &cpin.node.data;
55+ const ccells = cpage.getCells(crow);
56+
57+ // Find the last non-empty cell (including spaces)
58+ var last_col: usize = 0;
59+ var col: usize = 0;
60+ while (col < ccells.len) : (col += 1) {
61+ const cell = &ccells[col];
62+ if (cell.wide == .spacer_tail or cell.wide == .spacer_head) continue;
63+ const cp = cell.codepoint();
64+ if (cp != 0) { // Include spaces, just not null
65+ last_col = col;
66+ }
67+ if (cell.wide == .wide) col += 1;
68+ }
69
70- // Add newline after each row
71- try output.appendSlice(allocator, "\n");
72+ // If we found content, position cursor after the last character
73+ if (last_col > 0) {
74+ cursor_col = @intCast(last_col + 2); // +1 for after character, +1 for 1-based
75+ std.debug.print("Adjusted cursor from x=0 to col={d} (last content at col={d})\n", .{ cursor_col, last_col + 1 });
76+ }
77+ }
78 }
79
80- // TODO: Properly restore scrollback with colors and cursor position
81- // For now, just return the text content
82- // try output.appendSlice(allocator, "\x1b[2J\x1b[H"); // Clear screen and home cursor
83+ std.debug.print("Restoring cursor to row={d} col={d} (original: y={d} x={d})\n", .{ cursor_row, cursor_col, cursor.y, cursor.x });
84+
85+ try std.fmt.format(output.writer(allocator), "\x1b[{d};{d}H", .{ cursor_row, cursor_col });
86+
87+ // Show cursor
88+ try output.appendSlice(allocator, "\x1b[?25h");
89
90 return output.toOwnedSlice(allocator);
91 }