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
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. Example: GHOSTTY_COLOR_SCHEME_DARK encodes to ESC [ ? 997 ; 1 n, while GHOSTTY_COLOR_SCHEME_LIGHT encodes to ESC [ ? 997 ; 2 n.
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