terminal/tmux: many more output formats

pull/9860/head
Mitchell Hashimoto 2025-12-09 20:49:03 -08:00
parent 4c30c5aa76
commit bf46c4ebe7
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
1 changed files with 312 additions and 6 deletions

View File

@ -95,16 +95,107 @@ pub fn FormatStruct(comptime vars: []const Variable) type {
/// a subset of them here that are relevant to the use case of implementing /// a subset of them here that are relevant to the use case of implementing
/// control mode for terminal emulators. /// control mode for terminal emulators.
pub const Variable = enum { pub const Variable = enum {
/// 1 if pane is in alternate screen.
alternate_on,
/// Saved cursor X in alternate screen.
alternate_saved_x,
/// Saved cursor Y in alternate screen.
alternate_saved_y,
/// 1 if bracketed paste mode is enabled.
bracketed_paste,
/// 1 if the cursor is blinking.
cursor_blinking,
/// Cursor colour in pane. Possible formats:
/// - Named colors: `black`, `red`, `green`, `yellow`, `blue`, `magenta`,
/// `cyan`, `white`, `default`, `terminal`, or bright variants.
/// - 256 colors: `colour<N>` where N is 0-255 (e.g., `colour100`).
/// - RGB hex: `#RRGGBB` (e.g., `#ff0000`).
/// - Empty string if unset.
cursor_colour,
/// Pane cursor flag.
cursor_flag,
/// Cursor shape in pane. Possible values: `block`, `underline`, `bar`,
/// or `default`.
cursor_shape,
/// Cursor X position in pane.
cursor_x,
/// Cursor Y position in pane.
cursor_y,
/// 1 if focus reporting is enabled.
focus_flag,
/// Pane insert flag.
insert_flag,
/// Pane keypad cursor flag.
keypad_cursor_flag,
/// Pane keypad flag.
keypad_flag,
/// Pane mouse all flag.
mouse_all_flag,
/// Pane mouse any flag.
mouse_any_flag,
/// Pane mouse button flag.
mouse_button_flag,
/// Pane mouse SGR flag.
mouse_sgr_flag,
/// Pane mouse standard flag.
mouse_standard_flag,
/// Pane mouse UTF-8 flag.
mouse_utf8_flag,
/// Pane origin flag.
origin_flag,
/// Unique pane ID prefixed with `%` (e.g., `%0`, `%42`).
pane_id,
/// Pane tab positions as a comma-separated list of 0-indexed column
/// numbers (e.g., `8,16,24,32`). Empty string if no tabs are set.
pane_tabs,
/// Bottom of scroll region in pane.
scroll_region_lower,
/// Top of scroll region in pane.
scroll_region_upper,
/// Unique session ID prefixed with `$` (e.g., `$0`, `$42`).
session_id, session_id,
/// Unique window ID prefixed with `@` (e.g., `@0`, `@42`).
window_id, window_id,
/// Width of window.
window_width, window_width,
/// Height of window.
window_height, window_height,
/// Window layout description, ignoring zoomed window panes. Format is
/// `<checksum>,<layout>` where checksum is a 4-digit hex CRC16 and layout
/// encodes pane dimensions as `WxH,X,Y[,ID]` with `{...}` for horizontal
/// splits and `[...]` for vertical splits.
window_layout, window_layout,
/// Pane wrap flag.
wrap_flag,
/// Parse the given string value into the appropriate resulting /// Parse the given string value into the appropriate resulting
/// type for this variable. /// type for this variable.
pub fn parse(comptime self: Variable, value: []const u8) !Type(self) { pub fn parse(comptime self: Variable, value: []const u8) !Type(self) {
return switch (self) { return switch (self) {
.alternate_on,
.bracketed_paste,
.cursor_blinking,
.cursor_flag,
.focus_flag,
.insert_flag,
.keypad_cursor_flag,
.keypad_flag,
.mouse_all_flag,
.mouse_any_flag,
.mouse_button_flag,
.mouse_sgr_flag,
.mouse_standard_flag,
.mouse_utf8_flag,
.origin_flag,
.wrap_flag,
=> std.mem.eql(u8, value, "1"),
.alternate_saved_x,
.alternate_saved_y,
.cursor_x,
.cursor_y,
.scroll_region_lower,
.scroll_region_upper,
=> try std.fmt.parseInt(usize, value, 10),
.session_id => if (value.len >= 2 and value[0] == '$') .session_id => if (value.len >= 2 and value[0] == '$')
try std.fmt.parseInt(usize, value[1..], 10) try std.fmt.parseInt(usize, value[1..], 10)
else else
@ -113,24 +204,105 @@ pub const Variable = enum {
try std.fmt.parseInt(usize, value[1..], 10) try std.fmt.parseInt(usize, value[1..], 10)
else else
return error.FormatError, return error.FormatError,
.pane_id => if (value.len >= 2 and value[0] == '%')
try std.fmt.parseInt(usize, value[1..], 10)
else
return error.FormatError,
.window_width => try std.fmt.parseInt(usize, value, 10), .window_width => try std.fmt.parseInt(usize, value, 10),
.window_height => try std.fmt.parseInt(usize, value, 10), .window_height => try std.fmt.parseInt(usize, value, 10),
.window_layout => value, .cursor_colour,
.cursor_shape,
.pane_tabs,
.window_layout,
=> value,
}; };
} }
/// The type of the parsed value for this variable type. /// The type of the parsed value for this variable type.
pub fn Type(comptime self: Variable) type { pub fn Type(comptime self: Variable) type {
return switch (self) { return switch (self) {
.session_id => usize, .alternate_on,
.window_id => usize, .bracketed_paste,
.window_width => usize, .cursor_blinking,
.window_height => usize, .cursor_flag,
.window_layout => []const u8, .focus_flag,
.insert_flag,
.keypad_cursor_flag,
.keypad_flag,
.mouse_all_flag,
.mouse_any_flag,
.mouse_button_flag,
.mouse_sgr_flag,
.mouse_standard_flag,
.mouse_utf8_flag,
.origin_flag,
.wrap_flag,
=> bool,
.alternate_saved_x,
.alternate_saved_y,
.cursor_x,
.cursor_y,
.scroll_region_lower,
.scroll_region_upper,
.session_id,
.window_id,
.pane_id,
.window_width,
.window_height,
=> usize,
.cursor_colour,
.cursor_shape,
.pane_tabs,
.window_layout,
=> []const u8,
}; };
} }
}; };
test "parse alternate_on" {
try testing.expectEqual(true, try Variable.parse(.alternate_on, "1"));
try testing.expectEqual(false, try Variable.parse(.alternate_on, "0"));
try testing.expectEqual(false, try Variable.parse(.alternate_on, ""));
try testing.expectEqual(false, try Variable.parse(.alternate_on, "true"));
try testing.expectEqual(false, try Variable.parse(.alternate_on, "yes"));
}
test "parse alternate_saved_x" {
try testing.expectEqual(0, try Variable.parse(.alternate_saved_x, "0"));
try testing.expectEqual(42, try Variable.parse(.alternate_saved_x, "42"));
try testing.expectError(error.InvalidCharacter, Variable.parse(.alternate_saved_x, "abc"));
}
test "parse alternate_saved_y" {
try testing.expectEqual(0, try Variable.parse(.alternate_saved_y, "0"));
try testing.expectEqual(42, try Variable.parse(.alternate_saved_y, "42"));
try testing.expectError(error.InvalidCharacter, Variable.parse(.alternate_saved_y, "abc"));
}
test "parse cursor_x" {
try testing.expectEqual(0, try Variable.parse(.cursor_x, "0"));
try testing.expectEqual(79, try Variable.parse(.cursor_x, "79"));
try testing.expectError(error.InvalidCharacter, Variable.parse(.cursor_x, "abc"));
}
test "parse cursor_y" {
try testing.expectEqual(0, try Variable.parse(.cursor_y, "0"));
try testing.expectEqual(23, try Variable.parse(.cursor_y, "23"));
try testing.expectError(error.InvalidCharacter, Variable.parse(.cursor_y, "abc"));
}
test "parse scroll_region_upper" {
try testing.expectEqual(0, try Variable.parse(.scroll_region_upper, "0"));
try testing.expectEqual(5, try Variable.parse(.scroll_region_upper, "5"));
try testing.expectError(error.InvalidCharacter, Variable.parse(.scroll_region_upper, "abc"));
}
test "parse scroll_region_lower" {
try testing.expectEqual(0, try Variable.parse(.scroll_region_lower, "0"));
try testing.expectEqual(23, try Variable.parse(.scroll_region_lower, "23"));
try testing.expectError(error.InvalidCharacter, Variable.parse(.scroll_region_lower, "abc"));
}
test "parse session id" { test "parse session id" {
try testing.expectEqual(42, try Variable.parse(.session_id, "$42")); try testing.expectEqual(42, try Variable.parse(.session_id, "$42"));
try testing.expectEqual(0, try Variable.parse(.session_id, "$0")); try testing.expectEqual(0, try Variable.parse(.session_id, "$0"));
@ -176,6 +348,140 @@ test "parse window layout" {
try testing.expectEqualStrings("a]b,c{d}e(f)", try Variable.parse(.window_layout, "a]b,c{d}e(f)")); try testing.expectEqualStrings("a]b,c{d}e(f)", try Variable.parse(.window_layout, "a]b,c{d}e(f)"));
} }
test "parse cursor_flag" {
try testing.expectEqual(true, try Variable.parse(.cursor_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.cursor_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.cursor_flag, ""));
try testing.expectEqual(false, try Variable.parse(.cursor_flag, "true"));
}
test "parse insert_flag" {
try testing.expectEqual(true, try Variable.parse(.insert_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.insert_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.insert_flag, ""));
try testing.expectEqual(false, try Variable.parse(.insert_flag, "true"));
}
test "parse keypad_cursor_flag" {
try testing.expectEqual(true, try Variable.parse(.keypad_cursor_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.keypad_cursor_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.keypad_cursor_flag, ""));
try testing.expectEqual(false, try Variable.parse(.keypad_cursor_flag, "true"));
}
test "parse keypad_flag" {
try testing.expectEqual(true, try Variable.parse(.keypad_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.keypad_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.keypad_flag, ""));
try testing.expectEqual(false, try Variable.parse(.keypad_flag, "true"));
}
test "parse mouse_any_flag" {
try testing.expectEqual(true, try Variable.parse(.mouse_any_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.mouse_any_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.mouse_any_flag, ""));
try testing.expectEqual(false, try Variable.parse(.mouse_any_flag, "true"));
}
test "parse mouse_button_flag" {
try testing.expectEqual(true, try Variable.parse(.mouse_button_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.mouse_button_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.mouse_button_flag, ""));
try testing.expectEqual(false, try Variable.parse(.mouse_button_flag, "true"));
}
test "parse mouse_sgr_flag" {
try testing.expectEqual(true, try Variable.parse(.mouse_sgr_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.mouse_sgr_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.mouse_sgr_flag, ""));
try testing.expectEqual(false, try Variable.parse(.mouse_sgr_flag, "true"));
}
test "parse mouse_standard_flag" {
try testing.expectEqual(true, try Variable.parse(.mouse_standard_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.mouse_standard_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.mouse_standard_flag, ""));
try testing.expectEqual(false, try Variable.parse(.mouse_standard_flag, "true"));
}
test "parse mouse_utf8_flag" {
try testing.expectEqual(true, try Variable.parse(.mouse_utf8_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.mouse_utf8_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.mouse_utf8_flag, ""));
try testing.expectEqual(false, try Variable.parse(.mouse_utf8_flag, "true"));
}
test "parse wrap_flag" {
try testing.expectEqual(true, try Variable.parse(.wrap_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.wrap_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.wrap_flag, ""));
try testing.expectEqual(false, try Variable.parse(.wrap_flag, "true"));
}
test "parse bracketed_paste" {
try testing.expectEqual(true, try Variable.parse(.bracketed_paste, "1"));
try testing.expectEqual(false, try Variable.parse(.bracketed_paste, "0"));
try testing.expectEqual(false, try Variable.parse(.bracketed_paste, ""));
try testing.expectEqual(false, try Variable.parse(.bracketed_paste, "true"));
}
test "parse cursor_blinking" {
try testing.expectEqual(true, try Variable.parse(.cursor_blinking, "1"));
try testing.expectEqual(false, try Variable.parse(.cursor_blinking, "0"));
try testing.expectEqual(false, try Variable.parse(.cursor_blinking, ""));
try testing.expectEqual(false, try Variable.parse(.cursor_blinking, "true"));
}
test "parse focus_flag" {
try testing.expectEqual(true, try Variable.parse(.focus_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.focus_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.focus_flag, ""));
try testing.expectEqual(false, try Variable.parse(.focus_flag, "true"));
}
test "parse mouse_all_flag" {
try testing.expectEqual(true, try Variable.parse(.mouse_all_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.mouse_all_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.mouse_all_flag, ""));
try testing.expectEqual(false, try Variable.parse(.mouse_all_flag, "true"));
}
test "parse origin_flag" {
try testing.expectEqual(true, try Variable.parse(.origin_flag, "1"));
try testing.expectEqual(false, try Variable.parse(.origin_flag, "0"));
try testing.expectEqual(false, try Variable.parse(.origin_flag, ""));
try testing.expectEqual(false, try Variable.parse(.origin_flag, "true"));
}
test "parse pane_id" {
try testing.expectEqual(42, try Variable.parse(.pane_id, "%42"));
try testing.expectEqual(0, try Variable.parse(.pane_id, "%0"));
try testing.expectError(error.FormatError, Variable.parse(.pane_id, "0"));
try testing.expectError(error.FormatError, Variable.parse(.pane_id, "@0"));
try testing.expectError(error.FormatError, Variable.parse(.pane_id, "%"));
try testing.expectError(error.FormatError, Variable.parse(.pane_id, ""));
try testing.expectError(error.InvalidCharacter, Variable.parse(.pane_id, "%abc"));
}
test "parse cursor_colour" {
try testing.expectEqualStrings("red", try Variable.parse(.cursor_colour, "red"));
try testing.expectEqualStrings("#ff0000", try Variable.parse(.cursor_colour, "#ff0000"));
try testing.expectEqualStrings("", try Variable.parse(.cursor_colour, ""));
}
test "parse cursor_shape" {
try testing.expectEqualStrings("block", try Variable.parse(.cursor_shape, "block"));
try testing.expectEqualStrings("underline", try Variable.parse(.cursor_shape, "underline"));
try testing.expectEqualStrings("bar", try Variable.parse(.cursor_shape, "bar"));
try testing.expectEqualStrings("", try Variable.parse(.cursor_shape, ""));
}
test "parse pane_tabs" {
try testing.expectEqualStrings("0,8,16,24", try Variable.parse(.pane_tabs, "0,8,16,24"));
try testing.expectEqualStrings("", try Variable.parse(.pane_tabs, ""));
try testing.expectEqualStrings("0", try Variable.parse(.pane_tabs, "0"));
}
test "parseFormatStruct single field" { test "parseFormatStruct single field" {
const T = FormatStruct(&.{.session_id}); const T = FormatStruct(&.{.session_id});
const result = try parseFormatStruct(T, "$42", ' '); const result = try parseFormatStruct(T, "$42", ' ');