- commit
- eaf6c5a
- parent
- 7740eb9
- author
- Eric Bower
- date
- 2025-10-14 09:04:34 -0400 EDT
refactor: renderTerminalSnapshot
3 files changed,
+36,
-35
+1,
-1
1@@ -34,7 +34,7 @@ The protocol uses a hybrid approach: JSON for control messages and binary frames
2 **Current Usage:**
3 - Control messages (attach, detach, kill, etc.): NDJSON format
4 - PTY output from daemon to client: Binary frames (type 2)
5-- PTY input from client to daemon: JSON `pty_in` messages (may be optimized to binary frames in future)
6+- PTY input from client to daemon: Binary frames (type 2)
7
8 ## Message Structure
9
+31,
-30
1@@ -638,7 +638,7 @@ fn handleAttachSession(ctx: *ServerContext, client: *Client, session_name: []con
2
3 // If reattaching, send the scrollback buffer as binary frame
4 if (is_reattach) {
5- const buffer_slice = try session.vt.plainStringUnwrapped(client.allocator);
6+ const buffer_slice = try renderTerminalSnapshot(session, client.allocator);
7 defer client.allocator.free(buffer_slice);
8
9 try protocol.writeBinaryFrame(client.fd, .pty_binary, buffer_slice);
10@@ -738,40 +738,41 @@ fn renderTerminalSnapshot(session: *Session, allocator: std.mem.Allocator) ![]u8
11 const rows = screen.pages.rows;
12 const cols = screen.pages.cols;
13
14- var row: usize = 0;
15- while (row < rows) : (row += 1) {
16- var col: usize = 0;
17- while (col < cols) : (col += 1) {
18- // Build a point.Point referring to the active (visible) page
19- const pt: ghostty.point.Point = .{ .active = .{
20- .x = @as(u16, @intCast(col)),
21- .y = @as(u16, @intCast(row)),
22- } };
23-
24- if (screen.pages.getCell(pt)) |cell_ref| {
25- const cp = cell_ref.cell.content.codepoint;
26- if (cp == 0) {
27- try output.append(allocator, ' ');
28- } else {
29- var buf: [4]u8 = undefined;
30- const len = std.unicode.utf8Encode(cp, &buf) catch 0;
31- if (len == 0) {
32- try output.append(allocator, ' ');
33- } else {
34- try output.appendSlice(allocator, buf[0..len]);
35- }
36- }
37- } else {
38- // Outside bounds or no cell => space to preserve width
39- try output.append(allocator, ' ');
40- }
41- }
42+ // Use cellIterator to walk through the visible viewport
43+ const tl_pt: ghostty.point.Point = screen.pages.getTopLeft(tl);
44+ const br_pt: ghostty.point.Point = screen.pages.getBottomRight(tl);
45+
46+ var it = screen.pages.cellIterator(.right_down, tl_pt, br_pt);
47+ var current_row: u16 = 0;
48
49- if (row < rows - 1) {
50+ while (it.next()) |pin| {
51+ const cell_info = pin.rowAndCell();
52+ const cell = cell_info.cell;
53+
54+ // Check if we've moved to a new row
55+ if (pin.y > current_row) {
56 try output.appendSlice(allocator, "\r\n");
57+ current_row = pin.y;
58+ }
59+
60+ // Write the cell content
61+ const cp = cell.content.codepoint;
62+ if (cp == 0) {
63+ try output.append(allocator, ' ');
64+ } else {
65+ var buf: [4]u8 = undefined;
66+ const len = std.unicode.utf8Encode(cp, &buf) catch 0;
67+ if (len == 0) {
68+ try output.append(allocator, ' ');
69+ } else {
70+ try output.appendSlice(allocator, buf[0..len]);
71+ }
72 }
73 }
74
75+ // Add final newline if needed
76+ try output.appendSlice(allocator, "\r\n");
77+
78 // Position cursor at correct location (ANSI is 1-based)
79 const cursor = screen.cursor;
80 try output.writer(allocator).print("\x1b[{d};{d}H", .{ cursor.y + 1, cursor.x + 1 });
+4,
-4
1@@ -210,11 +210,11 @@ pub const LineBuffer = struct {
2 };
3 };
4
5-// Future: Binary frame support for PTY data
6+// Binary frame support for PTY data
7 // This infrastructure allows us to add binary framing later without breaking existing code
8 pub const FrameType = enum(u16) {
9 json_control = 1, // JSON-encoded control messages (current protocol)
10- pty_binary = 2, // Raw PTY bytes (future optimization)
11+ pty_binary = 2, // Raw PTY bytes
12 };
13
14 pub const FrameHeader = packed struct {
15@@ -222,7 +222,7 @@ pub const FrameHeader = packed struct {
16 frame_type: u16, // little-endian, FrameType value
17 };
18
19-// Future: Helper to write a binary frame (not used yet)
20+// Helper to write a binary frame
21 pub fn writeBinaryFrame(fd: posix.fd_t, frame_type: FrameType, payload: []const u8) !void {
22 const header = FrameHeader{
23 .length = @intCast(payload.len),
24@@ -234,7 +234,7 @@ pub fn writeBinaryFrame(fd: posix.fd_t, frame_type: FrameType, payload: []const
25 _ = try posix.write(fd, payload);
26 }
27
28-// Future: Helper to read a binary frame (not used yet)
29+// Helper to read a binary frame (not used yet)
30 pub fn readBinaryFrame(allocator: std.mem.Allocator, fd: posix.fd_t) !struct { frame_type: FrameType, payload: []u8 } {
31 var header_bytes: [@sizeOf(FrameHeader)]u8 = undefined;
32 const read_len = try posix.read(fd, &header_bytes);