mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-02 05:39:05 +00:00
lib-vt: add color scheme report encoder (#13192)
Add a shared encoder for CSI ? 997 ; Ps n color scheme reports and use it for both CSI ? 996 n replies and unsolicited Termio reports. Export the same encoder through the libghostty-vt C API with docs and an example. This is a really light API, arguably easy for consumers to hardcode, but it didn't match the rest of our style in the libghostty API so we should expose it.
This commit is contained in:
@@ -191,6 +191,7 @@ comptime {
|
||||
@export(&c.osc_end, .{ .name = "ghostty_osc_end" });
|
||||
@export(&c.osc_command_type, .{ .name = "ghostty_osc_command_type" });
|
||||
@export(&c.osc_command_data, .{ .name = "ghostty_osc_command_data" });
|
||||
@export(&c.color_scheme_report_encode, .{ .name = "ghostty_color_scheme_report_encode" });
|
||||
@export(&c.focus_encode, .{ .name = "ghostty_focus_encode" });
|
||||
@export(&c.mode_report_encode, .{ .name = "ghostty_mode_report_encode" });
|
||||
@export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" });
|
||||
|
||||
63
src/terminal/c/color_scheme.zig
Normal file
63
src/terminal/c/color_scheme.zig
Normal file
@@ -0,0 +1,63 @@
|
||||
const std = @import("std");
|
||||
const lib = @import("../lib.zig");
|
||||
const device_status = @import("../device_status.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
pub fn report_encode(
|
||||
scheme: device_status.ColorScheme,
|
||||
out_: ?[*]u8,
|
||||
out_len: usize,
|
||||
out_written: *usize,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
var writer: std.Io.Writer = .fixed(if (out_) |out| out[0..out_len] else &.{});
|
||||
device_status.encodeColorSchemeReport(&writer, scheme) catch |err| switch (err) {
|
||||
error.WriteFailed => {
|
||||
var discarding: std.Io.Writer.Discarding = .init(&.{});
|
||||
device_status.encodeColorSchemeReport(&discarding.writer, scheme) catch unreachable;
|
||||
out_written.* = @intCast(discarding.count);
|
||||
return .out_of_space;
|
||||
},
|
||||
};
|
||||
|
||||
out_written.* = writer.end;
|
||||
return .success;
|
||||
}
|
||||
|
||||
test "encode color scheme report dark" {
|
||||
var buf: [device_status.max_color_scheme_report_encode_size]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
const result = report_encode(.dark, &buf, buf.len, &written);
|
||||
try std.testing.expectEqual(.success, result);
|
||||
try std.testing.expectEqualStrings("\x1B[?997;1n", buf[0..written]);
|
||||
}
|
||||
|
||||
test "encode color scheme report light" {
|
||||
var buf: [device_status.max_color_scheme_report_encode_size]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
const result = report_encode(.light, &buf, buf.len, &written);
|
||||
try std.testing.expectEqual(.success, result);
|
||||
try std.testing.expectEqualStrings("\x1B[?997;2n", buf[0..written]);
|
||||
}
|
||||
|
||||
test "encode color scheme report with null buffer" {
|
||||
var written: usize = 0;
|
||||
const result = report_encode(.dark, null, 0, &written);
|
||||
try std.testing.expectEqual(.out_of_space, result);
|
||||
try std.testing.expectEqual(@as(usize, 9), written);
|
||||
}
|
||||
|
||||
test "encode color scheme report with insufficient buffer" {
|
||||
var buf: [3]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
const result = report_encode(.light, &buf, buf.len, &written);
|
||||
try std.testing.expectEqual(.out_of_space, result);
|
||||
try std.testing.expectEqual(@as(usize, 9), written);
|
||||
}
|
||||
|
||||
test "encode color scheme report with exact buffer" {
|
||||
var buf: [9]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
const result = report_encode(.dark, &buf, buf.len, &written);
|
||||
try std.testing.expectEqual(.success, result);
|
||||
try std.testing.expectEqual(@as(usize, 9), written);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ const buildpkg = @import("build_info.zig");
|
||||
pub const allocator = @import("allocator.zig");
|
||||
pub const cell = @import("cell.zig");
|
||||
pub const color = @import("color.zig");
|
||||
pub const color_scheme = @import("color_scheme.zig");
|
||||
pub const focus = @import("focus.zig");
|
||||
pub const formatter = @import("formatter.zig");
|
||||
pub const grid_ref = @import("grid_ref.zig");
|
||||
@@ -58,6 +59,8 @@ pub const osc_command_data = osc.commandData;
|
||||
|
||||
pub const color_rgb_get = color.rgb_get;
|
||||
|
||||
pub const color_scheme_report_encode = color_scheme.report_encode;
|
||||
|
||||
pub const focus_encode = focus.encode;
|
||||
|
||||
pub const mode_report_encode = modes.report_encode;
|
||||
@@ -218,6 +221,7 @@ test {
|
||||
_ = buildpkg;
|
||||
_ = cell;
|
||||
_ = color;
|
||||
_ = color_scheme;
|
||||
_ = grid_ref;
|
||||
_ = grid_ref_tracked;
|
||||
_ = kitty_graphics;
|
||||
|
||||
@@ -7,6 +7,32 @@ pub const ColorScheme = lib.Enum(lib.target, &.{
|
||||
"dark",
|
||||
});
|
||||
|
||||
/// Maximum number of bytes that `encodeColorSchemeReport` will write.
|
||||
pub const max_color_scheme_report_encode_size = max: {
|
||||
var result: usize = 0;
|
||||
for (@typeInfo(ColorScheme).@"enum".fields) |field| {
|
||||
var discarding: std.Io.Writer.Discarding = .init(&.{});
|
||||
encodeColorSchemeReport(
|
||||
&discarding.writer,
|
||||
@enumFromInt(field.value),
|
||||
) catch unreachable;
|
||||
result = @max(result, @as(usize, @intCast(discarding.count)));
|
||||
}
|
||||
|
||||
break :max result;
|
||||
};
|
||||
|
||||
/// Encode a color scheme report response for CSI ? 996 n queries.
|
||||
pub fn encodeColorSchemeReport(
|
||||
writer: *std.Io.Writer,
|
||||
scheme: ColorScheme,
|
||||
) std.Io.Writer.Error!void {
|
||||
try writer.writeAll(switch (scheme) {
|
||||
.dark => "\x1B[?997;1n",
|
||||
.light => "\x1B[?997;2n",
|
||||
});
|
||||
}
|
||||
|
||||
/// An enum(u16) of the available device status requests.
|
||||
pub const Request = dsr_enum: {
|
||||
const EnumField = std.builtin.Type.EnumField;
|
||||
@@ -72,3 +98,19 @@ const entries: []const Entry = &.{
|
||||
.{ .name = "cursor_position", .value = 6 },
|
||||
.{ .name = "color_scheme", .value = 996, .question = true },
|
||||
};
|
||||
|
||||
test "encode color scheme report dark" {
|
||||
try std.testing.expectEqual(@as(usize, 9), max_color_scheme_report_encode_size);
|
||||
|
||||
var buf: [max_color_scheme_report_encode_size]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try encodeColorSchemeReport(&writer, .dark);
|
||||
try std.testing.expectEqualStrings("\x1B[?997;1n", writer.buffered());
|
||||
}
|
||||
|
||||
test "encode color scheme report light" {
|
||||
var buf: [max_color_scheme_report_encode_size]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try encodeColorSchemeReport(&writer, .light);
|
||||
try std.testing.expectEqualStrings("\x1B[?997;2n", writer.buffered());
|
||||
}
|
||||
|
||||
@@ -348,10 +348,11 @@ pub const Handler = struct {
|
||||
.color_scheme => {
|
||||
const func = self.effects.color_scheme orelse return;
|
||||
const scheme = func(self) orelse return;
|
||||
self.writePty(switch (scheme) {
|
||||
.dark => "\x1B[?997;1n",
|
||||
.light => "\x1B[?997;2n",
|
||||
});
|
||||
var buf: [device_status.max_color_scheme_report_encode_size + 1]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(buf[0..device_status.max_color_scheme_report_encode_size]);
|
||||
device_status.encodeColorSchemeReport(&writer, scheme) catch return;
|
||||
buf[writer.end] = 0;
|
||||
self.writePty(buf[0..writer.end :0]);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,11 +712,15 @@ pub fn colorSchemeReportLocked(self: *Termio, td: *ThreadData, force: bool) !voi
|
||||
if (!force and !self.renderer_state.terminal.modes.get(.report_color_scheme)) {
|
||||
return;
|
||||
}
|
||||
const output = switch (self.config.conditional_state.theme) {
|
||||
.light => "\x1B[?997;2n",
|
||||
.dark => "\x1B[?997;1n",
|
||||
const scheme: terminalpkg.device_status.ColorScheme = switch (self.config.conditional_state.theme) {
|
||||
.light => .light,
|
||||
.dark => .dark,
|
||||
};
|
||||
try self.queueWrite(td, output, false);
|
||||
|
||||
var buf: [terminalpkg.device_status.max_color_scheme_report_encode_size]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try terminalpkg.device_status.encodeColorSchemeReport(&writer, scheme);
|
||||
try self.queueWrite(td, writer.buffered(), false);
|
||||
}
|
||||
|
||||
/// ThreadData is the data created and stored in the termio thread
|
||||
|
||||
Reference in New Issue
Block a user