terminal: formatter improvements for color handling
parent
4c504560d4
commit
83a4f32a14
|
|
@ -38,4 +38,5 @@ pub fn main() !void {
|
||||||
var stdout_writer = std.fs.File.stdout().writer(&buf);
|
var stdout_writer = std.fs.File.stdout().writer(&buf);
|
||||||
const stdout = &stdout_writer.interface;
|
const stdout = &stdout_writer.interface;
|
||||||
try stdout.print("{f}", .{formatter});
|
try stdout.print("{f}", .{formatter});
|
||||||
|
try stdout.flush();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,11 @@ pub const Options = struct {
|
||||||
/// is currently only space characters (0x20).
|
/// is currently only space characters (0x20).
|
||||||
trim: bool = true,
|
trim: bool = true,
|
||||||
|
|
||||||
|
/// Set a background and foreground color to use for the "screen".
|
||||||
|
/// For styled formats, this will emit the proper sequences or styles.
|
||||||
|
background: ?color.RGB = null,
|
||||||
|
foreground: ?color.RGB = null,
|
||||||
|
|
||||||
/// If set, then styled formats in `emit` will use this palette to
|
/// If set, then styled formats in `emit` will use this palette to
|
||||||
/// emit colors directly as RGB. If this is null, styled formats will
|
/// emit colors directly as RGB. If this is null, styled formats will
|
||||||
/// still work but will use deferred palette styling (e.g. CSS variables
|
/// still work but will use deferred palette styling (e.g. CSS variables
|
||||||
|
|
@ -902,14 +907,66 @@ pub const PageFormatter = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap HTML output in monospace font styling
|
// Wrap HTML output in monospace font styling
|
||||||
if (self.opts.emit == .html) {
|
switch (self.opts.emit) {
|
||||||
const monospace = "<div style=\"font-family: monospace; white-space: pre;\">";
|
.plain => {},
|
||||||
try writer.writeAll(monospace);
|
|
||||||
if (self.point_map) |*map| map.map.appendNTimes(
|
.html => {
|
||||||
map.alloc,
|
// Setup our div. We use a buffer here that should always
|
||||||
.{ .x = 0, .y = 0 },
|
// fit the stuff we need, in order to make counting bytes easier.
|
||||||
monospace.len,
|
var buf: [1024]u8 = undefined;
|
||||||
) catch return error.WriteFailed;
|
var stream = std.io.fixedBufferStream(&buf);
|
||||||
|
const buf_writer = stream.writer();
|
||||||
|
|
||||||
|
// Monospace and whitespace preserving
|
||||||
|
buf_writer.writeAll("<div style=\"font-family: monospace; white-space: pre;") catch return error.WriteFailed;
|
||||||
|
|
||||||
|
// Background/foreground colors
|
||||||
|
if (self.opts.background) |bg| buf_writer.print(
|
||||||
|
"background-color: #{x:0>2}{x:0>2}{x:0>2};",
|
||||||
|
.{ bg.r, bg.g, bg.b },
|
||||||
|
) catch return error.WriteFailed;
|
||||||
|
if (self.opts.foreground) |fg| buf_writer.print(
|
||||||
|
"color: #{x:0>2}{x:0>2}{x:0>2};",
|
||||||
|
.{ fg.r, fg.g, fg.b },
|
||||||
|
) catch return error.WriteFailed;
|
||||||
|
|
||||||
|
buf_writer.writeAll("\">") catch return error.WriteFailed;
|
||||||
|
|
||||||
|
const header = stream.getWritten();
|
||||||
|
try writer.writeAll(header);
|
||||||
|
if (self.point_map) |*map| map.map.appendNTimes(
|
||||||
|
map.alloc,
|
||||||
|
.{ .x = 0, .y = 0 },
|
||||||
|
header.len,
|
||||||
|
) catch return error.WriteFailed;
|
||||||
|
},
|
||||||
|
|
||||||
|
.vt => {
|
||||||
|
// OSC 10 sets foreground color, OSC 11 sets background color
|
||||||
|
var buf: [512]u8 = undefined;
|
||||||
|
var stream = std.io.fixedBufferStream(&buf);
|
||||||
|
const buf_writer = stream.writer();
|
||||||
|
if (self.opts.foreground) |fg| {
|
||||||
|
buf_writer.print(
|
||||||
|
"\x1b]10;rgb:{x:0>2}/{x:0>2}/{x:0>2}\x1b\\",
|
||||||
|
.{ fg.r, fg.g, fg.b },
|
||||||
|
) catch return error.WriteFailed;
|
||||||
|
}
|
||||||
|
if (self.opts.background) |bg| {
|
||||||
|
buf_writer.print(
|
||||||
|
"\x1b]11;rgb:{x:0>2}/{x:0>2}/{x:0>2}\x1b\\",
|
||||||
|
.{ bg.r, bg.g, bg.b },
|
||||||
|
) catch return error.WriteFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = stream.getWritten();
|
||||||
|
try writer.writeAll(header);
|
||||||
|
if (self.point_map) |*map| map.map.appendNTimes(
|
||||||
|
map.alloc,
|
||||||
|
.{ .x = 0, .y = 0 },
|
||||||
|
header.len,
|
||||||
|
) catch return error.WriteFailed;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our style for non-plain formats
|
// Our style for non-plain formats
|
||||||
|
|
@ -3073,6 +3130,43 @@ test "Page VT with foreground color" {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Page VT with background and foreground colors" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var builder: std.Io.Writer.Allocating = .init(alloc);
|
||||||
|
defer builder.deinit();
|
||||||
|
|
||||||
|
var t = try Terminal.init(alloc, .{
|
||||||
|
.cols = 80,
|
||||||
|
.rows = 24,
|
||||||
|
});
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
|
||||||
|
var s = t.vtStream();
|
||||||
|
defer s.deinit();
|
||||||
|
|
||||||
|
try s.nextSlice("hello");
|
||||||
|
|
||||||
|
const pages = &t.screen.pages;
|
||||||
|
const page = &pages.pages.last.?.data;
|
||||||
|
|
||||||
|
var formatter: PageFormatter = .init(page, .{
|
||||||
|
.emit = .vt,
|
||||||
|
.background = .{ .r = 0x12, .g = 0x34, .b = 0x56 },
|
||||||
|
.foreground = .{ .r = 0xab, .g = 0xcd, .b = 0xef },
|
||||||
|
});
|
||||||
|
|
||||||
|
try formatter.format(&builder.writer);
|
||||||
|
const output = builder.writer.buffered();
|
||||||
|
|
||||||
|
// Should emit OSC 10 for foreground, OSC 11 for background, then the text
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
"\x1b]10;rgb:ab/cd/ef\x1b\\\x1b]11;rgb:12/34/56\x1b\\hello",
|
||||||
|
output,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
test "Page VT multi-line with styles" {
|
test "Page VT multi-line with styles" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
@ -4866,6 +4960,41 @@ test "TerminalFormatter html with palette" {
|
||||||
try testing.expect(std.mem.indexOf(u8, output, "test") != null);
|
try testing.expect(std.mem.indexOf(u8, output, "test") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Page html with background and foreground colors" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var builder: std.Io.Writer.Allocating = .init(alloc);
|
||||||
|
defer builder.deinit();
|
||||||
|
|
||||||
|
var t = try Terminal.init(alloc, .{
|
||||||
|
.cols = 80,
|
||||||
|
.rows = 24,
|
||||||
|
});
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
|
||||||
|
var s = t.vtStream();
|
||||||
|
defer s.deinit();
|
||||||
|
|
||||||
|
try s.nextSlice("hello");
|
||||||
|
|
||||||
|
const pages = &t.screen.pages;
|
||||||
|
const page = &pages.pages.last.?.data;
|
||||||
|
var formatter: PageFormatter = .init(page, .{
|
||||||
|
.emit = .html,
|
||||||
|
.background = .{ .r = 0x12, .g = 0x34, .b = 0x56 },
|
||||||
|
.foreground = .{ .r = 0xab, .g = 0xcd, .b = 0xef },
|
||||||
|
});
|
||||||
|
|
||||||
|
try formatter.format(&builder.writer);
|
||||||
|
const output = builder.writer.buffered();
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
"<div style=\"font-family: monospace; white-space: pre;background-color: #123456;color: #abcdef;\">hello</div>",
|
||||||
|
output,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
test "Page html with escaping" {
|
test "Page html with escaping" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ const std = @import("std");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const stream = @import("stream.zig");
|
const stream = @import("stream.zig");
|
||||||
const Action = stream.Action;
|
const Action = stream.Action;
|
||||||
const CursorStyle = @import("Screen.zig").CursorStyle;
|
const Screen = @import("Screen.zig");
|
||||||
const Mode = @import("modes.zig").Mode;
|
const modes = @import("modes.zig");
|
||||||
|
const osc_color = @import("osc/color.zig");
|
||||||
|
const kitty_color = @import("kitty/color.zig");
|
||||||
const Terminal = @import("Terminal.zig");
|
const Terminal = @import("Terminal.zig");
|
||||||
|
|
||||||
/// This is a Stream implementation that processes actions against
|
/// This is a Stream implementation that processes actions against
|
||||||
|
|
@ -76,7 +78,7 @@ pub const Handler = struct {
|
||||||
.default, .steady_block, .steady_bar, .steady_underline => false,
|
.default, .steady_block, .steady_bar, .steady_underline => false,
|
||||||
.blinking_block, .blinking_bar, .blinking_underline => true,
|
.blinking_block, .blinking_bar, .blinking_underline => true,
|
||||||
};
|
};
|
||||||
const style: CursorStyle = switch (value) {
|
const style: Screen.CursorStyle = switch (value) {
|
||||||
.default, .blinking_block, .steady_block => .block,
|
.default, .blinking_block, .steady_block => .block,
|
||||||
.blinking_bar, .steady_bar => .bar,
|
.blinking_bar, .steady_bar => .bar,
|
||||||
.blinking_underline, .steady_underline => .underline,
|
.blinking_underline, .steady_underline => .underline,
|
||||||
|
|
@ -214,7 +216,7 @@ pub const Handler = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setMode(self: *Handler, mode: Mode, enabled: bool) !void {
|
fn setMode(self: *Handler, mode: modes.Mode, enabled: bool) !void {
|
||||||
// Set the mode on the terminal
|
// Set the mode on the terminal
|
||||||
self.terminal.modes.set(mode, enabled);
|
self.terminal.modes.set(mode, enabled);
|
||||||
|
|
||||||
|
|
@ -294,8 +296,8 @@ pub const Handler = struct {
|
||||||
|
|
||||||
fn colorOperation(
|
fn colorOperation(
|
||||||
self: *Handler,
|
self: *Handler,
|
||||||
op: @import("osc/color.zig").Operation,
|
op: osc_color.Operation,
|
||||||
requests: *const @import("osc/color.zig").List,
|
requests: *const osc_color.List,
|
||||||
) !void {
|
) !void {
|
||||||
_ = op;
|
_ = op;
|
||||||
if (requests.count() == 0) return;
|
if (requests.count() == 0) return;
|
||||||
|
|
@ -366,7 +368,7 @@ pub const Handler = struct {
|
||||||
|
|
||||||
fn kittyColorOperation(
|
fn kittyColorOperation(
|
||||||
self: *Handler,
|
self: *Handler,
|
||||||
request: @import("kitty/color.zig").OSC,
|
request: kitty_color.OSC,
|
||||||
) !void {
|
) !void {
|
||||||
for (request.list.items) |item| {
|
for (request.list.items) |item| {
|
||||||
switch (item) {
|
switch (item) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue