diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-10 21:51:57 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-10 21:51:57 +0300 |
| commit | 425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch) | |
| tree | e52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/terminal/dcs.zig | |
| parent | 90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff) | |
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/terminal/dcs.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/terminal/dcs.zig | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/terminal/dcs.zig b/vendor/libghostty-vt/src/terminal/dcs.zig index 425325d4..a76ab31b 100644 --- a/vendor/libghostty-vt/src/terminal/dcs.zig +++ b/vendor/libghostty-vt/src/terminal/dcs.zig @@ -254,6 +254,63 @@ pub const Command = union(enum) { decscusr, decstbm, decslrm, + + /// Fixed upper bound for an encoded DECRPSS response. The comptime + /// calculated max at the time of writing this was around 63 so this + /// leaves a ton of space for future stuff. We don't do the comptime + /// calculation cause it complicated the implementation a bit too + /// much. + pub const max_response_bytes = 256; + + /// Encode the response for this request. + pub fn encode( + self: DECRQSS, + t: *terminal.Terminal, + response: []u8, + ) ![]const u8 { + var writer: std.Io.Writer = .fixed(response); + + const prefix_fmt = "\x1bP{d}$r"; + const prefix_len = std.fmt.comptimePrint(prefix_fmt, .{0}).len; + writer.end = prefix_len; + + switch (self) { + .none => {}, + .sgr => { + const buf = try t.printAttributes(writer.buffer[writer.end..]); + writer.end += buf.len; + try writer.writeByte('m'); + }, + .decscusr => { + const blink = t.modes.get(.cursor_blinking); + const style: u8 = switch (t.screens.active.cursor.cursor_style) { + .block, .block_hollow => if (blink) 1 else 2, + .underline => if (blink) 3 else 4, + .bar => if (blink) 5 else 6, + }; + try writer.print("{d} q", .{style}); + }, + .decstbm => try writer.print("{d};{d}r", .{ + t.scrolling_region.top + 1, + t.scrolling_region.bottom + 1, + }), + .decslrm => if (t.modes.get(.enable_left_and_right_margin)) { + try writer.print("{d};{d}s", .{ + t.scrolling_region.left + 1, + t.scrolling_region.right + 1, + }); + }, + } + + const valid = writer.end > prefix_len; + try writer.writeAll("\x1b\\"); + _ = try std.fmt.bufPrint( + response[0..prefix_len], + prefix_fmt, + .{@intFromBool(valid)}, + ); + return writer.buffered(); + } }; }; @@ -405,6 +462,90 @@ test "DECRQSS invalid command" { try testing.expect(h.unhook() == null); } +test "DECRQSS response encoding" { + const testing = std.testing; + + var t: terminal.Terminal = try .init( + testing.io, + testing.allocator, + .{ .cols = 80, .rows = 24 }, + ); + defer t.deinit(testing.allocator); + + const S = struct { + fn expectResponse( + term: *terminal.Terminal, + request: Command.DECRQSS, + expected: []const u8, + ) !void { + var buf: [Command.DECRQSS.max_response_bytes]u8 = undefined; + const encoded = try request.encode(term, &buf); + try testing.expectEqualStrings(expected, encoded); + } + }; + + try S.expectResponse(&t, .none, "\x1BP0$r\x1B\\"); + try S.expectResponse(&t, .sgr, "\x1BP1$r0m\x1B\\"); + + try t.setAttribute(.bold); + try t.setAttribute(.{ .underline = .curly }); + try S.expectResponse(&t, .sgr, "\x1BP1$r0;1;4:3m\x1B\\"); + + t.setCursorStyle(.steady_underline); + try S.expectResponse(&t, .decscusr, "\x1BP1$r4 q\x1B\\"); + + t.scrolling_region.top = 4; + t.scrolling_region.bottom = 19; + try S.expectResponse(&t, .decstbm, "\x1BP1$r5;20r\x1B\\"); + + try S.expectResponse(&t, .decslrm, "\x1BP0$r\x1B\\"); + t.modes.set(.enable_left_and_right_margin, true); + t.scrolling_region.left = 2; + t.scrolling_region.right = 69; + try S.expectResponse(&t, .decslrm, "\x1BP1$r3;70s\x1B\\"); +} + +test "DECRQSS largest response fits fixed buffer" { + const testing = std.testing; + + var t: terminal.Terminal = try .init( + testing.io, + testing.allocator, + .{ .cols = 80, .rows = 24 }, + ); + defer t.deinit(testing.allocator); + + try t.setAttribute(.bold); + try t.setAttribute(.faint); + try t.setAttribute(.italic); + try t.setAttribute(.{ .underline = .dashed }); + try t.setAttribute(.blink); + try t.setAttribute(.inverse); + try t.setAttribute(.invisible); + try t.setAttribute(.strikethrough); + try t.setAttribute(.overline); + try t.setAttribute(.{ .direct_color_fg = .{ + .r = 255, + .g = 255, + .b = 255, + } }); + try t.setAttribute(.{ .direct_color_bg = .{ + .r = 255, + .g = 255, + .b = 255, + } }); + + var buf: [Command.DECRQSS.max_response_bytes]u8 = undefined; + const encoded = try Command.DECRQSS.sgr.encode(&t, &buf); + + const expected = + "\x1BP1$r0;1;2;3;4:5;53;5;7;8;9" ++ + ";38:2::255:255:255" ++ + ";48:2::255:255:255m\x1B\\"; + try testing.expectEqualStrings(expected, encoded); + try testing.expect(encoded.len <= Command.DECRQSS.max_response_bytes); +} + test "tmux enter and implicit exit" { if (comptime !build_options.tmux_control_mode) return error.SkipZigTest; |
