mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-31 20:59:02 +00:00
terminal/snapshot: use lib.Enum enums where possible
This commit is contained in:
@@ -8,6 +8,7 @@ const ansi = @import("ansi.zig");
|
||||
const charsets = @import("charsets.zig");
|
||||
const fastmem = @import("../fastmem.zig");
|
||||
const kitty = @import("kitty.zig");
|
||||
const lib = @import("lib.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const tripwire = @import("../tripwire.zig");
|
||||
const unicode = @import("../unicode/main.zig");
|
||||
@@ -114,7 +115,12 @@ pub const SemanticPrompt = struct {
|
||||
.click = .none,
|
||||
};
|
||||
|
||||
pub const SemanticClick = union(enum) {
|
||||
pub const SemanticClickKind = lib.Enum(
|
||||
lib.target,
|
||||
&.{ "none", "click_events", "cl" },
|
||||
);
|
||||
|
||||
pub const SemanticClick = union(SemanticClickKind) {
|
||||
none,
|
||||
click_events: osc.semantic_prompt.ClickEvents,
|
||||
cl: osc.semantic_prompt.Click,
|
||||
|
||||
@@ -99,8 +99,11 @@ pub const ModifyKeyFormat = lib.Enum(
|
||||
|
||||
/// The protection modes that can be set for the terminal. See DECSCA and
|
||||
/// ESC V, W.
|
||||
pub const ProtectedMode = enum {
|
||||
off,
|
||||
iso, // ESC V, W
|
||||
dec, // CSI Ps " q
|
||||
};
|
||||
pub const ProtectedMode = lib.Enum(
|
||||
lib.target,
|
||||
&.{
|
||||
"off",
|
||||
"iso", // ESC V, W
|
||||
"dec", // CSI Ps " q
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
const lib = @import("lib.zig");
|
||||
|
||||
/// The visual style of the cursor. Whether or not it blinks
|
||||
/// is determined by mode 12 (modes.zig). This mode is synchronized
|
||||
/// with CSI q, the same as xterm.
|
||||
pub const Style = enum {
|
||||
bar, // DECSCUSR 5, 6
|
||||
block, // DECSCUSR 1, 2
|
||||
underline, // DECSCUSR 3, 4
|
||||
///
|
||||
/// Bar, block, and underline correspond to DECSCUSR 5/6, 1/2, and 3/4.
|
||||
/// Hollow block is Ghostty-specific and is reported as DECSCUSR 1 or 2.
|
||||
pub const Style = lib.Enum(lib.target, &.{
|
||||
// DECSCUSR 5, 6
|
||||
"bar",
|
||||
|
||||
/// The cursor styles below aren't known by DESCUSR and are custom
|
||||
/// implemented in Ghostty. They are reported as some standard style
|
||||
/// if requested, though.
|
||||
/// Hollow block cursor. This is a block cursor with the center empty.
|
||||
/// Reported as DECSCUSR 1 or 2 (block).
|
||||
block_hollow,
|
||||
};
|
||||
// DECSCUSR 1, 2
|
||||
"block",
|
||||
|
||||
// DECSCUSR 3, 4
|
||||
"underline",
|
||||
|
||||
// The cursor styles below aren't known by DESCUSR and are custom
|
||||
// implemented in Ghostty. They are reported as some standard style
|
||||
// if requested, though.
|
||||
// Hollow block cursor. This is a block cursor with the center empty.
|
||||
// Reported as DECSCUSR 1 or 2 (block).
|
||||
"block_hollow",
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md
|
||||
const std = @import("std");
|
||||
|
||||
const lib = @import("../../lib.zig");
|
||||
const Parser = @import("../../osc.zig").Parser;
|
||||
const OSCCommand = @import("../../osc.zig").Command;
|
||||
const string_encoding = @import("../../../os/string_encoding.zig");
|
||||
@@ -68,7 +69,10 @@ pub const Command = struct {
|
||||
// See https://github.com/ghostty-org/ghostty/issues/10865 and
|
||||
// https://github.com/kovidgoyal/kitty/issues/9500
|
||||
// for further details.
|
||||
pub const ClickEvents = enum { absolute, relative };
|
||||
pub const ClickEvents = lib.Enum(
|
||||
lib.target,
|
||||
&.{ "absolute", "relative" },
|
||||
);
|
||||
|
||||
pub const Option = enum {
|
||||
aid,
|
||||
@@ -199,7 +203,7 @@ pub const Option = enum {
|
||||
|
||||
return switch (self) {
|
||||
.aid => value,
|
||||
.cl => .init(value),
|
||||
.cl => parseClick(value),
|
||||
.prompt_kind => if (value.len == 1) PromptKind.init(value[0]) else null,
|
||||
.err => value,
|
||||
.redraw => if (std.mem.eql(u8, value, "0"))
|
||||
@@ -234,42 +238,50 @@ pub const Option = enum {
|
||||
|
||||
/// The `cl` option specifies what kind of cursor key sequences are handled
|
||||
/// by the application for click-to-move-cursor functionality.
|
||||
pub const Click = enum {
|
||||
/// Value: "line". Allows motion within a single input line using standard
|
||||
/// left/right arrow escape sequences. Only a single left/right sequence
|
||||
/// should be emitted for double-width characters.
|
||||
line,
|
||||
///
|
||||
/// `line` allows movement within one input line. `multiple` allows movement
|
||||
/// across lines with left/right sequences. The two vertical modes additionally
|
||||
/// allow up/down sequences, with `smart_vertical` permitting editor-aware
|
||||
/// column clamping.
|
||||
pub const Click = lib.Enum(
|
||||
lib.target,
|
||||
&.{
|
||||
// Value: "line". Allows motion within a single input line using
|
||||
// standard left/right arrow escape sequences. Only a single left/right
|
||||
// sequence should be emitted for double-width characters.
|
||||
"line",
|
||||
|
||||
/// Value: "m". Allows movement between different lines in the same group,
|
||||
/// but only using left/right arrow escape sequences.
|
||||
multiple,
|
||||
// Value: "m". Allows movement between different lines in the same
|
||||
// group, but only using left/right arrow escape sequences.
|
||||
"multiple",
|
||||
|
||||
/// Value: "v". Like `multiple` but cursor up/down should be used. The
|
||||
/// terminal should be conservative when moving between lines: move the
|
||||
/// cursor left to the start of line, emit the needed up/down sequences,
|
||||
/// then move the cursor right to the clicked destination.
|
||||
conservative_vertical,
|
||||
// Value: "v". Like `multiple` but cursor up/down should be used. The
|
||||
// terminal should be conservative when moving between lines: move the
|
||||
// cursor left to the start of line, emit the needed up/down sequences,
|
||||
// then move the cursor right to the clicked destination.
|
||||
"conservative_vertical",
|
||||
|
||||
/// Value: "w". Like `conservative_vertical` but specifies that there are
|
||||
/// no spurious spaces at the end of the line, and the application editor
|
||||
/// handles "smart vertical movement" (moving 2 lines up from position 20,
|
||||
/// where the intermediate line is 15 chars wide and the destination is
|
||||
/// 18 chars wide, ends at position 18).
|
||||
smart_vertical,
|
||||
// Value: "w". Like `conservative_vertical` but specifies that there
|
||||
// are no spurious spaces at the end of the line, and the application
|
||||
// editor handles "smart vertical movement" (moving 2 lines up from
|
||||
// position 20, where the intermediate line is 15 chars wide and the
|
||||
// destination is 18 chars wide, ends at position 18).
|
||||
"smart_vertical",
|
||||
},
|
||||
);
|
||||
|
||||
pub fn init(value: []const u8) ?Click {
|
||||
return if (value.len == 1) switch (value[0]) {
|
||||
'm' => .multiple,
|
||||
'v' => .conservative_vertical,
|
||||
'w' => .smart_vertical,
|
||||
else => null,
|
||||
} else if (std.mem.eql(
|
||||
u8,
|
||||
value,
|
||||
"line",
|
||||
)) .line else null;
|
||||
}
|
||||
};
|
||||
fn parseClick(value: []const u8) ?Click {
|
||||
return if (value.len == 1) switch (value[0]) {
|
||||
'm' => .multiple,
|
||||
'v' => .conservative_vertical,
|
||||
'w' => .smart_vertical,
|
||||
else => null,
|
||||
} else if (std.mem.eql(
|
||||
u8,
|
||||
value,
|
||||
"line",
|
||||
)) .line else null;
|
||||
}
|
||||
|
||||
pub const PromptKind = enum {
|
||||
initial,
|
||||
|
||||
@@ -149,11 +149,7 @@ pub fn encode(
|
||||
const row_header: RowHeader = .{
|
||||
.wrap = row.wrap,
|
||||
.wrap_continuation = row.wrap_continuation,
|
||||
.semantic_prompt = switch (row.semantic_prompt) {
|
||||
.none => .none,
|
||||
.prompt => .prompt,
|
||||
.prompt_continuation => .prompt_continuation,
|
||||
},
|
||||
.semantic_prompt = row.semantic_prompt,
|
||||
};
|
||||
try writer.writeByte(@bitCast(row_header));
|
||||
|
||||
@@ -262,20 +258,24 @@ pub fn decode(
|
||||
hyperlink_remap: *const HyperlinkRemap,
|
||||
) DecodeError!void {
|
||||
for (0..page.size.rows) |y| {
|
||||
const row_header: RowHeader = @bitCast(try reader.takeByte());
|
||||
const row_raw = try reader.takeByte();
|
||||
|
||||
// Validate the enum field before bitcasting into the packed native
|
||||
// representation. The remaining value is checked through its named
|
||||
// padding field below.
|
||||
const semantic_prompt_raw: u2 = @truncate(row_raw >> 2);
|
||||
_ = std.enums.fromInt(
|
||||
TerminalRow.SemanticPrompt,
|
||||
semantic_prompt_raw,
|
||||
) orelse return error.InvalidRow;
|
||||
|
||||
const row_header: RowHeader = @bitCast(row_raw);
|
||||
if (row_header._padding != 0) return error.InvalidRow;
|
||||
const semantic_prompt: TerminalRow.SemanticPrompt =
|
||||
switch (row_header.semantic_prompt) {
|
||||
.none => .none,
|
||||
.prompt => .prompt,
|
||||
.prompt_continuation => .prompt_continuation,
|
||||
.invalid => return error.InvalidRow,
|
||||
};
|
||||
|
||||
const row = page.getRow(y);
|
||||
row.wrap = row_header.wrap;
|
||||
row.wrap_continuation = row_header.wrap_continuation;
|
||||
row.semantic_prompt = semantic_prompt;
|
||||
row.semantic_prompt = row_header.semantic_prompt;
|
||||
|
||||
const cells = page.getCells(row);
|
||||
for (cells, 0..) |*cell, x| {
|
||||
@@ -396,15 +396,8 @@ pub fn decode(
|
||||
const RowHeader = packed struct(u8) {
|
||||
wrap: bool = false,
|
||||
wrap_continuation: bool = false,
|
||||
semantic_prompt: SemanticPrompt = .none,
|
||||
semantic_prompt: TerminalRow.SemanticPrompt = .none,
|
||||
_padding: u4 = 0,
|
||||
|
||||
const SemanticPrompt = enum(u2) {
|
||||
none = 0,
|
||||
prompt = 1,
|
||||
prompt_continuation = 2,
|
||||
invalid = 3,
|
||||
};
|
||||
};
|
||||
|
||||
/// The fixed fields that precede a cell's grapheme suffix codepoints.
|
||||
@@ -451,14 +444,9 @@ const CellHeader = struct {
|
||||
self: CellHeader,
|
||||
writer: *std.Io.Writer,
|
||||
) std.Io.Writer.Error!void {
|
||||
const semantic_content: Flags.SemanticContent = switch (self.semantic_content) {
|
||||
.output => .output,
|
||||
.input => .input,
|
||||
.prompt => .prompt,
|
||||
};
|
||||
const flags: Flags = .{
|
||||
.protected = self.protected,
|
||||
.semantic_content = semantic_content,
|
||||
.semantic_content = self.semantic_content,
|
||||
};
|
||||
|
||||
// 0 1 2 3 4 6 8 12 16
|
||||
@@ -488,14 +476,18 @@ const CellHeader = struct {
|
||||
try reader.takeByte(),
|
||||
) orelse return error.InvalidCell;
|
||||
|
||||
const flags: Flags = @bitCast(try reader.takeByte());
|
||||
const flags_raw = try reader.takeByte();
|
||||
|
||||
// As with the row header, validate the exhaustive native enum before
|
||||
// bitcasting the complete packed byte.
|
||||
const semantic_content_raw: u2 = @truncate(flags_raw >> 1);
|
||||
_ = std.enums.fromInt(
|
||||
TerminalCell.SemanticContent,
|
||||
semantic_content_raw,
|
||||
) orelse return error.InvalidCell;
|
||||
|
||||
const flags: Flags = @bitCast(flags_raw);
|
||||
if (flags._padding != 0) return error.InvalidCell;
|
||||
const semantic_content: TerminalCell.SemanticContent = switch (flags.semantic_content) {
|
||||
.output => .output,
|
||||
.input => .input,
|
||||
.prompt => .prompt,
|
||||
.invalid => return error.InvalidCell,
|
||||
};
|
||||
|
||||
// This byte is reserved so the IDs and content value remain naturally
|
||||
// aligned within the fixed cell header.
|
||||
@@ -505,7 +497,7 @@ const CellHeader = struct {
|
||||
.content_kind = content_kind,
|
||||
.width = width,
|
||||
.protected = flags.protected,
|
||||
.semantic_content = semantic_content,
|
||||
.semantic_content = flags.semantic_content,
|
||||
.style_id = try io.readInt(reader, TerminalStyleId),
|
||||
.hyperlink_id = try io.readInt(reader, TerminalHyperlinkId),
|
||||
.value = @bitCast(try io.readInt(reader, u32)),
|
||||
@@ -547,14 +539,7 @@ const CellHeader = struct {
|
||||
|
||||
const Flags = packed struct(u8) {
|
||||
protected: bool = false,
|
||||
semantic_content: SemanticContent = .output,
|
||||
semantic_content: TerminalCell.SemanticContent = .output,
|
||||
_padding: u5 = 0,
|
||||
|
||||
const SemanticContent = enum(u2) {
|
||||
output = 0,
|
||||
input = 1,
|
||||
prompt = 2,
|
||||
invalid = 3,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -86,7 +86,7 @@ pub const Header = struct {
|
||||
}
|
||||
|
||||
/// Identifies the previously encoded screen that owns this history.
|
||||
key: screen.Key,
|
||||
key: TerminalScreenKey,
|
||||
|
||||
/// Number of complete PAGE records immediately following this record.
|
||||
page_count: u32,
|
||||
@@ -102,7 +102,7 @@ pub const Header = struct {
|
||||
self: Header,
|
||||
writer: *std.Io.Writer,
|
||||
) std.Io.Writer.Error!void {
|
||||
try io.writeInt(writer, u16, @intFromEnum(self.key));
|
||||
try io.writeInt(writer, u16, @intCast(@intFromEnum(self.key)));
|
||||
try io.writeInt(writer, u32, self.page_count);
|
||||
try io.writeInt(writer, u64, self.total_rows);
|
||||
try io.writeInt(writer, u16, self.screen_overlap_rows);
|
||||
@@ -112,9 +112,13 @@ pub const Header = struct {
|
||||
|
||||
/// Decode and validate the fixed HISTORY payload.
|
||||
pub fn decode(reader: *std.Io.Reader) Header.DecodeError!Header {
|
||||
const raw = try io.readInt(reader, u16);
|
||||
const Tag = @typeInfo(TerminalScreenKey).@"enum".tag_type;
|
||||
const value = std.math.cast(Tag, raw) orelse
|
||||
return error.InvalidKey;
|
||||
const key = std.enums.fromInt(
|
||||
screen.Key,
|
||||
try io.readInt(reader, u16),
|
||||
TerminalScreenKey,
|
||||
value,
|
||||
) orelse return error.InvalidKey;
|
||||
return .{
|
||||
.key = key,
|
||||
@@ -186,10 +190,7 @@ pub fn encode(
|
||||
};
|
||||
|
||||
const header: Header = .{
|
||||
.key = switch (key) {
|
||||
.primary => .primary,
|
||||
.alternate => .alternate,
|
||||
},
|
||||
.key = key,
|
||||
.page_count = std.math.cast(
|
||||
u32,
|
||||
page_count,
|
||||
@@ -265,11 +266,7 @@ pub fn decode(
|
||||
break :header header;
|
||||
};
|
||||
|
||||
const decoded_key: TerminalScreenKey = switch (header.key) {
|
||||
.primary => .primary,
|
||||
.alternate => .alternate,
|
||||
};
|
||||
if (decoded_key != expected_key) return error.UnexpectedScreenKey;
|
||||
if (header.key != expected_key) return error.UnexpectedScreenKey;
|
||||
|
||||
// A freshly restored SCREEN may carry overlap inside its first page, but
|
||||
// cannot already contain a complete historical page before that boundary.
|
||||
@@ -437,7 +434,7 @@ test "HISTORY encodes newest first and restores complete history" {
|
||||
try std.testing.expectEqual(record.Tag.history, history_record.header.tag);
|
||||
const header = try Header.decode(history_record.payloadReader());
|
||||
try history_record.finish();
|
||||
try std.testing.expectEqual(screen.Key.primary, header.key);
|
||||
try std.testing.expectEqual(TerminalScreenKey.primary, header.key);
|
||||
try std.testing.expectEqual(@as(u32, 2), header.page_count);
|
||||
try std.testing.expectEqual(
|
||||
@as(u16, active_top.y),
|
||||
|
||||
@@ -193,9 +193,9 @@
|
||||
//! bit 8 +---------------------------+
|
||||
//! ```
|
||||
//!
|
||||
//! All enum values and bit assignments below are snapshot-version registries,
|
||||
//! not native enum ordinals. Changing any of them requires a snapshot version
|
||||
//! bump.
|
||||
//! Native enum values used below are stable terminal registries. Their wire
|
||||
//! widths are fixed independently by this payload. Changing those values or
|
||||
//! any packed bit assignment requires a snapshot version bump.
|
||||
|
||||
const std = @import("std");
|
||||
const build_options = @import("terminal_options");
|
||||
@@ -209,6 +209,7 @@ const terminal_ansi = @import("../ansi.zig");
|
||||
const terminal_charsets = @import("../charsets.zig");
|
||||
const terminal_hyperlink = @import("../hyperlink.zig");
|
||||
const terminal_kitty = @import("../kitty.zig");
|
||||
const terminal_osc = @import("../osc.zig");
|
||||
const terminal_page = @import("../page.zig");
|
||||
const TerminalPageList = @import("../PageList.zig");
|
||||
const TerminalScreen = @import("../Screen.zig");
|
||||
@@ -221,7 +222,6 @@ const TerminalStyle = terminal_style.Style;
|
||||
/// Errors possible while encoding fixed SCREEN payload fields.
|
||||
const PayloadEncodeError = std.Io.Writer.Error || error{
|
||||
InvalidCursorFlags,
|
||||
InvalidSemanticContent,
|
||||
InvalidCharsetState,
|
||||
InvalidKittyKeyboardIndex,
|
||||
InvalidKittyKeyboardFlags,
|
||||
@@ -349,7 +349,7 @@ pub fn decode(
|
||||
defer if (cursor_hyperlink) |*value| value.deinit(alloc);
|
||||
try record_reader.finish();
|
||||
|
||||
const key = header.key.terminal();
|
||||
const key = header.key;
|
||||
if (header.page_count == 0) return error.InvalidPageCount;
|
||||
|
||||
// Dimensions are terminal-wide state supplied by the enclosing snapshot.
|
||||
@@ -417,13 +417,12 @@ pub fn decode(
|
||||
.cursor = .{
|
||||
.x = header.cursor_x,
|
||||
.y = header.cursor_y,
|
||||
.cursor_style = header.cursor_style.terminal(),
|
||||
.cursor_style = header.cursor_style,
|
||||
.pending_wrap = header.cursor_flags.pending_wrap,
|
||||
.protected = header.cursor_flags.protected,
|
||||
.style = header.cursor_pen,
|
||||
.hyperlink_implicit_id = header.hyperlink_implicit_id,
|
||||
.semantic_content = header.cursor_flags.semantic_content
|
||||
.terminal(),
|
||||
.semantic_content = header.cursor_flags.semantic_content,
|
||||
.semantic_content_clear_eol = header.cursor_flags
|
||||
.semantic_content_clear_eol,
|
||||
.page_pin = cursor_pin,
|
||||
@@ -434,12 +433,12 @@ pub fn decode(
|
||||
value.terminal()
|
||||
else
|
||||
null,
|
||||
.charset = header.charset.terminal(),
|
||||
.protected_mode = header.protected_mode.terminal(),
|
||||
.charset = header.charset,
|
||||
.protected_mode = header.protected_mode,
|
||||
.kitty_keyboard = header.kitty_keyboard.terminal(),
|
||||
.semantic_prompt = .{
|
||||
.seen = false,
|
||||
.click = header.semantic_click.terminal(),
|
||||
.click = header.semantic_click,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -527,86 +526,19 @@ const PayloadDecodeError = style.DecodeError || error{
|
||||
InvalidSavedCursorFlags,
|
||||
};
|
||||
|
||||
/// Identifies which terminal screen the record restores.
|
||||
pub const Key = enum(u16) {
|
||||
primary = 0,
|
||||
alternate = 1,
|
||||
|
||||
fn init(value: TerminalScreenKey) Key {
|
||||
return switch (value) {
|
||||
.primary => .primary,
|
||||
.alternate => .alternate,
|
||||
};
|
||||
}
|
||||
|
||||
fn terminal(self: Key) TerminalScreenKey {
|
||||
return switch (self) {
|
||||
.primary => .primary,
|
||||
.alternate => .alternate,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Snapshot registry for the cursor's visual shape.
|
||||
pub const CursorStyle = enum(u8) {
|
||||
bar = 0,
|
||||
block = 1,
|
||||
underline = 2,
|
||||
block_hollow = 3,
|
||||
|
||||
fn init(value: TerminalScreen.CursorStyle) CursorStyle {
|
||||
return switch (value) {
|
||||
.bar => .bar,
|
||||
.block => .block,
|
||||
.underline => .underline,
|
||||
.block_hollow => .block_hollow,
|
||||
};
|
||||
}
|
||||
|
||||
fn terminal(self: CursorStyle) TerminalScreen.CursorStyle {
|
||||
return switch (self) {
|
||||
.bar => .bar,
|
||||
.block => .block,
|
||||
.underline => .underline,
|
||||
.block_hollow => .block_hollow,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Flags encoded after the cursor's visual shape.
|
||||
pub const CursorFlags = packed struct(u8) {
|
||||
pending_wrap: bool = false,
|
||||
protected: bool = false,
|
||||
semantic_content: SemanticContent = .output,
|
||||
semantic_content: terminal_page.Cell.SemanticContent = .output,
|
||||
semantic_content_clear_eol: bool = false,
|
||||
_padding: u3 = 0,
|
||||
|
||||
/// Snapshot registry for the cursor's semantic content.
|
||||
pub const SemanticContent = enum(u2) {
|
||||
output = 0,
|
||||
input = 1,
|
||||
prompt = 2,
|
||||
invalid = 3,
|
||||
|
||||
fn terminal(self: SemanticContent) terminal_page.Cell.SemanticContent {
|
||||
return switch (self) {
|
||||
.output => .output,
|
||||
.input => .input,
|
||||
.prompt => .prompt,
|
||||
.invalid => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn init(cursor: *const TerminalScreen.Cursor) CursorFlags {
|
||||
return .{
|
||||
.pending_wrap = cursor.pending_wrap,
|
||||
.protected = cursor.protected,
|
||||
.semantic_content = switch (cursor.semantic_content) {
|
||||
.output => .output,
|
||||
.input => .input,
|
||||
.prompt => .prompt,
|
||||
},
|
||||
.semantic_content = cursor.semantic_content,
|
||||
.semantic_content_clear_eol = cursor
|
||||
.semantic_content_clear_eol,
|
||||
};
|
||||
@@ -614,166 +546,143 @@ pub const CursorFlags = packed struct(u8) {
|
||||
|
||||
/// Decode and validate the cursor flag registry.
|
||||
pub fn decode(reader: *std.Io.Reader) PayloadDecodeError!CursorFlags {
|
||||
const result: CursorFlags = @bitCast(try reader.takeByte());
|
||||
const raw = try reader.takeByte();
|
||||
const semantic_content_raw: u2 = @truncate(raw >> 2);
|
||||
_ = std.enums.fromInt(
|
||||
terminal_page.Cell.SemanticContent,
|
||||
semantic_content_raw,
|
||||
) orelse return error.InvalidSemanticContent;
|
||||
|
||||
const result: CursorFlags = @bitCast(raw);
|
||||
if (result._padding != 0) return error.InvalidCursorFlags;
|
||||
if (result.semantic_content == .invalid) {
|
||||
return error.InvalidSemanticContent;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/// The packed charset state used by current and saved cursors.
|
||||
pub const CharsetState = packed struct(u16) {
|
||||
g0: Charset = .utf8,
|
||||
g1: Charset = .utf8,
|
||||
g2: Charset = .utf8,
|
||||
g3: Charset = .utf8,
|
||||
gl: Slot = .g0,
|
||||
gr: Slot = .g2,
|
||||
single_shift: SingleShift = .none,
|
||||
/// The packed wire bits shared by current and saved cursor charset state.
|
||||
///
|
||||
/// Native enum values are stable, but their backing type is `c_int` in C ABI
|
||||
/// builds. The wire therefore stores only checked integer values at its fixed
|
||||
/// bit widths.
|
||||
const CharsetBits = packed struct(u16) {
|
||||
g0: u2 = 0,
|
||||
g1: u2 = 0,
|
||||
g2: u2 = 0,
|
||||
g3: u2 = 0,
|
||||
gl: u2 = 0,
|
||||
gr: u2 = 2,
|
||||
single_shift: u3 = 0,
|
||||
_padding: u1 = 0,
|
||||
|
||||
/// Snapshot registry for one graphical character set.
|
||||
pub const Charset = enum(u2) {
|
||||
utf8 = 0,
|
||||
ascii = 1,
|
||||
british = 2,
|
||||
dec_special = 3,
|
||||
|
||||
fn terminal(self: Charset) terminal_charsets.Charset {
|
||||
return switch (self) {
|
||||
.utf8 => .utf8,
|
||||
.ascii => .ascii,
|
||||
.british => .british,
|
||||
.dec_special => .dec_special,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Snapshot registry for a G0 through G3 charset slot.
|
||||
pub const Slot = enum(u2) {
|
||||
g0 = 0,
|
||||
g1 = 1,
|
||||
g2 = 2,
|
||||
g3 = 3,
|
||||
|
||||
fn terminal(self: Slot) terminal_charsets.Slots {
|
||||
return switch (self) {
|
||||
.g0 => .G0,
|
||||
.g1 => .G1,
|
||||
.g2 => .G2,
|
||||
.g3 => .G3,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Snapshot registry for the single-shift charset slot.
|
||||
pub const SingleShift = enum(u3) {
|
||||
none = 0,
|
||||
g0 = 1,
|
||||
g1 = 2,
|
||||
g2 = 3,
|
||||
g3 = 4,
|
||||
_,
|
||||
|
||||
fn terminal(self: SingleShift) ?terminal_charsets.Slots {
|
||||
return switch (self) {
|
||||
.none => null,
|
||||
.g0 => .G0,
|
||||
.g1 => .G1,
|
||||
.g2 => .G2,
|
||||
.g3 => .G3,
|
||||
_ => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn init(value: TerminalScreen.CharsetState) CharsetState {
|
||||
return .{
|
||||
.g0 = charset(value.charsets.get(.G0)),
|
||||
.g1 = charset(value.charsets.get(.G1)),
|
||||
.g2 = charset(value.charsets.get(.G2)),
|
||||
.g3 = charset(value.charsets.get(.G3)),
|
||||
.gl = slot(value.gl),
|
||||
.gr = slot(value.gr),
|
||||
.single_shift = if (value.single_shift) |value_slot|
|
||||
switch (value_slot) {
|
||||
.G0 => .g0,
|
||||
.G1 => .g1,
|
||||
.G2 => .g2,
|
||||
.G3 => .g3,
|
||||
}
|
||||
else
|
||||
.none,
|
||||
};
|
||||
}
|
||||
|
||||
fn terminal(self: CharsetState) TerminalScreen.CharsetState {
|
||||
var result: TerminalScreen.CharsetState = .{
|
||||
.gl = self.gl.terminal(),
|
||||
.gr = self.gr.terminal(),
|
||||
.single_shift = self.single_shift.terminal(),
|
||||
};
|
||||
result.charsets.set(.G0, self.g0.terminal());
|
||||
result.charsets.set(.G1, self.g1.terminal());
|
||||
result.charsets.set(.G2, self.g2.terminal());
|
||||
result.charsets.set(.G3, self.g3.terminal());
|
||||
return result;
|
||||
}
|
||||
|
||||
fn charset(value: terminal_charsets.Charset) Charset {
|
||||
return switch (value) {
|
||||
.utf8 => .utf8,
|
||||
.ascii => .ascii,
|
||||
.british => .british,
|
||||
.dec_special => .dec_special,
|
||||
};
|
||||
}
|
||||
|
||||
fn slot(value: terminal_charsets.Slots) Slot {
|
||||
return switch (value) {
|
||||
.G0 => .g0,
|
||||
.G1 => .g1,
|
||||
.G2 => .g2,
|
||||
.G3 => .g3,
|
||||
};
|
||||
}
|
||||
|
||||
/// Decode and validate the packed charset registry.
|
||||
pub fn decode(reader: *std.Io.Reader) PayloadDecodeError!CharsetState {
|
||||
const result: CharsetState = @bitCast(try io.readInt(reader, u16));
|
||||
if (result._padding != 0) return error.InvalidCharsetState;
|
||||
switch (result.single_shift) {
|
||||
.none, .g0, .g1, .g2, .g3 => {},
|
||||
_ => return error.InvalidCharsetState,
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/// Snapshot registry for selective-erase protection behavior.
|
||||
pub const ProtectedMode = enum(u8) {
|
||||
off = 0,
|
||||
iso = 1,
|
||||
dec = 2,
|
||||
fn enumFromInt(comptime T: type, raw: anytype) ?T {
|
||||
const Tag = @typeInfo(T).@"enum".tag_type;
|
||||
const value = std.math.cast(Tag, raw) orelse return null;
|
||||
return std.enums.fromInt(T, value);
|
||||
}
|
||||
|
||||
fn init(value: terminal_ansi.ProtectedMode) ProtectedMode {
|
||||
return switch (value) {
|
||||
.off => .off,
|
||||
.iso => .iso,
|
||||
.dec => .dec,
|
||||
};
|
||||
fn encodeCharsetState(
|
||||
value: TerminalScreen.CharsetState,
|
||||
) error{InvalidCharsetState}!u16 {
|
||||
const g0 = std.math.cast(
|
||||
u2,
|
||||
@intFromEnum(value.charsets.get(.G0)),
|
||||
) orelse return error.InvalidCharsetState;
|
||||
const g1 = std.math.cast(
|
||||
u2,
|
||||
@intFromEnum(value.charsets.get(.G1)),
|
||||
) orelse return error.InvalidCharsetState;
|
||||
const g2 = std.math.cast(
|
||||
u2,
|
||||
@intFromEnum(value.charsets.get(.G2)),
|
||||
) orelse return error.InvalidCharsetState;
|
||||
const g3 = std.math.cast(
|
||||
u2,
|
||||
@intFromEnum(value.charsets.get(.G3)),
|
||||
) orelse return error.InvalidCharsetState;
|
||||
const gl = std.math.cast(
|
||||
u2,
|
||||
@intFromEnum(value.gl),
|
||||
) orelse return error.InvalidCharsetState;
|
||||
const gr = std.math.cast(
|
||||
u2,
|
||||
@intFromEnum(value.gr),
|
||||
) orelse return error.InvalidCharsetState;
|
||||
const single_shift: u3 = if (value.single_shift) |slot| single_shift: {
|
||||
const raw = std.math.cast(
|
||||
u2,
|
||||
@intFromEnum(slot),
|
||||
) orelse return error.InvalidCharsetState;
|
||||
break :single_shift @as(u3, raw) + 1;
|
||||
} else 0;
|
||||
|
||||
const bits: CharsetBits = .{
|
||||
.g0 = g0,
|
||||
.g1 = g1,
|
||||
.g2 = g2,
|
||||
.g3 = g3,
|
||||
.gl = gl,
|
||||
.gr = gr,
|
||||
.single_shift = single_shift,
|
||||
};
|
||||
return @bitCast(bits);
|
||||
}
|
||||
|
||||
fn decodeCharsetState(
|
||||
raw: u16,
|
||||
) error{InvalidCharsetState}!TerminalScreen.CharsetState {
|
||||
const bits: CharsetBits = @bitCast(raw);
|
||||
if (bits._padding != 0 or bits.single_shift > 4) {
|
||||
return error.InvalidCharsetState;
|
||||
}
|
||||
|
||||
fn terminal(self: ProtectedMode) terminal_ansi.ProtectedMode {
|
||||
return switch (self) {
|
||||
.off => .off,
|
||||
.iso => .iso,
|
||||
.dec => .dec,
|
||||
};
|
||||
}
|
||||
};
|
||||
var result: TerminalScreen.CharsetState = .{
|
||||
.gl = enumFromInt(
|
||||
terminal_charsets.Slots,
|
||||
bits.gl,
|
||||
) orelse return error.InvalidCharsetState,
|
||||
.gr = enumFromInt(
|
||||
terminal_charsets.Slots,
|
||||
bits.gr,
|
||||
) orelse return error.InvalidCharsetState,
|
||||
.single_shift = if (bits.single_shift == 0)
|
||||
null
|
||||
else
|
||||
enumFromInt(
|
||||
terminal_charsets.Slots,
|
||||
bits.single_shift - 1,
|
||||
) orelse return error.InvalidCharsetState,
|
||||
};
|
||||
result.charsets.set(
|
||||
.G0,
|
||||
enumFromInt(
|
||||
terminal_charsets.Charset,
|
||||
bits.g0,
|
||||
) orelse return error.InvalidCharsetState,
|
||||
);
|
||||
result.charsets.set(
|
||||
.G1,
|
||||
enumFromInt(
|
||||
terminal_charsets.Charset,
|
||||
bits.g1,
|
||||
) orelse return error.InvalidCharsetState,
|
||||
);
|
||||
result.charsets.set(
|
||||
.G2,
|
||||
enumFromInt(
|
||||
terminal_charsets.Charset,
|
||||
bits.g2,
|
||||
) orelse return error.InvalidCharsetState,
|
||||
);
|
||||
result.charsets.set(
|
||||
.G3,
|
||||
enumFromInt(
|
||||
terminal_charsets.Charset,
|
||||
bits.g3,
|
||||
) orelse return error.InvalidCharsetState,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// The complete fixed-size Kitty keyboard stack.
|
||||
pub const KittyKeyboard = struct {
|
||||
@@ -840,98 +749,31 @@ pub const KittyKeyboard = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// Selects the interpretation of the semantic-click value byte.
|
||||
pub const SemanticClickKind = enum(u8) {
|
||||
none = 0,
|
||||
click_events = 1,
|
||||
cl = 2,
|
||||
};
|
||||
/// Decode and validate the semantic-click kind and value bytes.
|
||||
fn decodeSemanticClick(
|
||||
reader: *std.Io.Reader,
|
||||
) PayloadDecodeError!TerminalScreen.SemanticPrompt.SemanticClick {
|
||||
const kind = enumFromInt(
|
||||
TerminalScreen.SemanticPrompt.SemanticClickKind,
|
||||
try reader.takeByte(),
|
||||
) orelse return error.InvalidSemanticClick;
|
||||
const value = try reader.takeByte();
|
||||
|
||||
/// Snapshot registry for the OSC 133 `click_events` option.
|
||||
pub const ClickEvents = enum(u8) {
|
||||
absolute = 0,
|
||||
relative = 1,
|
||||
};
|
||||
|
||||
/// Snapshot registry for the OSC 133 `cl` option.
|
||||
pub const Click = enum(u8) {
|
||||
line = 0,
|
||||
multiple = 1,
|
||||
conservative_vertical = 2,
|
||||
smart_vertical = 3,
|
||||
};
|
||||
|
||||
/// The typed semantic-click kind and value from the fixed header.
|
||||
pub const SemanticClick = union(SemanticClickKind) {
|
||||
none,
|
||||
click_events: ClickEvents,
|
||||
cl: Click,
|
||||
|
||||
fn init(
|
||||
value: TerminalScreen.SemanticPrompt.SemanticClick,
|
||||
) SemanticClick {
|
||||
return switch (value) {
|
||||
.none => .none,
|
||||
.click_events => |click_events| .{
|
||||
.click_events = switch (click_events) {
|
||||
.absolute => .absolute,
|
||||
.relative => .relative,
|
||||
},
|
||||
},
|
||||
.cl => |click| .{
|
||||
.cl = switch (click) {
|
||||
.line => .line,
|
||||
.multiple => .multiple,
|
||||
.conservative_vertical => .conservative_vertical,
|
||||
.smart_vertical => .smart_vertical,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn terminal(
|
||||
self: SemanticClick,
|
||||
) TerminalScreen.SemanticPrompt.SemanticClick {
|
||||
return switch (self) {
|
||||
.none => .none,
|
||||
.click_events => |value| .{ .click_events = switch (value) {
|
||||
.absolute => .absolute,
|
||||
.relative => .relative,
|
||||
} },
|
||||
.cl => |value| .{ .cl = switch (value) {
|
||||
.line => .line,
|
||||
.multiple => .multiple,
|
||||
.conservative_vertical => .conservative_vertical,
|
||||
.smart_vertical => .smart_vertical,
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
/// Decode and validate the semantic-click kind and value bytes.
|
||||
pub fn decode(reader: *std.Io.Reader) PayloadDecodeError!SemanticClick {
|
||||
const kind_raw = try reader.takeByte();
|
||||
const value_raw = try reader.takeByte();
|
||||
const kind = std.enums.fromInt(
|
||||
SemanticClickKind,
|
||||
kind_raw,
|
||||
) orelse return error.InvalidSemanticClick;
|
||||
|
||||
return switch (kind) {
|
||||
.none => if (value_raw == 0)
|
||||
.none
|
||||
else
|
||||
error.InvalidSemanticClick,
|
||||
.click_events => .{ .click_events = std.enums.fromInt(
|
||||
ClickEvents,
|
||||
value_raw,
|
||||
) orelse return error.InvalidSemanticClick },
|
||||
.cl => .{ .cl = std.enums.fromInt(
|
||||
Click,
|
||||
value_raw,
|
||||
) orelse return error.InvalidSemanticClick },
|
||||
};
|
||||
}
|
||||
};
|
||||
return switch (kind) {
|
||||
.none => if (value == 0)
|
||||
.none
|
||||
else
|
||||
error.InvalidSemanticClick,
|
||||
.click_events => .{ .click_events = enumFromInt(
|
||||
terminal_osc.semantic_prompt.ClickEvents,
|
||||
value,
|
||||
) orelse return error.InvalidSemanticClick },
|
||||
.cl => .{ .cl = enumFromInt(
|
||||
terminal_osc.semantic_prompt.Click,
|
||||
value,
|
||||
) orelse return error.InvalidSemanticClick },
|
||||
};
|
||||
}
|
||||
|
||||
/// The optional fixed-size saved cursor state.
|
||||
pub const SavedCursor = struct {
|
||||
@@ -946,7 +788,7 @@ pub const SavedCursor = struct {
|
||||
y: u16,
|
||||
pen: TerminalStyle,
|
||||
flags: Flags,
|
||||
charset: CharsetState,
|
||||
charset: TerminalScreen.CharsetState,
|
||||
|
||||
/// Flags encoded with an optional saved cursor.
|
||||
pub const Flags = packed struct(u8) {
|
||||
@@ -973,7 +815,7 @@ pub const SavedCursor = struct {
|
||||
.pending_wrap = value.pending_wrap,
|
||||
.origin = value.origin,
|
||||
},
|
||||
.charset = .init(value.charset),
|
||||
.charset = value.charset,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -985,7 +827,7 @@ pub const SavedCursor = struct {
|
||||
.protected = self.flags.protected,
|
||||
.pending_wrap = self.flags.pending_wrap,
|
||||
.origin = self.flags.origin,
|
||||
.charset = self.charset.terminal(),
|
||||
.charset = self.charset,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -997,17 +839,13 @@ pub const SavedCursor = struct {
|
||||
if (self.flags._padding != 0) {
|
||||
return error.InvalidSavedCursorFlags;
|
||||
}
|
||||
if (self.charset._padding != 0) return error.InvalidCharsetState;
|
||||
switch (self.charset.single_shift) {
|
||||
.none, .g0, .g1, .g2, .g3 => {},
|
||||
_ => return error.InvalidCharsetState,
|
||||
}
|
||||
const charset = try encodeCharsetState(self.charset);
|
||||
|
||||
try io.writeInt(writer, u16, self.x);
|
||||
try io.writeInt(writer, u16, self.y);
|
||||
try style.encode(self.pen, writer);
|
||||
try writer.writeByte(@bitCast(self.flags));
|
||||
try io.writeInt(writer, u16, @bitCast(self.charset));
|
||||
try io.writeInt(writer, u16, charset);
|
||||
}
|
||||
|
||||
/// Decode and validate one optional saved cursor value.
|
||||
@@ -1017,7 +855,9 @@ pub const SavedCursor = struct {
|
||||
.y = try io.readInt(reader, u16),
|
||||
.pen = try style.decode(reader),
|
||||
.flags = try Flags.decode(reader),
|
||||
.charset = try CharsetState.decode(reader),
|
||||
.charset = try decodeCharsetState(
|
||||
try io.readInt(reader, u16),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1047,19 +887,19 @@ pub const Header = struct {
|
||||
std.debug.assert(len == 45);
|
||||
}
|
||||
|
||||
key: Key,
|
||||
key: TerminalScreenKey,
|
||||
/// Complete pages in the minimal suffix covering the active area.
|
||||
page_count: u16,
|
||||
cursor_x: u16,
|
||||
cursor_y: u16,
|
||||
cursor_style: CursorStyle,
|
||||
cursor_style: TerminalScreen.CursorStyle,
|
||||
cursor_flags: CursorFlags,
|
||||
cursor_pen: TerminalStyle,
|
||||
hyperlink_implicit_id: u32,
|
||||
charset: CharsetState,
|
||||
protected_mode: ProtectedMode,
|
||||
charset: TerminalScreen.CharsetState,
|
||||
protected_mode: terminal_ansi.ProtectedMode,
|
||||
kitty_keyboard: KittyKeyboard,
|
||||
semantic_click: SemanticClick,
|
||||
semantic_click: TerminalScreen.SemanticPrompt.SemanticClick,
|
||||
saved_cursor_present: bool,
|
||||
|
||||
/// Initialize the fixed header from one native screen.
|
||||
@@ -1069,18 +909,18 @@ pub const Header = struct {
|
||||
page_count: u16,
|
||||
) Header {
|
||||
return .{
|
||||
.key = .init(key),
|
||||
.key = key,
|
||||
.page_count = page_count,
|
||||
.cursor_x = screen.cursor.x,
|
||||
.cursor_y = screen.cursor.y,
|
||||
.cursor_style = .init(screen.cursor.cursor_style),
|
||||
.cursor_style = screen.cursor.cursor_style,
|
||||
.cursor_flags = .init(&screen.cursor),
|
||||
.cursor_pen = screen.cursor.style,
|
||||
.hyperlink_implicit_id = screen.cursor.hyperlink_implicit_id,
|
||||
.charset = .init(screen.charset),
|
||||
.protected_mode = .init(screen.protected_mode),
|
||||
.charset = screen.charset,
|
||||
.protected_mode = screen.protected_mode,
|
||||
.kitty_keyboard = .init(screen.kitty_keyboard),
|
||||
.semantic_click = .init(screen.semantic_prompt.click),
|
||||
.semantic_click = screen.semantic_prompt.click,
|
||||
.saved_cursor_present = screen.saved_cursor != null,
|
||||
};
|
||||
}
|
||||
@@ -1094,16 +934,9 @@ pub const Header = struct {
|
||||
if (self.cursor_flags._padding != 0) {
|
||||
return error.InvalidCursorFlags;
|
||||
}
|
||||
if (self.cursor_flags.semantic_content == .invalid) {
|
||||
return error.InvalidSemanticContent;
|
||||
}
|
||||
|
||||
// Validate packed charset state.
|
||||
if (self.charset._padding != 0) return error.InvalidCharsetState;
|
||||
switch (self.charset.single_shift) {
|
||||
.none, .g0, .g1, .g2, .g3 => {},
|
||||
_ => return error.InvalidCharsetState,
|
||||
}
|
||||
// Validate and narrow native charset enum values before writing.
|
||||
const charset = try encodeCharsetState(self.charset);
|
||||
|
||||
// Validate the complete Kitty keyboard stack.
|
||||
if (self.kitty_keyboard.index >= self.kitty_keyboard.flags.len) {
|
||||
@@ -1116,41 +949,37 @@ pub const Header = struct {
|
||||
}
|
||||
|
||||
// Screen identity and cursor position.
|
||||
try io.writeInt(writer, u16, @intFromEnum(self.key));
|
||||
try io.writeInt(writer, u16, @intCast(@intFromEnum(self.key)));
|
||||
try io.writeInt(writer, u16, self.page_count);
|
||||
try io.writeInt(writer, u16, self.cursor_x);
|
||||
try io.writeInt(writer, u16, self.cursor_y);
|
||||
|
||||
// Current cursor rendering state.
|
||||
try writer.writeByte(@intFromEnum(self.cursor_style));
|
||||
try writer.writeByte(@intCast(@intFromEnum(self.cursor_style)));
|
||||
try writer.writeByte(@bitCast(self.cursor_flags));
|
||||
try style.encode(self.cursor_pen, writer);
|
||||
try io.writeInt(writer, u32, self.hyperlink_implicit_id);
|
||||
|
||||
// Charset and selective-erase state.
|
||||
try io.writeInt(writer, u16, @bitCast(self.charset));
|
||||
try writer.writeByte(@intFromEnum(self.protected_mode));
|
||||
try io.writeInt(writer, u16, charset);
|
||||
try writer.writeByte(@intCast(@intFromEnum(self.protected_mode)));
|
||||
|
||||
// Keyboard modes and semantic-click state.
|
||||
try writer.writeByte(self.kitty_keyboard.index);
|
||||
for (self.kitty_keyboard.flags) |flags| {
|
||||
try writer.writeByte(@bitCast(flags));
|
||||
}
|
||||
try writer.writeByte(@intCast(@intFromEnum(
|
||||
std.meta.activeTag(self.semantic_click),
|
||||
)));
|
||||
switch (self.semantic_click) {
|
||||
.none => {
|
||||
try writer.writeByte(@intFromEnum(SemanticClickKind.none));
|
||||
try writer.writeByte(0);
|
||||
},
|
||||
.click_events => |value| {
|
||||
try writer.writeByte(
|
||||
@intFromEnum(SemanticClickKind.click_events),
|
||||
);
|
||||
try writer.writeByte(@intFromEnum(value));
|
||||
},
|
||||
.cl => |value| {
|
||||
try writer.writeByte(@intFromEnum(SemanticClickKind.cl));
|
||||
try writer.writeByte(@intFromEnum(value));
|
||||
},
|
||||
.none => try writer.writeByte(0),
|
||||
.click_events => |value| try writer.writeByte(
|
||||
@intCast(@intFromEnum(value)),
|
||||
),
|
||||
.cl => |value| try writer.writeByte(
|
||||
@intCast(@intFromEnum(value)),
|
||||
),
|
||||
}
|
||||
|
||||
// Presence of the optional saved-cursor suffix.
|
||||
@@ -1160,8 +989,8 @@ pub const Header = struct {
|
||||
/// Decode and validate the fixed SCREEN payload header.
|
||||
pub fn decode(reader: *std.Io.Reader) PayloadDecodeError!Header {
|
||||
// Screen identity and cursor position.
|
||||
const key = std.enums.fromInt(
|
||||
Key,
|
||||
const key = enumFromInt(
|
||||
TerminalScreenKey,
|
||||
try io.readInt(reader, u16),
|
||||
) orelse return error.InvalidKey;
|
||||
const page_count = try io.readInt(reader, u16);
|
||||
@@ -1169,8 +998,8 @@ pub const Header = struct {
|
||||
const cursor_y = try io.readInt(reader, u16);
|
||||
|
||||
// Current cursor rendering state.
|
||||
const cursor_style = std.enums.fromInt(
|
||||
CursorStyle,
|
||||
const cursor_style = enumFromInt(
|
||||
TerminalScreen.CursorStyle,
|
||||
try reader.takeByte(),
|
||||
) orelse return error.InvalidCursorStyle;
|
||||
const cursor_flags = try CursorFlags.decode(reader);
|
||||
@@ -1178,15 +1007,17 @@ pub const Header = struct {
|
||||
const hyperlink_implicit_id = try io.readInt(reader, u32);
|
||||
|
||||
// Charset and selective-erase state.
|
||||
const charset = try CharsetState.decode(reader);
|
||||
const protected_mode = std.enums.fromInt(
|
||||
ProtectedMode,
|
||||
const charset = try decodeCharsetState(
|
||||
try io.readInt(reader, u16),
|
||||
);
|
||||
const protected_mode = enumFromInt(
|
||||
terminal_ansi.ProtectedMode,
|
||||
try reader.takeByte(),
|
||||
) orelse return error.InvalidProtectedMode;
|
||||
|
||||
// Keyboard modes and semantic-click state.
|
||||
const kitty_keyboard = try KittyKeyboard.decode(reader);
|
||||
const semantic_click = try SemanticClick.decode(reader);
|
||||
const semantic_click = try decodeSemanticClick(reader);
|
||||
|
||||
// Presence of the optional saved-cursor suffix.
|
||||
const saved_cursor_present = switch (try reader.takeByte()) {
|
||||
@@ -1299,16 +1130,17 @@ const test_saved_cursor_fixture =
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" ++
|
||||
"\x07\xe4\x3d";
|
||||
|
||||
fn testCharsetState() CharsetState {
|
||||
return .{
|
||||
.g0 = .utf8,
|
||||
.g1 = .ascii,
|
||||
.g2 = .british,
|
||||
.g3 = .dec_special,
|
||||
.gl = .g1,
|
||||
.gr = .g3,
|
||||
.single_shift = .g2,
|
||||
fn testCharsetState() TerminalScreen.CharsetState {
|
||||
var result: TerminalScreen.CharsetState = .{
|
||||
.gl = .G1,
|
||||
.gr = .G3,
|
||||
.single_shift = .G2,
|
||||
};
|
||||
result.charsets.set(.G0, .utf8);
|
||||
result.charsets.set(.G1, .ascii);
|
||||
result.charsets.set(.G2, .british);
|
||||
result.charsets.set(.G3, .dec_special);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn testHeader() Header {
|
||||
@@ -1443,14 +1275,14 @@ test "header decoding with a one-byte reader buffer" {
|
||||
}
|
||||
|
||||
test "header registry values round trip" {
|
||||
const keys = [_]Key{ .primary, .alternate };
|
||||
const keys = [_]TerminalScreenKey{ .primary, .alternate };
|
||||
for (keys) |value| {
|
||||
var header = testHeader();
|
||||
header.key = value;
|
||||
try expectHeaderRoundTrip(header);
|
||||
}
|
||||
|
||||
const cursor_styles = [_]CursorStyle{
|
||||
const cursor_styles = [_]TerminalScreen.CursorStyle{
|
||||
.bar,
|
||||
.block,
|
||||
.underline,
|
||||
@@ -1462,7 +1294,7 @@ test "header registry values round trip" {
|
||||
try expectHeaderRoundTrip(header);
|
||||
}
|
||||
|
||||
const semantic_contents = [_]CursorFlags.SemanticContent{
|
||||
const semantic_contents = [_]terminal_page.Cell.SemanticContent{
|
||||
.output,
|
||||
.input,
|
||||
.prompt,
|
||||
@@ -1473,7 +1305,7 @@ test "header registry values round trip" {
|
||||
try expectHeaderRoundTrip(header);
|
||||
}
|
||||
|
||||
const charsets = [_]CharsetState.Charset{
|
||||
const charsets = [_]terminal_charsets.Charset{
|
||||
.utf8,
|
||||
.ascii,
|
||||
.british,
|
||||
@@ -1481,14 +1313,14 @@ test "header registry values round trip" {
|
||||
};
|
||||
for (charsets) |value| {
|
||||
var header = testHeader();
|
||||
header.charset.g0 = value;
|
||||
header.charset.g1 = value;
|
||||
header.charset.g2 = value;
|
||||
header.charset.g3 = value;
|
||||
header.charset.charsets.set(.G0, value);
|
||||
header.charset.charsets.set(.G1, value);
|
||||
header.charset.charsets.set(.G2, value);
|
||||
header.charset.charsets.set(.G3, value);
|
||||
try expectHeaderRoundTrip(header);
|
||||
}
|
||||
|
||||
const slots = [_]CharsetState.Slot{ .g0, .g1, .g2, .g3 };
|
||||
const slots = [_]terminal_charsets.Slots{ .G0, .G1, .G2, .G3 };
|
||||
for (slots) |value| {
|
||||
var header = testHeader();
|
||||
header.charset.gl = value;
|
||||
@@ -1496,12 +1328,12 @@ test "header registry values round trip" {
|
||||
try expectHeaderRoundTrip(header);
|
||||
}
|
||||
|
||||
const single_shifts = [_]CharsetState.SingleShift{
|
||||
.none,
|
||||
.g0,
|
||||
.g1,
|
||||
.g2,
|
||||
.g3,
|
||||
const single_shifts = [_]?terminal_charsets.Slots{
|
||||
null,
|
||||
.G0,
|
||||
.G1,
|
||||
.G2,
|
||||
.G3,
|
||||
};
|
||||
for (single_shifts) |value| {
|
||||
var header = testHeader();
|
||||
@@ -1509,22 +1341,27 @@ test "header registry values round trip" {
|
||||
try expectHeaderRoundTrip(header);
|
||||
}
|
||||
|
||||
const protected_modes = [_]ProtectedMode{ .off, .iso, .dec };
|
||||
const protected_modes = [_]terminal_ansi.ProtectedMode{
|
||||
.off,
|
||||
.iso,
|
||||
.dec,
|
||||
};
|
||||
for (protected_modes) |value| {
|
||||
var header = testHeader();
|
||||
header.protected_mode = value;
|
||||
try expectHeaderRoundTrip(header);
|
||||
}
|
||||
|
||||
const semantic_clicks = [_]SemanticClick{
|
||||
.none,
|
||||
.{ .click_events = .absolute },
|
||||
.{ .click_events = .relative },
|
||||
.{ .cl = .line },
|
||||
.{ .cl = .multiple },
|
||||
.{ .cl = .conservative_vertical },
|
||||
.{ .cl = .smart_vertical },
|
||||
};
|
||||
const semantic_clicks =
|
||||
[_]TerminalScreen.SemanticPrompt.SemanticClick{
|
||||
.none,
|
||||
.{ .click_events = .absolute },
|
||||
.{ .click_events = .relative },
|
||||
.{ .cl = .line },
|
||||
.{ .cl = .multiple },
|
||||
.{ .cl = .conservative_vertical },
|
||||
.{ .cl = .smart_vertical },
|
||||
};
|
||||
for (semantic_clicks) |value| {
|
||||
var header = testHeader();
|
||||
header.semantic_click = value;
|
||||
@@ -1591,30 +1428,6 @@ test "header encoding rejects invalid state" {
|
||||
invalid_cursor_flags.encode(&cursor_flags_writer),
|
||||
);
|
||||
|
||||
var invalid_semantic_content = testHeader();
|
||||
invalid_semantic_content.cursor_flags.semantic_content = .invalid;
|
||||
var semantic_content_writer: std.Io.Writer = .fixed(&encoded);
|
||||
try std.testing.expectError(
|
||||
error.InvalidSemanticContent,
|
||||
invalid_semantic_content.encode(&semantic_content_writer),
|
||||
);
|
||||
|
||||
var invalid_charset = testHeader();
|
||||
invalid_charset.charset._padding = 1;
|
||||
var charset_writer: std.Io.Writer = .fixed(&encoded);
|
||||
try std.testing.expectError(
|
||||
error.InvalidCharsetState,
|
||||
invalid_charset.encode(&charset_writer),
|
||||
);
|
||||
|
||||
var invalid_single_shift = testHeader();
|
||||
invalid_single_shift.charset.single_shift = @enumFromInt(5);
|
||||
var single_shift_writer: std.Io.Writer = .fixed(&encoded);
|
||||
try std.testing.expectError(
|
||||
error.InvalidCharsetState,
|
||||
invalid_single_shift.encode(&single_shift_writer),
|
||||
);
|
||||
|
||||
var invalid_kitty_index = testHeader();
|
||||
invalid_kitty_index.kitty_keyboard.index = 8;
|
||||
var kitty_index_writer: std.Io.Writer = .fixed(&encoded);
|
||||
@@ -1781,7 +1594,7 @@ test "native SCREEN payload omits absent optional state" {
|
||||
|
||||
var reader: std.Io.Reader = .fixed(writer.buffered());
|
||||
const header = try Header.decode(&reader);
|
||||
try std.testing.expectEqual(Key.primary, header.key);
|
||||
try std.testing.expectEqual(TerminalScreenKey.primary, header.key);
|
||||
try std.testing.expectEqual(@as(u16, 1), header.page_count);
|
||||
try std.testing.expect(!header.saved_cursor_present);
|
||||
try std.testing.expectEqual(
|
||||
@@ -1824,22 +1637,6 @@ test "saved cursor rejects invalid state" {
|
||||
invalid_flags.encode(&flags_writer),
|
||||
);
|
||||
|
||||
var invalid_charset = testSavedCursor();
|
||||
invalid_charset.charset._padding = 1;
|
||||
var charset_writer: std.Io.Writer = .fixed(&encoded);
|
||||
try std.testing.expectError(
|
||||
error.InvalidCharsetState,
|
||||
invalid_charset.encode(&charset_writer),
|
||||
);
|
||||
|
||||
var invalid_single_shift_value = testSavedCursor();
|
||||
invalid_single_shift_value.charset.single_shift = @enumFromInt(5);
|
||||
var single_shift_value_writer: std.Io.Writer = .fixed(&encoded);
|
||||
try std.testing.expectError(
|
||||
error.InvalidCharsetState,
|
||||
invalid_single_shift_value.encode(&single_shift_value_writer),
|
||||
);
|
||||
|
||||
const valid = [_]u8{0} ** SavedCursor.len;
|
||||
|
||||
var invalid_flags_bytes = valid;
|
||||
@@ -2176,7 +1973,7 @@ test "SCREEN encodes the minimal complete-page active suffix" {
|
||||
|
||||
const payload_reader = screen_record.payloadReader();
|
||||
const header = try Header.decode(payload_reader);
|
||||
try std.testing.expectEqual(Key.primary, header.key);
|
||||
try std.testing.expectEqual(TerminalScreenKey.primary, header.key);
|
||||
try std.testing.expectEqual(@as(u16, 2), header.page_count);
|
||||
try std.testing.expect(!header.saved_cursor_present);
|
||||
try std.testing.expectEqual(
|
||||
|
||||
Reference in New Issue
Block a user