mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-13 19:15:48 +00:00
vt: wip formatter api
This commit is contained in:
@@ -75,6 +75,7 @@ extern "C" {
|
||||
|
||||
#include <ghostty/vt/result.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/formatter.h>
|
||||
#include <ghostty/vt/terminal.h>
|
||||
#include <ghostty/vt/osc.h>
|
||||
#include <ghostty/vt/sgr.h>
|
||||
|
||||
156
include/ghostty/vt/formatter.h
Normal file
156
include/ghostty/vt/formatter.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* @file formatter.h
|
||||
*
|
||||
* Format terminal content as plain text, VT sequences, or HTML.
|
||||
*/
|
||||
|
||||
#ifndef GHOSTTY_VT_FORMATTER_H
|
||||
#define GHOSTTY_VT_FORMATTER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/result.h>
|
||||
#include <ghostty/vt/terminal.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup formatter Formatter
|
||||
*
|
||||
* Format terminal content as plain text, VT sequences, or HTML.
|
||||
*
|
||||
* A formatter captures a reference to a terminal and formatting options.
|
||||
* It can be used repeatedly to produce output that reflects the current
|
||||
* terminal state at the time of each format call.
|
||||
*
|
||||
* The terminal must outlive the formatter.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Output format.
|
||||
*
|
||||
* @ingroup formatter
|
||||
*/
|
||||
typedef enum {
|
||||
/** Plain text (no escape sequences). */
|
||||
GHOSTTY_FORMATTER_FORMAT_PLAIN,
|
||||
|
||||
/** VT sequences preserving colors, styles, URLs, etc. */
|
||||
GHOSTTY_FORMATTER_FORMAT_VT,
|
||||
|
||||
/** HTML with inline styles. */
|
||||
GHOSTTY_FORMATTER_FORMAT_HTML,
|
||||
} GhosttyFormatterFormat;
|
||||
|
||||
/**
|
||||
* Extra terminal state to include in styled output.
|
||||
*
|
||||
* @ingroup formatter
|
||||
*/
|
||||
typedef enum {
|
||||
/** Emit no extra state. */
|
||||
GHOSTTY_FORMATTER_EXTRA_NONE,
|
||||
|
||||
/** Emit style-relevant state (palette, cursor style, hyperlinks). */
|
||||
GHOSTTY_FORMATTER_EXTRA_STYLES,
|
||||
|
||||
/** Emit all state to reconstruct terminal as closely as possible. */
|
||||
GHOSTTY_FORMATTER_EXTRA_ALL,
|
||||
} GhosttyFormatterExtra;
|
||||
|
||||
/**
|
||||
* Opaque handle to a formatter instance.
|
||||
*
|
||||
* @ingroup formatter
|
||||
*/
|
||||
typedef struct GhosttyFormatter* GhosttyFormatter;
|
||||
|
||||
/**
|
||||
* Options for creating a terminal formatter.
|
||||
*
|
||||
* @ingroup formatter
|
||||
*/
|
||||
typedef struct {
|
||||
/** Output format to emit. */
|
||||
GhosttyFormatterFormat emit;
|
||||
|
||||
/** Whether to unwrap soft-wrapped lines. */
|
||||
bool unwrap;
|
||||
|
||||
/** Whether to trim trailing whitespace on non-blank lines. */
|
||||
bool trim;
|
||||
|
||||
/** Extra terminal state to include in styled output. */
|
||||
GhosttyFormatterExtra extra;
|
||||
} GhosttyFormatterTerminalOptions;
|
||||
|
||||
/**
|
||||
* Create a formatter for a terminal's active screen.
|
||||
*
|
||||
* The terminal must outlive the formatter. The formatter stores a borrowed
|
||||
* reference to the terminal and reads its current state on each format call.
|
||||
*
|
||||
* @param allocator Pointer to allocator, or NULL to use the default allocator
|
||||
* @param formatter Pointer to store the created formatter handle
|
||||
* @param terminal The terminal to format (must not be NULL)
|
||||
* @param options Formatting options
|
||||
* @return GHOSTTY_SUCCESS on success, or an error code on failure
|
||||
*
|
||||
* @ingroup formatter
|
||||
*/
|
||||
GhosttyResult ghostty_formatter_terminal_new(
|
||||
const GhosttyAllocator* allocator,
|
||||
GhosttyFormatter* formatter,
|
||||
GhosttyTerminal terminal,
|
||||
GhosttyFormatterTerminalOptions options);
|
||||
|
||||
/**
|
||||
* Run the formatter and produce output into the caller-provided buffer.
|
||||
*
|
||||
* Each call formats the current terminal state. Pass NULL for buf to
|
||||
* query the required buffer size without writing any output; in that case
|
||||
* out_written receives the required size and the return value is
|
||||
* GHOSTTY_OUT_OF_SPACE.
|
||||
*
|
||||
* If the buffer is too small, returns GHOSTTY_OUT_OF_SPACE and sets
|
||||
* out_written to the required size. The caller can then retry with a
|
||||
* larger buffer.
|
||||
*
|
||||
* @param formatter The formatter handle (must not be NULL)
|
||||
* @param buf Pointer to the output buffer, or NULL to query size
|
||||
* @param buf_len Length of the output buffer in bytes
|
||||
* @param out_written Pointer to receive the number of bytes written,
|
||||
* or the required size on failure
|
||||
* @return GHOSTTY_SUCCESS on success, or an error code on failure
|
||||
*
|
||||
* @ingroup formatter
|
||||
*/
|
||||
GhosttyResult ghostty_formatter_format(GhosttyFormatter formatter,
|
||||
uint8_t* buf,
|
||||
size_t buf_len,
|
||||
size_t* out_written);
|
||||
|
||||
/**
|
||||
* Free a formatter instance.
|
||||
*
|
||||
* Releases all resources associated with the formatter. After this call,
|
||||
* the formatter handle becomes invalid.
|
||||
*
|
||||
* @param formatter The formatter handle to free (may be NULL)
|
||||
*
|
||||
* @ingroup formatter
|
||||
*/
|
||||
void ghostty_formatter_free(GhosttyFormatter formatter);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GHOSTTY_VT_FORMATTER_H */
|
||||
@@ -17,6 +17,8 @@ typedef enum {
|
||||
GHOSTTY_OUT_OF_MEMORY = -1,
|
||||
/** Operation failed due to invalid value */
|
||||
GHOSTTY_INVALID_VALUE = -2,
|
||||
/** Operation failed because the provided buffer was too small */
|
||||
GHOSTTY_OUT_OF_SPACE = -3,
|
||||
} GhosttyResult;
|
||||
|
||||
#endif /* GHOSTTY_VT_RESULT_H */
|
||||
|
||||
@@ -143,6 +143,9 @@ comptime {
|
||||
@export(&c.sgr_unknown_partial, .{ .name = "ghostty_sgr_unknown_partial" });
|
||||
@export(&c.sgr_attribute_tag, .{ .name = "ghostty_sgr_attribute_tag" });
|
||||
@export(&c.sgr_attribute_value, .{ .name = "ghostty_sgr_attribute_value" });
|
||||
@export(&c.formatter_terminal_new, .{ .name = "ghostty_formatter_terminal_new" });
|
||||
@export(&c.formatter_format, .{ .name = "ghostty_formatter_format" });
|
||||
@export(&c.formatter_free, .{ .name = "ghostty_formatter_free" });
|
||||
@export(&c.terminal_new, .{ .name = "ghostty_terminal_new" });
|
||||
@export(&c.terminal_free, .{ .name = "ghostty_terminal_free" });
|
||||
@export(&c.terminal_reset, .{ .name = "ghostty_terminal_reset" });
|
||||
|
||||
344
src/terminal/c/formatter.zig
Normal file
344
src/terminal/c/formatter.zig
Normal file
@@ -0,0 +1,344 @@
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const lib_alloc = @import("../../lib/allocator.zig");
|
||||
const CAllocator = lib_alloc.Allocator;
|
||||
const terminal_c = @import("terminal.zig");
|
||||
const ZigTerminal = @import("../Terminal.zig");
|
||||
const formatterpkg = @import("../formatter.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
/// Wrapper around formatter that tracks the allocator for C API usage.
|
||||
const FormatterWrapper = struct {
|
||||
kind: Kind,
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
const Kind = union(enum) {
|
||||
terminal: formatterpkg.TerminalFormatter,
|
||||
};
|
||||
};
|
||||
|
||||
/// C: GhosttyFormatter
|
||||
pub const Formatter = ?*FormatterWrapper;
|
||||
|
||||
/// C: GhosttyFormatterFormat
|
||||
pub const Format = formatterpkg.Format;
|
||||
|
||||
/// C: GhosttyFormatterExtra
|
||||
pub const Extra = enum(c_int) {
|
||||
none = 0,
|
||||
styles = 1,
|
||||
all = 2,
|
||||
};
|
||||
|
||||
/// C: GhosttyFormatterTerminalOptions
|
||||
pub const TerminalOptions = extern struct {
|
||||
emit: Format,
|
||||
unwrap: bool,
|
||||
trim: bool,
|
||||
extra: Extra,
|
||||
};
|
||||
|
||||
pub fn terminal_new(
|
||||
alloc_: ?*const CAllocator,
|
||||
result: *Formatter,
|
||||
terminal_: terminal_c.Terminal,
|
||||
opts: TerminalOptions,
|
||||
) callconv(.c) Result {
|
||||
result.* = terminal_new_(
|
||||
alloc_,
|
||||
terminal_,
|
||||
opts,
|
||||
) catch |err| {
|
||||
result.* = null;
|
||||
return switch (err) {
|
||||
error.InvalidValue => .invalid_value,
|
||||
error.OutOfMemory => .out_of_memory,
|
||||
};
|
||||
};
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
fn terminal_new_(
|
||||
alloc_: ?*const CAllocator,
|
||||
terminal_: terminal_c.Terminal,
|
||||
opts: TerminalOptions,
|
||||
) error{
|
||||
InvalidValue,
|
||||
OutOfMemory,
|
||||
}!*FormatterWrapper {
|
||||
const t = terminal_ orelse return error.InvalidValue;
|
||||
|
||||
const alloc = lib_alloc.default(alloc_);
|
||||
const ptr = alloc.create(FormatterWrapper) catch
|
||||
return error.OutOfMemory;
|
||||
errdefer alloc.destroy(ptr);
|
||||
|
||||
const extra: formatterpkg.TerminalFormatter.Extra = switch (opts.extra) {
|
||||
.none => .none,
|
||||
.styles => .styles,
|
||||
.all => .all,
|
||||
};
|
||||
|
||||
var formatter: formatterpkg.TerminalFormatter = .init(t, .{
|
||||
.emit = opts.emit,
|
||||
.unwrap = opts.unwrap,
|
||||
.trim = opts.trim,
|
||||
});
|
||||
formatter.extra = extra;
|
||||
|
||||
ptr.* = .{
|
||||
.kind = .{ .terminal = formatter },
|
||||
.alloc = alloc,
|
||||
};
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
pub fn format(
|
||||
formatter_: Formatter,
|
||||
out_: ?[*]u8,
|
||||
out_len: usize,
|
||||
out_written: *usize,
|
||||
) callconv(.c) Result {
|
||||
const wrapper = formatter_ orelse return .invalid_value;
|
||||
|
||||
var writer: std.Io.Writer = .fixed(if (out_) |out|
|
||||
out[0..out_len]
|
||||
else
|
||||
&.{});
|
||||
|
||||
switch (wrapper.kind) {
|
||||
.terminal => |*t| t.format(&writer) catch |err| switch (err) {
|
||||
error.WriteFailed => {
|
||||
// On write failed we always report how much
|
||||
// space we actually needed.
|
||||
var discarding: std.Io.Writer.Discarding = .init(&.{});
|
||||
t.format(&discarding.writer) catch unreachable;
|
||||
out_written.* = @intCast(discarding.count);
|
||||
return .out_of_space;
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out_written.* = writer.end;
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn free(formatter_: Formatter) callconv(.c) void {
|
||||
const wrapper = formatter_ orelse return;
|
||||
const alloc = wrapper.alloc;
|
||||
alloc.destroy(wrapper);
|
||||
}
|
||||
|
||||
test "terminal_new/free" {
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
var f: Formatter = null;
|
||||
try testing.expectEqual(Result.success, terminal_new(
|
||||
&lib_alloc.test_allocator,
|
||||
&f,
|
||||
t,
|
||||
.{ .emit = .plain, .unwrap = false, .trim = true, .extra = .none },
|
||||
));
|
||||
try testing.expect(f != null);
|
||||
free(f);
|
||||
}
|
||||
|
||||
test "terminal_new invalid_value on null terminal" {
|
||||
var f: Formatter = null;
|
||||
try testing.expectEqual(Result.invalid_value, terminal_new(
|
||||
&lib_alloc.test_allocator,
|
||||
&f,
|
||||
null,
|
||||
.{ .emit = .plain, .unwrap = false, .trim = true, .extra = .none },
|
||||
));
|
||||
try testing.expect(f == null);
|
||||
}
|
||||
|
||||
test "free null" {
|
||||
free(null);
|
||||
}
|
||||
|
||||
test "format plain" {
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
terminal_c.vt_write(t, "Hello", 5);
|
||||
|
||||
var f: Formatter = null;
|
||||
try testing.expectEqual(Result.success, terminal_new(
|
||||
&lib_alloc.test_allocator,
|
||||
&f,
|
||||
t,
|
||||
.{ .emit = .plain, .unwrap = false, .trim = true, .extra = .none },
|
||||
));
|
||||
defer free(f);
|
||||
|
||||
var buf: [1024]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
|
||||
try testing.expectEqualStrings("Hello", buf[0..written]);
|
||||
}
|
||||
|
||||
test "format reflects terminal changes" {
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
terminal_c.vt_write(t, "Hello", 5);
|
||||
|
||||
var f: Formatter = null;
|
||||
try testing.expectEqual(Result.success, terminal_new(
|
||||
&lib_alloc.test_allocator,
|
||||
&f,
|
||||
t,
|
||||
.{ .emit = .plain, .unwrap = false, .trim = true, .extra = .none },
|
||||
));
|
||||
defer free(f);
|
||||
|
||||
var buf: [1024]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
|
||||
try testing.expectEqualStrings("Hello", buf[0..written]);
|
||||
|
||||
// Write more data and re-format
|
||||
terminal_c.vt_write(t, "\r\nWorld", 7);
|
||||
|
||||
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
|
||||
try testing.expectEqualStrings("Hello\nWorld", buf[0..written]);
|
||||
}
|
||||
|
||||
test "format null returns required size" {
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
terminal_c.vt_write(t, "Hello", 5);
|
||||
|
||||
var f: Formatter = null;
|
||||
try testing.expectEqual(Result.success, terminal_new(
|
||||
&lib_alloc.test_allocator,
|
||||
&f,
|
||||
t,
|
||||
.{ .emit = .plain, .unwrap = false, .trim = true, .extra = .none },
|
||||
));
|
||||
defer free(f);
|
||||
|
||||
// Pass null buffer to query required size
|
||||
var required: usize = 0;
|
||||
try testing.expectEqual(Result.out_of_space, format(f, null, 0, &required));
|
||||
try testing.expect(required > 0);
|
||||
|
||||
// Now allocate and format
|
||||
var buf: [1024]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
|
||||
try testing.expectEqual(required, written);
|
||||
}
|
||||
|
||||
test "format buffer too small" {
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
terminal_c.vt_write(t, "Hello", 5);
|
||||
|
||||
var f: Formatter = null;
|
||||
try testing.expectEqual(Result.success, terminal_new(
|
||||
&lib_alloc.test_allocator,
|
||||
&f,
|
||||
t,
|
||||
.{ .emit = .plain, .unwrap = false, .trim = true, .extra = .none },
|
||||
));
|
||||
defer free(f);
|
||||
|
||||
// Buffer too small
|
||||
var buf: [2]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.out_of_space, format(f, &buf, buf.len, &written));
|
||||
// written contains the required size
|
||||
try testing.expectEqual(@as(usize, 5), written);
|
||||
}
|
||||
|
||||
test "format null formatter" {
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.invalid_value, format(null, null, 0, &written));
|
||||
}
|
||||
|
||||
test "format vt" {
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
terminal_c.vt_write(t, "Test", 4);
|
||||
|
||||
var f: Formatter = null;
|
||||
try testing.expectEqual(Result.success, terminal_new(
|
||||
&lib_alloc.test_allocator,
|
||||
&f,
|
||||
t,
|
||||
.{ .emit = .vt, .unwrap = false, .trim = true, .extra = .styles },
|
||||
));
|
||||
defer free(f);
|
||||
|
||||
var buf: [65536]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
|
||||
try testing.expect(written > 0);
|
||||
try testing.expect(std.mem.indexOf(u8, buf[0..written], "Test") != null);
|
||||
}
|
||||
|
||||
test "format html" {
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
terminal_c.vt_write(t, "Html", 4);
|
||||
|
||||
var f: Formatter = null;
|
||||
try testing.expectEqual(Result.success, terminal_new(
|
||||
&lib_alloc.test_allocator,
|
||||
&f,
|
||||
t,
|
||||
.{ .emit = .html, .unwrap = false, .trim = true, .extra = .none },
|
||||
));
|
||||
defer free(f);
|
||||
|
||||
var buf: [65536]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, format(f, &buf, buf.len, &written));
|
||||
try testing.expect(written > 0);
|
||||
try testing.expect(std.mem.indexOf(u8, buf[0..written], "Html") != null);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub const color = @import("color.zig");
|
||||
pub const formatter = @import("formatter.zig");
|
||||
pub const osc = @import("osc.zig");
|
||||
pub const key_event = @import("key_event.zig");
|
||||
pub const key_encode = @import("key_encode.zig");
|
||||
@@ -17,6 +18,10 @@ pub const osc_command_data = osc.commandData;
|
||||
|
||||
pub const color_rgb_get = color.rgb_get;
|
||||
|
||||
pub const formatter_terminal_new = formatter.terminal_new;
|
||||
pub const formatter_format = formatter.format;
|
||||
pub const formatter_free = formatter.free;
|
||||
|
||||
pub const sgr_new = sgr.new;
|
||||
pub const sgr_free = sgr.free;
|
||||
pub const sgr_reset = sgr.reset;
|
||||
@@ -62,6 +67,7 @@ pub const terminal_scroll_viewport = terminal.scroll_viewport;
|
||||
|
||||
test {
|
||||
_ = color;
|
||||
_ = formatter;
|
||||
_ = osc;
|
||||
_ = key_event;
|
||||
_ = key_encode;
|
||||
|
||||
@@ -3,4 +3,5 @@ pub const Result = enum(c_int) {
|
||||
success = 0,
|
||||
out_of_memory = -1,
|
||||
invalid_value = -2,
|
||||
out_of_space = -3,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const std = @import("std");
|
||||
const build_options = @import("terminal_options");
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const lib = @import("../lib/main.zig");
|
||||
const lib_target: lib.Target = if (build_options.c_abi) .c else .zig;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const color = @import("color.zig");
|
||||
const size = @import("size.zig");
|
||||
@@ -19,46 +22,47 @@ const Selection = @import("Selection.zig");
|
||||
const Style = @import("style.zig").Style;
|
||||
|
||||
/// Formats available.
|
||||
pub const Format = enum {
|
||||
/// Plain text.
|
||||
plain,
|
||||
pub const Format = lib.Enum(lib_target, &.{
|
||||
// Plain text.
|
||||
"plain",
|
||||
|
||||
/// Include VT sequences to preserve colors, styles, URLs, etc.
|
||||
/// This is predominantly SGR sequences but may contain others as needed.
|
||||
///
|
||||
/// Note that for reference colors, like palette indices, this will
|
||||
/// vary based on the formatter and you should see the docs. For example,
|
||||
/// PageFormatter with VT will emit SGR sequences with palette indices,
|
||||
/// not the color itself.
|
||||
///
|
||||
/// For VT, newlines will be emitted as `\r\n` so that the cursor properly
|
||||
/// moves back to the beginning prior emitting follow-up lines.
|
||||
vt,
|
||||
// Include VT sequences to preserve colors, styles, URLs, etc.
|
||||
// This is predominantly SGR sequences but may contain others as needed.
|
||||
//
|
||||
// Note that for reference colors, like palette indices, this will
|
||||
// vary based on the formatter and you should see the docs. For example,
|
||||
// PageFormatter with VT will emit SGR sequences with palette indices,
|
||||
// not the color itself.
|
||||
//
|
||||
// For VT, newlines will be emitted as `\r\n` so that the cursor properly
|
||||
// moves back to the beginning prior emitting follow-up lines.
|
||||
"vt",
|
||||
|
||||
/// HTML output.
|
||||
///
|
||||
/// This will emit inline styles for as much styling as possible,
|
||||
/// in the interest of simplicity and ease of editing. This isn't meant
|
||||
/// to build the most beautiful or efficient HTML, but rather to be
|
||||
/// stylistically correct.
|
||||
///
|
||||
/// For colors, RGB values are emitted as inline CSS (#RRGGBB) while palette
|
||||
/// indices use CSS variables (var(--vt-palette-N)). The palette colors are
|
||||
/// emitted by TerminalFormatter.Extra.palette as a <style> block if you
|
||||
/// want to also include that. But if you only format a screen or lower,
|
||||
/// the formatter doesn't have access to the current palette to render it.
|
||||
///
|
||||
/// Newlines are emitted as actual '\n' characters. Consumers should use
|
||||
/// CSS white-space: pre or pre-wrap to preserve spacing and alignment.
|
||||
html,
|
||||
// HTML output.
|
||||
//
|
||||
// This will emit inline styles for as much styling as possible,
|
||||
// in the interest of simplicity and ease of editing. This isn't meant
|
||||
// to build the most beautiful or efficient HTML, but rather to be
|
||||
// stylistically correct.
|
||||
//
|
||||
// For colors, RGB values are emitted as inline CSS (#RRGGBB) while palette
|
||||
// indices use CSS variables (var(--vt-palette-N)). The palette colors are
|
||||
// emitted by TerminalFormatter.Extra.palette as a <style> block if you
|
||||
// want to also include that. But if you only format a screen or lower,
|
||||
// the formatter doesn't have access to the current palette to render it.
|
||||
//
|
||||
// Newlines are emitted as actual '\n' characters. Consumers should use
|
||||
// CSS white-space: pre or pre-wrap to preserve spacing and alignment.
|
||||
"html",
|
||||
});
|
||||
|
||||
pub fn styled(self: Format) bool {
|
||||
return switch (self) {
|
||||
.plain => false,
|
||||
.html, .vt => true,
|
||||
};
|
||||
}
|
||||
};
|
||||
/// Returns true if the format emits styled output (not plaintext).
|
||||
pub fn formatStyled(fmt: Format) bool {
|
||||
return switch (fmt) {
|
||||
.plain => false,
|
||||
.html, .vt => true,
|
||||
};
|
||||
}
|
||||
|
||||
pub const CodepointMap = struct {
|
||||
/// Unicode codepoint range to replace.
|
||||
@@ -1130,7 +1134,7 @@ pub const PageFormatter = struct {
|
||||
// If we're emitting styled output (not plaintext) and
|
||||
// the cell has some kind of styling or is not empty
|
||||
// then this isn't blank.
|
||||
if (self.opts.emit.styled() and
|
||||
if (formatStyled(self.opts.emit) and
|
||||
(!cell.isEmpty() or cell.hasStyling())) break :blank;
|
||||
|
||||
// Cells with no text are blank
|
||||
@@ -1186,7 +1190,7 @@ pub const PageFormatter = struct {
|
||||
style: {
|
||||
// If we aren't emitting styled output then we don't
|
||||
// have to worry about styles.
|
||||
if (!self.opts.emit.styled()) break :style;
|
||||
if (!formatStyled(self.opts.emit)) break :style;
|
||||
|
||||
// Get our cell style.
|
||||
const cell_style = self.cellStyle(cell);
|
||||
|
||||
Reference in New Issue
Block a user