Commit 9575587
Eric Bower
·
2026-08-19 14:05:45 -0400 EDT
parent d201ba3
Revert "feat: term command to print terminal state as json" This reverts commit df3d8a22a6c65a0fd8b63cfaadb4c200a0dcb1cc.
6 files changed,
+11,
-371
+0,
-1
| ... | ... | @@ -112,7 +112,6 @@ Commands: | |
| 112 | 112 | set <name> k=v ... Set session labels | |
| 113 | 113 | [un]set <name> key ... Remove session labels | |
| 114 | 114 | [cl]ear <name> Clear all session labels | |
| 115 | - | term <name> Get terminal state as JSON | |
| 116 | 115 | [k]ill <name>... [--force] Kill session and all attached clients | |
| 117 | 116 | [hi]story <name> [--vt|--html] Output session scrollback | |
| 118 | 117 | [w]ait <name>... Wait for session tasks to complete |
+4,
-10
| ... | ... | @@ -32,7 +32,7 @@ const bash_completions = | |
| 32 | 32 | \\ cur="${COMP_WORDS[COMP_CWORD]}" | |
| 33 | 33 | \\ prev="${COMP_WORDS[COMP_CWORD-1]}" | |
| 34 | 34 | \\ | |
| 35 | - | \\ local commands="attach run send print write detach list kill history get set clear term wait tail completions version help" | |
| 35 | + | \\ local commands="attach run send print write detach list kill history get set clear wait tail completions version help" | |
| 36 | 36 | \\ | |
| 37 | 37 | \\ if [[ $COMP_CWORD -eq 1 ]]; then | |
| 38 | 38 | \\ COMPREPLY=($(compgen -W "$commands" -- "$cur")) |
| ... | ... | @@ -40,7 +40,7 @@ const bash_completions = | |
| 40 | 40 | \\ fi | |
| 41 | 41 | \\ | |
| 42 | 42 | \\ case "$prev" in | |
| 43 | - | \\ attach|run|send|print|write|kill|history|get|set|clear|term|wait|tail) | |
| 43 | + | \\ attach|run|send|print|write|kill|history|get|set|clear|wait|tail) | |
| 44 | 44 | \\ local sessions=$(zmx list --short 2>/dev/null | tr '\n' ' ') | |
| 45 | 45 | \\ COMPREPLY=($(compgen -W "$sessions" -- "$cur")) | |
| 46 | 46 | \\ ;; |
| ... | ... | @@ -89,7 +89,6 @@ const zsh_completions = | |
| 89 | 89 | \\ 'get:Get session labels' | |
| 90 | 90 | \\ 'set:Set session labels' | |
| 91 | 91 | \\ 'clear:Clear all session labels' | |
| 92 | - | \\ 'term:Get terminal state as JSON' | |
| 93 | 92 | \\ 'version:Show version' | |
| 94 | 93 | \\ 'help:Show help message' | |
| 95 | 94 | \\ ) |
| ... | ... | @@ -97,7 +96,7 @@ const zsh_completions = | |
| 97 | 96 | \\ ;; | |
| 98 | 97 | \\ args) | |
| 99 | 98 | \\ case $words[2] in | |
| 100 | - | \\ attach|a|kill|k|run|r|send|s|print|p|write|wr|history|get|g|set|clear|term|hi|wait|w|tail|t) | |
| 99 | + | \\ attach|a|kill|k|run|r|send|s|print|p|write|wr|history|get|g|set|clear|hi|wait|w|tail|t) | |
| 101 | 100 | \\ _zmx_sessions | |
| 102 | 101 | \\ ;; | |
| 103 | 102 | \\ completions|c) |
| ... | ... | @@ -152,11 +151,10 @@ const fish_completions = | |
| 152 | 151 | \\complete -c zmx -n "__fish_is_nth_token 1" -a get -d 'Get session labels' | |
| 153 | 152 | \\complete -c zmx -n "__fish_is_nth_token 1" -a set -d 'Set session labels' | |
| 154 | 153 | \\complete -c zmx -n "__fish_is_nth_token 1" -a clear -d 'Clear all session labels' | |
| 155 | - | \\complete -c zmx -n "__fish_is_nth_token 1" -a term -d 'Get terminal state as JSON' | |
| 156 | 154 | \\complete -c zmx -n "__fish_is_nth_token 1" -a help -d 'Show help message' | |
| 157 | 155 | \\ | |
| 158 | 156 | \\# Complete session names and shells | |
| 159 | - | \\complete -c zmx -n "__fish_is_nth_token 2; and __fish_seen_subcommand_from a attach r run s send p print wr write hi history g get se set cl clear term" -a '(zmx list --short 2>/dev/null)' -d 'Session name' | |
| 157 | + | \\complete -c zmx -n "__fish_is_nth_token 2; and __fish_seen_subcommand_from a attach r run s send p print wr write hi history g get se set cl clear" -a '(zmx list --short 2>/dev/null)' -d 'Session name' | |
| 160 | 158 | \\complete -c zmx -n "not __fish_is_nth_token 1; and __fish_seen_subcommand_from k kill w wait t tail" -a '(zmx list --short 2>/dev/null)' -d 'Session name' | |
| 161 | 159 | \\ | |
| 162 | 160 | \\complete -c zmx -n "__fish_is_nth_token 2; and __fish_seen_subcommand_from c completions" -a 'bash zsh fish nu' -d Shell |
| ... | ... | @@ -232,9 +230,5 @@ const nu_completions = | |
| 232 | 230 | \\ name?: string@"nu-complete zmx sessions" | |
| 233 | 231 | \\] | |
| 234 | 232 | \\ | |
| 235 | - | \\export extern "zmx term" [ | |
| 236 | - | \\ name?: string@"nu-complete zmx sessions" | |
| 237 | - | \\] | |
| 238 | - | \\ | |
| 239 | 233 | \\export extern "zmx help" [] | |
| 240 | 234 | ; |
+6,
-13
| ... | ... | @@ -23,8 +23,6 @@ pub const Tag = enum(u8) { | |
| 23 | 23 | LabelClear = 16, | |
| 24 | 24 | LabelData = 17, | |
| 25 | 25 | Send = 18, | |
| 26 | - | TermGet = 19, | |
| 27 | - | TermData = 20, | |
| 28 | 26 | // Non-exhaustive: this enum comes off the wire via bytesToValue and | |
| 29 | 27 | // @enumFromInt, so out-of-range values are representable | |
| 30 | 28 | // rather than UB. Switches must handle `_` (unknown tag). |
| ... | ... | @@ -322,7 +320,7 @@ test "Tag wire values are frozen" { | |
| 322 | 320 | .{ Tag.Run, 9 }, .{ Tag.Ack, 10 }, .{ Tag.Switch, 11 }, | |
| 323 | 321 | .{ Tag.Write, 12 }, .{ Tag.TaskComplete, 13 }, .{ Tag.LabelGet, 14 }, | |
| 324 | 322 | .{ Tag.LabelSet, 15 }, .{ Tag.LabelClear, 16 }, .{ Tag.LabelData, 17 }, | |
| 325 | - | .{ Tag.Send, 18 }, .{ Tag.TermGet, 19 }, .{ Tag.TermData, 20 }, | |
| 323 | + | .{ Tag.Send, 18 }, | |
| 326 | 324 | }) |p| try std.testing.expectEqual(@as(u8, p[1]), @intFromEnum(p[0])); | |
| 327 | 325 | } | |
| 328 | 326 |
| ... | ... | @@ -346,18 +344,13 @@ pub fn roundTripForTag( | |
| 346 | 344 | var sb = SocketBuffer.init(alloc) catch return error.Unexpected; | |
| 347 | 345 | defer sb.deinit(); | |
| 348 | 346 | ||
| 349 | - | while (true) { | |
| 350 | - | const n = sb.read(fd) catch return error.Unexpected; | |
| 351 | - | if (n == 0) return error.Unexpected; | |
| 347 | + | const n = sb.read(fd) catch return error.Unexpected; | |
| 348 | + | if (n == 0) return error.Unexpected; | |
| 352 | 349 | ||
| 353 | - | while (sb.next()) |msg| { | |
| 354 | - | if (msg.header.tag == expected_tag) { | |
| 355 | - | return alloc.dupe(u8, msg.payload) catch return error.Unexpected; | |
| 356 | - | } | |
| 350 | + | while (sb.next()) |msg| { | |
| 351 | + | if (msg.header.tag == expected_tag) { | |
| 352 | + | return alloc.dupe(u8, msg.payload) catch return error.Unexpected; | |
| 357 | 353 | } | |
| 358 | - | ||
| 359 | - | const more = lib_posix.poll(&poll_fds, timeout_ms) catch break; | |
| 360 | - | if (more == 0) return error.Timeout; | |
| 361 | 354 | } | |
| 362 | 355 | return error.Unexpected; | |
| 363 | 356 | } |
+1,
-18
| ... | ... | @@ -11,7 +11,6 @@ const Cfg = @import("cfg.zig"); | |
| 11 | 11 | const signal = @import("signal.zig"); | |
| 12 | 12 | const assert = std.debug.assert; | |
| 13 | 13 | const daemonize = @import("daemonize.zig"); | |
| 14 | - | const term_mod = @import("term.zig"); | |
| 15 | 14 | const builtin = @import("builtin"); | |
| 16 | 15 | ||
| 17 | 16 | /// clientLoop sends ipc commands to its corresponding daemon. It uses poll() as its non-blocking |
| ... | ... | @@ -474,10 +473,9 @@ fn daemonLoop(daemon: *Daemon, gpa: std.mem.Allocator, io: std.Io, server_sock_f | |
| 474 | 473 | .LabelGet => try daemon.handleLabelGet(gpa, client), | |
| 475 | 474 | .LabelSet => try daemon.handleLabelSet(gpa, client, msg.payload), | |
| 476 | 475 | .LabelClear => try daemon.handleLabelClear(gpa, client), | |
| 477 | - | .TermGet => try daemon.handleTermGet(gpa, client, &term), | |
| 478 | 476 | .History => try daemon.handleHistory(gpa, client, &term, msg.payload), | |
| 479 | 477 | .Run => try daemon.handleRun(gpa, io, client, msg.payload), | |
| 480 | - | .Ack, .TaskComplete, .LabelData, .TermData => {}, | |
| 478 | + | .Ack, .TaskComplete, .LabelData => {}, | |
| 481 | 479 | .Write => try daemon.handleWrite(gpa, client, msg.payload), | |
| 482 | 480 | _ => std.log.warn( | |
| 483 | 481 | "ignoring unknown IPC tag={d}", |
| ... | ... | @@ -1304,21 +1302,6 @@ pub const Daemon = struct { | |
| 1304 | 1302 | try ipc.appendMessage(gpa, &client.write_buf, .Ack, ""); | |
| 1305 | 1303 | client.has_pending_output = true; | |
| 1306 | 1304 | } | |
| 1307 | - | ||
| 1308 | - | fn handleTermGet(self: *Daemon, gpa: std.mem.Allocator, client: *Client, term: *ghostty_vt.Terminal) !void { | |
| 1309 | - | self.setPwd(term); | |
| 1310 | - | var info = std.mem.zeroes(ipc.Info); | |
| 1311 | - | info.clients_len = if (self.clients.items.len > 0) self.clients.items.len - 1 else 0; | |
| 1312 | - | info.pid = self.pid; | |
| 1313 | - | info.created_at = self.created_at; | |
| 1314 | - | info.task_ended_at = self.task_ended_at orelse 0; | |
| 1315 | - | info.task_exit_code = self.task_exit_code orelse 0; | |
| 1316 | - | ||
| 1317 | - | const json = try term_mod.dumpState(gpa, term, &info); | |
| 1318 | - | defer gpa.free(json); | |
| 1319 | - | try ipc.appendMessage(gpa, &client.write_buf, .TermData, json); | |
| 1320 | - | client.has_pending_output = true; | |
| 1321 | - | } | |
| 1322 | 1305 | }; | |
| 1323 | 1306 | ||
| 1324 | 1307 | test "send queues PTY input without changing leader" { |
+0,
-48
| ... | ... | @@ -91,12 +91,6 @@ pub fn main(init: std.process.Init) !void { | |
| 91 | 91 | const sesh = try socket.resolveSessionOrEnv(gpa, io, sesh_name); | |
| 92 | 92 | defer gpa.free(sesh); | |
| 93 | 93 | return labelClear(gpa, io, &cfg, sesh); | |
| 94 | - | } else if (std.mem.eql(u8, cmd, "term")) { | |
| 95 | - | const sesh_name = args.next() orelse return error.SessionNameRequired; | |
| 96 | - | if (detectHelp(sesh_name)) return help(io); | |
| 97 | - | const sesh = try socket.resolveSessionOrEnv(gpa, io, sesh_name); | |
| 98 | - | defer gpa.free(sesh); | |
| 99 | - | return termGet(gpa, io, &cfg, sesh); | |
| 100 | 94 | } else if (std.mem.eql(u8, cmd, "completions") or std.mem.eql(u8, cmd, "c")) { | |
| 101 | 95 | const arg = args.next() orelse return; | |
| 102 | 96 | if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { |
| ... | ... | @@ -427,7 +421,6 @@ fn help(io: std.Io) !void { | |
| 427 | 421 | \\ [g]et <name> Get session labels | |
| 428 | 422 | \\ set <name> k=v ... Set session labels (k= to remove) | |
| 429 | 423 | \\ [cl]ear <name> Clear all session labels | |
| 430 | - | \\ term <name> Get terminal state as JSON | |
| 431 | 424 | \\ [k]ill <name>... [--force] Kill session and all attached clients | |
| 432 | 425 | \\ [hi]story <name> [--vt|--html] Output session scrollback | |
| 433 | 426 | \\ [w]ait <name>... Wait for session tasks to complete |
| ... | ... | @@ -1136,47 +1129,6 @@ fn labelClear(alloc: std.mem.Allocator, io: std.Io, cfg: *Cfg, session_name: []c | |
| 1136 | 1129 | }; | |
| 1137 | 1130 | } | |
| 1138 | 1131 | ||
| 1139 | - | fn printTermError(io: std.Io, session_name: []const u8, err: anyerror) noreturn { | |
| 1140 | - | var buf: [4096]u8 = undefined; | |
| 1141 | - | var w = std.Io.File.stderr().writer(io, &buf); | |
| 1142 | - | switch (err) { | |
| 1143 | - | error.Timeout => w.interface.print( | |
| 1144 | - | "error: session \"{s}\" does not support term commands (daemon too old?)\n", | |
| 1145 | - | .{session_name}, | |
| 1146 | - | ) catch {}, | |
| 1147 | - | error.ConnectionRefused, error.Unexpected => w.interface.print( | |
| 1148 | - | "error: session \"{s}\" not found or unresponsive\n", | |
| 1149 | - | .{session_name}, | |
| 1150 | - | ) catch {}, | |
| 1151 | - | else => w.interface.print( | |
| 1152 | - | "error: {s}\n", | |
| 1153 | - | .{@errorName(err)}, | |
| 1154 | - | ) catch {}, | |
| 1155 | - | } | |
| 1156 | - | w.interface.flush() catch {}; | |
| 1157 | - | std.process.exit(1); | |
| 1158 | - | } | |
| 1159 | - | ||
| 1160 | - | fn termGet(alloc: std.mem.Allocator, io: std.Io, cfg: *Cfg, session_name: []const u8) !void { | |
| 1161 | - | std.log.info("term get session={s}", .{session_name}); | |
| 1162 | - | ||
| 1163 | - | const socket_path = socket.getSocketPath(alloc, cfg.socket_dir, session_name) catch |err| switch (err) { | |
| 1164 | - | error.NameTooLong => return socket.printSessionNameTooLong(io, session_name, cfg.socket_dir), | |
| 1165 | - | error.OutOfMemory => return err, | |
| 1166 | - | }; | |
| 1167 | - | defer alloc.free(socket_path); | |
| 1168 | - | ||
| 1169 | - | const payload = ipc.roundTripForTag(alloc, socket_path, .TermGet, "", .TermData) catch |err| { | |
| 1170 | - | printTermError(io, session_name, err); | |
| 1171 | - | }; | |
| 1172 | - | defer alloc.free(payload); | |
| 1173 | - | ||
| 1174 | - | var buf: [4096]u8 = undefined; | |
| 1175 | - | var stdout = std.Io.File.stdout().writer(io, &buf); | |
| 1176 | - | try stdout.interface.print("{s}", .{payload}); | |
| 1177 | - | try stdout.interface.flush(); | |
| 1178 | - | } | |
| 1179 | - | ||
| 1180 | 1132 | /// Fetch terminal history from a session socket, returning it as an allocated | |
| 1181 | 1133 | /// string. Caller owns the returned memory and must free it. | |
| 1182 | 1134 | fn fetchHistory( |
+0,
-281
| ... | ... | @@ -1,281 +0,0 @@ | |
| 1 | - | const std = @import("std"); | |
| 2 | - | const ghostty_vt = @import("ghostty-vt"); | |
| 3 | - | const ipc = @import("ipc.zig"); | |
| 4 | - | const Allocator = std.mem.Allocator; | |
| 5 | - | ||
| 6 | - | pub fn dumpState(alloc: Allocator, term: *const ghostty_vt.Terminal, daemon_info: ?*const ipc.Info) ![]u8 { | |
| 7 | - | var out: std.Io.Writer.Allocating = .init(alloc); | |
| 8 | - | defer out.deinit(); | |
| 9 | - | ||
| 10 | - | var jw: std.json.Stringify = .{ | |
| 11 | - | .writer = &out.writer, | |
| 12 | - | .options = .{ .whitespace = .indent_2 }, | |
| 13 | - | }; | |
| 14 | - | ||
| 15 | - | try jw.beginObject(); | |
| 16 | - | ||
| 17 | - | // meta | |
| 18 | - | try jw.objectField("meta"); | |
| 19 | - | try jw.beginObject(); | |
| 20 | - | try jw.objectField("title"); | |
| 21 | - | try jw.write(term.getTitle() orelse ""); | |
| 22 | - | try jw.objectField("pwd"); | |
| 23 | - | try jw.write(term.getPwd() orelse ""); | |
| 24 | - | if (daemon_info) |info| { | |
| 25 | - | try jw.objectField("pid"); | |
| 26 | - | try jw.write(info.pid); | |
| 27 | - | try jw.objectField("clients"); | |
| 28 | - | try jw.write(info.clients_len); | |
| 29 | - | } | |
| 30 | - | try jw.endObject(); | |
| 31 | - | ||
| 32 | - | // geometry | |
| 33 | - | try jw.objectField("geometry"); | |
| 34 | - | try jw.beginObject(); | |
| 35 | - | try jw.objectField("rows"); | |
| 36 | - | try jw.write(term.rows); | |
| 37 | - | try jw.objectField("cols"); | |
| 38 | - | try jw.write(term.cols); | |
| 39 | - | try jw.objectField("width_px"); | |
| 40 | - | try jw.write(term.width_px); | |
| 41 | - | try jw.objectField("height_px"); | |
| 42 | - | try jw.write(term.height_px); | |
| 43 | - | try jw.objectField("scrolling_region"); | |
| 44 | - | try jw.beginObject(); | |
| 45 | - | try jw.objectField("top"); | |
| 46 | - | try jw.write(term.scrolling_region.top); | |
| 47 | - | try jw.objectField("bottom"); | |
| 48 | - | try jw.write(term.scrolling_region.bottom); | |
| 49 | - | try jw.endObject(); | |
| 50 | - | try jw.endObject(); | |
| 51 | - | ||
| 52 | - | // cursor | |
| 53 | - | const active_screen = term.screens.active; | |
| 54 | - | try jw.objectField("cursor"); | |
| 55 | - | try jw.beginObject(); | |
| 56 | - | try jw.objectField("x"); | |
| 57 | - | try jw.write(active_screen.cursor.x); | |
| 58 | - | try jw.objectField("y"); | |
| 59 | - | try jw.write(active_screen.cursor.y); | |
| 60 | - | try jw.objectField("style"); | |
| 61 | - | try jw.write(@tagName(active_screen.cursor.cursor_style)); | |
| 62 | - | try jw.objectField("blinking"); | |
| 63 | - | try jw.write(term.modes.get(.cursor_blinking)); | |
| 64 | - | try jw.objectField("visible"); | |
| 65 | - | try jw.write(term.modes.get(.cursor_visible)); | |
| 66 | - | try jw.endObject(); | |
| 67 | - | ||
| 68 | - | // colors | |
| 69 | - | try jw.objectField("colors"); | |
| 70 | - | try jw.beginObject(); | |
| 71 | - | if (term.colors.background.get()) |bg| { | |
| 72 | - | var buf: [7]u8 = undefined; | |
| 73 | - | const hex = std.fmt.bufPrint(&buf, "#{x:0>2}{x:0>2}{x:0>2}", .{ bg.r, bg.g, bg.b }) catch unreachable; | |
| 74 | - | try jw.objectField("background"); | |
| 75 | - | try jw.write(hex); | |
| 76 | - | try jw.objectField("appearance"); | |
| 77 | - | const lum = @as(f32, @floatFromInt(bg.r)) * 0.299 + | |
| 78 | - | @as(f32, @floatFromInt(bg.g)) * 0.587 + | |
| 79 | - | @as(f32, @floatFromInt(bg.b)) * 0.114; | |
| 80 | - | if (lum < 128.0) { | |
| 81 | - | try jw.write("dark"); | |
| 82 | - | } else { | |
| 83 | - | try jw.write("light"); | |
| 84 | - | } | |
| 85 | - | } else { | |
| 86 | - | try jw.objectField("background"); | |
| 87 | - | try jw.write(null); | |
| 88 | - | try jw.objectField("appearance"); | |
| 89 | - | try jw.write("dark"); | |
| 90 | - | } | |
| 91 | - | ||
| 92 | - | if (term.colors.foreground.get()) |fg| { | |
| 93 | - | var buf: [7]u8 = undefined; | |
| 94 | - | const hex = std.fmt.bufPrint(&buf, "#{x:0>2}{x:0>2}{x:0>2}", .{ fg.r, fg.g, fg.b }) catch unreachable; | |
| 95 | - | try jw.objectField("foreground"); | |
| 96 | - | try jw.write(hex); | |
| 97 | - | } else { | |
| 98 | - | try jw.objectField("foreground"); | |
| 99 | - | try jw.write(null); | |
| 100 | - | } | |
| 101 | - | ||
| 102 | - | if (term.colors.cursor.get()) |c| { | |
| 103 | - | var buf: [7]u8 = undefined; | |
| 104 | - | const hex = std.fmt.bufPrint(&buf, "#{x:0>2}{x:0>2}{x:0>2}", .{ c.r, c.g, c.b }) catch unreachable; | |
| 105 | - | try jw.objectField("cursor"); | |
| 106 | - | try jw.write(hex); | |
| 107 | - | } else { | |
| 108 | - | try jw.objectField("cursor"); | |
| 109 | - | try jw.write(null); | |
| 110 | - | } | |
| 111 | - | ||
| 112 | - | // palette | |
| 113 | - | try jw.objectField("palette"); | |
| 114 | - | try jw.beginArray(); | |
| 115 | - | for (term.colors.palette.current) |c| { | |
| 116 | - | var buf: [7]u8 = undefined; | |
| 117 | - | const hex = std.fmt.bufPrint(&buf, "#{x:0>2}{x:0>2}{x:0>2}", .{ c.r, c.g, c.b }) catch unreachable; | |
| 118 | - | try jw.write(hex); | |
| 119 | - | } | |
| 120 | - | try jw.endArray(); | |
| 121 | - | try jw.endObject(); | |
| 122 | - | ||
| 123 | - | // modes | |
| 124 | - | try jw.objectField("modes"); | |
| 125 | - | try jw.beginObject(); | |
| 126 | - | try jw.objectField("alt_screen"); | |
| 127 | - | try jw.write(term.screens.active_key == .alternate); | |
| 128 | - | inline for (std.meta.fields(ghostty_vt.modes.Mode)) |f| { | |
| 129 | - | const mode = @field(ghostty_vt.modes.Mode, f.name); | |
| 130 | - | try jw.objectField(f.name); | |
| 131 | - | try jw.write(term.modes.get(mode)); | |
| 132 | - | } | |
| 133 | - | try jw.endObject(); | |
| 134 | - | ||
| 135 | - | // mouse | |
| 136 | - | try jw.objectField("mouse"); | |
| 137 | - | try jw.beginObject(); | |
| 138 | - | try jw.objectField("shape"); | |
| 139 | - | try jw.write(@tagName(term.mouse_shape)); | |
| 140 | - | try jw.objectField("event"); | |
| 141 | - | try jw.write(@tagName(term.flags.mouse_event)); | |
| 142 | - | try jw.objectField("format"); | |
| 143 | - | try jw.write(@tagName(term.flags.mouse_format)); | |
| 144 | - | try jw.objectField("shift_capture"); | |
| 145 | - | try jw.write(@tagName(term.flags.mouse_shift_capture)); | |
| 146 | - | try jw.endObject(); | |
| 147 | - | ||
| 148 | - | // keyboard | |
| 149 | - | try jw.objectField("keyboard"); | |
| 150 | - | try jw.beginObject(); | |
| 151 | - | const kflags = active_screen.kitty_keyboard.current(); | |
| 152 | - | try jw.objectField("kitty_flags"); | |
| 153 | - | try jw.write(kflags.int()); | |
| 154 | - | try jw.objectField("kitty_disambiguate"); | |
| 155 | - | try jw.write(kflags.disambiguate); | |
| 156 | - | try jw.objectField("kitty_report_events"); | |
| 157 | - | try jw.write(kflags.report_events); | |
| 158 | - | try jw.objectField("kitty_report_alternates"); | |
| 159 | - | try jw.write(kflags.report_alternates); | |
| 160 | - | try jw.objectField("kitty_report_all"); | |
| 161 | - | try jw.write(kflags.report_all); | |
| 162 | - | try jw.objectField("kitty_report_associated"); | |
| 163 | - | try jw.write(kflags.report_associated); | |
| 164 | - | try jw.objectField("modify_other_keys_2"); | |
| 165 | - | try jw.write(term.flags.modify_other_keys_2); | |
| 166 | - | try jw.endObject(); | |
| 167 | - | ||
| 168 | - | // buffer | |
| 169 | - | try jw.objectField("buffer"); | |
| 170 | - | try jw.beginObject(); | |
| 171 | - | var total_rows: usize = 0; | |
| 172 | - | var page_it = active_screen.pages.pages.first; | |
| 173 | - | while (page_it) |node| : (page_it = node.next) { | |
| 174 | - | total_rows += node.rows(); | |
| 175 | - | } | |
| 176 | - | const scrollback_rows = total_rows - @min(total_rows, @as(usize, active_screen.pages.rows)); | |
| 177 | - | try jw.objectField("scrollback_rows"); | |
| 178 | - | try jw.write(scrollback_rows); | |
| 179 | - | try jw.objectField("has_saved_cursor"); | |
| 180 | - | try jw.write(active_screen.saved_cursor != null); | |
| 181 | - | if (active_screen.saved_cursor) |sc| { | |
| 182 | - | try jw.objectField("saved_cursor"); | |
| 183 | - | try jw.beginObject(); | |
| 184 | - | try jw.objectField("x"); | |
| 185 | - | try jw.write(sc.x); | |
| 186 | - | try jw.objectField("y"); | |
| 187 | - | try jw.write(sc.y); | |
| 188 | - | try jw.objectField("origin"); | |
| 189 | - | try jw.write(sc.origin); | |
| 190 | - | try jw.objectField("pending_wrap"); | |
| 191 | - | try jw.write(sc.pending_wrap); | |
| 192 | - | try jw.endObject(); | |
| 193 | - | } | |
| 194 | - | try jw.objectField("has_selection"); | |
| 195 | - | try jw.write(active_screen.selection != null); | |
| 196 | - | try jw.objectField("protected_mode"); | |
| 197 | - | try jw.write(@tagName(active_screen.protected_mode)); | |
| 198 | - | try jw.endObject(); | |
| 199 | - | ||
| 200 | - | // shell_integration | |
| 201 | - | try jw.objectField("shell_integration"); | |
| 202 | - | try jw.beginObject(); | |
| 203 | - | try jw.objectField("semantic_prompt"); | |
| 204 | - | try jw.write(active_screen.semantic_prompt.seen); | |
| 205 | - | try jw.objectField("shell_redraws_prompt"); | |
| 206 | - | try jw.write(@tagName(term.flags.shell_redraws_prompt)); | |
| 207 | - | try jw.objectField("password_input"); | |
| 208 | - | try jw.write(term.flags.password_input); | |
| 209 | - | try jw.endObject(); | |
| 210 | - | ||
| 211 | - | // tabstops | |
| 212 | - | try jw.objectField("tabstops"); | |
| 213 | - | try jw.beginArray(); | |
| 214 | - | var col: usize = 0; | |
| 215 | - | while (col < term.cols) : (col += 1) { | |
| 216 | - | if (term.tabstops.get(col)) { | |
| 217 | - | try jw.write(col); | |
| 218 | - | } | |
| 219 | - | } | |
| 220 | - | try jw.endArray(); | |
| 221 | - | ||
| 222 | - | // charsets | |
| 223 | - | try jw.objectField("charsets"); | |
| 224 | - | try jw.beginObject(); | |
| 225 | - | try jw.objectField("g0"); | |
| 226 | - | try jw.write(@tagName(active_screen.charset.charsets.g0)); | |
| 227 | - | try jw.objectField("g1"); | |
| 228 | - | try jw.write(@tagName(active_screen.charset.charsets.g1)); | |
| 229 | - | try jw.objectField("g2"); | |
| 230 | - | try jw.write(@tagName(active_screen.charset.charsets.g2)); | |
| 231 | - | try jw.objectField("g3"); | |
| 232 | - | try jw.write(@tagName(active_screen.charset.charsets.g3)); | |
| 233 | - | try jw.objectField("gl"); | |
| 234 | - | try jw.write(@tagName(active_screen.charset.gl)); | |
| 235 | - | try jw.objectField("gr"); | |
| 236 | - | try jw.write(@tagName(active_screen.charset.gr)); | |
| 237 | - | try jw.endObject(); | |
| 238 | - | ||
| 239 | - | // flags | |
| 240 | - | try jw.objectField("flags"); | |
| 241 | - | try jw.beginObject(); | |
| 242 | - | try jw.objectField("focused"); | |
| 243 | - | try jw.write(term.flags.focused); | |
| 244 | - | try jw.endObject(); | |
| 245 | - | ||
| 246 | - | try jw.endObject(); | |
| 247 | - | ||
| 248 | - | try out.writer.writeAll("\n"); | |
| 249 | - | return alloc.dupe(u8, out.written()); | |
| 250 | - | } | |
| 251 | - | ||
| 252 | - | test "dumpState generates valid json with terminal state" { | |
| 253 | - | const testing = std.testing; | |
| 254 | - | const alloc = testing.allocator; | |
| 255 | - | const io = testing.io; | |
| 256 | - | ||
| 257 | - | var term = try ghostty_vt.Terminal.init(io, alloc, .{ | |
| 258 | - | .cols = 80, | |
| 259 | - | .rows = 24, | |
| 260 | - | }); | |
| 261 | - | defer term.deinit(alloc); | |
| 262 | - | ||
| 263 | - | try term.setTitle("my window"); | |
| 264 | - | try term.setPwd("/home/user"); | |
| 265 | - | ||
| 266 | - | const json = try dumpState(alloc, &term, null); | |
| 267 | - | defer alloc.free(json); | |
| 268 | - | ||
| 269 | - | try testing.expect(std.mem.indexOf(u8, json, "\"title\": \"my window\"") != null); | |
| 270 | - | try testing.expect(std.mem.indexOf(u8, json, "\"pwd\": \"/home/user\"") != null); | |
| 271 | - | try testing.expect(std.mem.indexOf(u8, json, "\"rows\": 24") != null); | |
| 272 | - | try testing.expect(std.mem.indexOf(u8, json, "\"cols\": 80") != null); | |
| 273 | - | try testing.expect(std.mem.indexOf(u8, json, "\"cursor\"") != null); | |
| 274 | - | try testing.expect(std.mem.indexOf(u8, json, "\"colors\"") != null); | |
| 275 | - | try testing.expect(std.mem.indexOf(u8, json, "\"modes\"") != null); | |
| 276 | - | try testing.expect(std.mem.indexOf(u8, json, "\"keyboard\"") != null); | |
| 277 | - | try testing.expect(std.mem.indexOf(u8, json, "\"buffer\"") != null); | |
| 278 | - | try testing.expect(std.mem.indexOf(u8, json, "\"shell_integration\"") != null); | |
| 279 | - | try testing.expect(std.mem.indexOf(u8, json, "\"tabstops\"") != null); | |
| 280 | - | try testing.expect(std.mem.indexOf(u8, json, "\"charsets\"") != null); | |
| 281 | - | } |