libghostty: add configurable mode defaults, remove mode_set/get

ABI BREAKING: This removes `ghostty_terminal_mode_get` and `_mode_set`.
We can now represent these operations completely with standard 
`ghostty_terminal_get` and `ghostty_terminal_set`, which makes it much
more flexible to preserve ABI in the future.

This is all centered around a new `GhosttyTerminalModeConfig` structure
that is an in or out parameter depending on use case.

This also adds a new `GHOSTTY_TERMINAL_OPT_MODE_DEFAULT` option that
can be used to set the _default_ value of mode that happens when a RIS
event (full reset) is sent.
This commit is contained in:
Mitchell Hashimoto
2026-08-05 21:57:27 -07:00
parent 8eecb8fdbf
commit cfc19e8053
6 changed files with 319 additions and 139 deletions

View File

@@ -695,6 +695,24 @@ typedef void (*GhosttyTerminalWritePtyFn)(GhosttyTerminal terminal,
typedef GhosttyString (*GhosttyTerminalXtversionFn)(GhosttyTerminal terminal,
void* userdata);
/**
* A terminal mode and boolean value used for mode configuration and queries.
*
* For GHOSTTY_TERMINAL_DATA_MODE, initialize `mode` before calling
* ghostty_terminal_get(). On success, `value` contains the current mode value.
*
* This struct has a frozen layout and will not gain fields in future versions.
*
* @ingroup terminal
*/
typedef struct {
/** Mode to configure or query. */
GhosttyMode mode;
/** Value to set, or the current value returned by a query. */
bool value;
} GhosttyTerminalModeConfig;
/**
* Terminal option identifiers.
*
@@ -1060,6 +1078,31 @@ typedef enum GHOSTTY_ENUM_TYPED {
* Input type: bool*
*/
GHOSTTY_TERMINAL_OPT_TITLE_REPORT = 32,
/**
* Set the reset default for a terminal mode.
*
* This unconditionally updates both the current value and the value restored
* by a full terminal reset (RIS).
*
* Some recognized modes represent transitions or mirror additional terminal
* state and cannot safely be configured as reset defaults. Those modes return
* GHOSTTY_INVALID_VALUE. A NULL value pointer also returns
* GHOSTTY_INVALID_VALUE.
*
* Input type: GhosttyTerminalModeConfig*
*/
GHOSTTY_TERMINAL_OPT_MODE_DEFAULT = 33,
/**
* Set the current value of a terminal mode.
*
* This does not change the value restored by a full terminal reset (RIS).
* A NULL value pointer or unknown mode returns GHOSTTY_INVALID_VALUE.
*
* Input type: GhosttyTerminalModeConfig*
*/
GHOSTTY_TERMINAL_OPT_MODE = 34,
GHOSTTY_TERMINAL_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyTerminalOption;
@@ -1420,6 +1463,17 @@ typedef enum GHOSTTY_ENUM_TYPED {
* Output type: size_t *
*/
GHOSTTY_TERMINAL_DATA_CONTINUATION_MAX_BYTES = 36,
/**
* Get the current value of a terminal mode.
*
* The caller must initialize the `mode` field. On success, the `value` field
* is updated with the current value. A NULL pointer or unknown mode returns
* GHOSTTY_INVALID_VALUE.
*
* Input/output type: GhosttyTerminalModeConfig *
*/
GHOSTTY_TERMINAL_DATA_MODE = 37,
GHOSTTY_TERMINAL_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyTerminalData;
@@ -1709,41 +1763,6 @@ GHOSTTY_API GhosttyResult ghostty_terminal_compress(
GhosttyTerminalCompressionMode mode,
GhosttyTerminalCompressionResult* out_result);
/**
* Get the current value of a terminal mode.
*
* Returns the value of the mode identified by the given mode.
*
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
* @param mode The mode identifying the mode to query
* @param[out] out_value On success, set to true if the mode is set, false
* if it is reset
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
* is NULL or the mode does not correspond to a known mode
*
* @ingroup terminal
*/
GHOSTTY_API GhosttyResult ghostty_terminal_mode_get(GhosttyTerminal terminal,
GhosttyMode mode,
bool* out_value);
/**
* Set the value of a terminal mode.
*
* Sets the mode identified by the given mode to the specified value.
*
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
* @param mode The mode identifying the mode to set
* @param value true to set the mode, false to reset it
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
* is NULL or the mode does not correspond to a known mode
*
* @ingroup terminal
*/
GHOSTTY_API GhosttyResult ghostty_terminal_mode_set(GhosttyTerminal terminal,
GhosttyMode mode,
bool value);
/**
* Get data from a terminal instance.
*

View File

@@ -284,8 +284,6 @@ comptime {
@export(&c.terminal_scroll_viewport, .{ .name = "ghostty_terminal_scroll_viewport" });
@export(&c.terminal_compression_activity, .{ .name = "ghostty_terminal_compression_activity" });
@export(&c.terminal_compress, .{ .name = "ghostty_terminal_compress" });
@export(&c.terminal_mode_get, .{ .name = "ghostty_terminal_mode_get" });
@export(&c.terminal_mode_set, .{ .name = "ghostty_terminal_mode_set" });
@export(&c.terminal_get, .{ .name = "ghostty_terminal_get" });
@export(&c.terminal_get_multi, .{ .name = "ghostty_terminal_get_multi" });
@export(&c.terminal_continuation_write, .{ .name = "ghostty_terminal_continuation_write" });

View File

@@ -187,8 +187,6 @@ pub const terminal_vt_write = terminal.vt_write;
pub const terminal_scroll_viewport = terminal.scroll_viewport;
pub const terminal_compression_activity = terminal.compression_activity;
pub const terminal_compress = terminal.compress;
pub const terminal_mode_get = terminal.mode_get;
pub const terminal_mode_set = terminal.mode_set;
pub const terminal_get = terminal.get;
pub const terminal_get_multi = terminal.get_multi;
pub const terminal_continuation_write = terminal.continuation_write;

View File

@@ -148,6 +148,19 @@ pub const ProgressReport = extern struct {
progress: i8,
};
/// A terminal mode and boolean value used for mode configuration.
///
/// C: GhosttyTerminalModeConfig
pub const ModeConfig = extern struct {
mode: modes.ModeTag.Backing,
value: bool,
fn toMode(self: ModeConfig) ?modes.Mode {
const tag: modes.ModeTag = @bitCast(self.mode);
return modes.modeFromInt(tag.value, tag.ansi);
}
};
/// C callback state for terminal effects. Trampolines are always
/// installed on the stream handler; they check these fields and
/// no-op when the corresponding callback is null.
@@ -890,6 +903,8 @@ pub const Option = enum(c_int) {
progress_report = 30,
continuation_max_bytes = 31,
title_report = 32,
mode_default = 33,
mode = 34,
/// Input type expected for setting the option.
pub fn InType(comptime self: Option) type {
@@ -926,6 +941,7 @@ pub const Option = enum(c_int) {
.selection => ?*const selection_c.CSelection,
.default_cursor_style => ?*const TerminalCursorStyle,
.default_cursor_blink => ?*const bool,
.mode, .mode_default => ?*const ModeConfig,
};
}
};
@@ -1090,6 +1106,18 @@ fn setTyped(
wrapper,
if (value) |ptr| ptr.* else default_continuation_max_bytes,
),
.mode, .mode_default => {
const config = (value orelse return .invalid_value).*;
const mode = config.toMode() orelse return .invalid_value;
switch (option) {
.mode => wrapper.terminal.modes.set(mode, config.value),
.mode_default => {
if (!modes.defaultConfigurable(mode)) return .invalid_value;
wrapper.terminal.modes.setDefault(mode, config.value);
},
else => unreachable,
}
},
}
return .success;
}
@@ -1160,30 +1188,6 @@ pub fn reset(terminal_: Terminal) callconv(lib.calling_conv) void {
t.fullReset();
}
pub fn mode_get(
terminal_: Terminal,
tag: modes.ModeTag.Backing,
out_value: *bool,
) callconv(lib.calling_conv) Result {
const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal;
const mode_tag: modes.ModeTag = @bitCast(tag);
const mode = modes.modeFromInt(mode_tag.value, mode_tag.ansi) orelse return .invalid_value;
out_value.* = t.modes.get(mode);
return .success;
}
pub fn mode_set(
terminal_: Terminal,
tag: modes.ModeTag.Backing,
value: bool,
) callconv(lib.calling_conv) Result {
const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal;
const mode_tag: modes.ModeTag = @bitCast(tag);
const mode = modes.modeFromInt(mode_tag.value, mode_tag.ansi) orelse return .invalid_value;
t.modes.set(mode, value);
return .success;
}
/// C: GhosttyKittyGraphics
pub const KittyGraphics = kitty_gfx_c.KittyGraphics;
@@ -1232,6 +1236,7 @@ pub const TerminalData = enum(c_int) {
scrollback_max_bytes = 34,
scrollback_max_lines = 35,
continuation_max_bytes = 36,
mode = 37,
/// Output type expected for querying the data of the given kind.
pub fn OutType(comptime self: TerminalData) type {
@@ -1271,6 +1276,7 @@ pub const TerminalData = enum(c_int) {
.kitty_image_medium_temp_file => lib.String,
.kitty_graphics => KittyGraphics,
.selection => selection_c.CSelection,
.mode => ModeConfig,
};
}
};
@@ -1287,12 +1293,14 @@ pub fn get(
};
}
const out_ptr = out orelse return .invalid_value;
return switch (data) {
.invalid => .invalid_value,
inline else => |comptime_data| getTyped(
terminal_,
comptime_data,
@ptrCast(@alignCast(out)),
@ptrCast(@alignCast(out_ptr)),
),
};
}
@@ -1401,6 +1409,10 @@ fn getTyped(
out.* = max;
},
.continuation_max_bytes => out.* = continuationMaxBytes(wrapper),
.mode => {
const mode = out.toMode() orelse return .invalid_value;
out.value = t.modes.get(mode);
},
}
return .success;
@@ -2230,7 +2242,7 @@ test "resize shrinks both axes with cursor at bottom" {
try testing.expectEqual(23, t.?.terminal.rows);
}
test "mode_get and mode_set" {
test "set and get mode" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(
&lib.alloc.test_allocator,
@@ -2240,40 +2252,31 @@ test "mode_get and mode_set" {
));
defer free(t);
var value: bool = undefined;
// DEC mode 25 (cursor_visible) defaults to true
const cursor_visible: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 25, .ansi = false });
try testing.expectEqual(Result.success, mode_get(t, cursor_visible, &value));
try testing.expect(value);
var config: ModeConfig = .{ .mode = cursor_visible, .value = undefined };
try testing.expectEqual(Result.success, get(t, .mode, @ptrCast(&config)));
try testing.expect(config.value);
// Set it to false
try testing.expectEqual(Result.success, mode_set(t, cursor_visible, false));
try testing.expectEqual(Result.success, mode_get(t, cursor_visible, &value));
try testing.expect(!value);
config.value = false;
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
try testing.expectEqual(Result.success, get(t, .mode, @ptrCast(&config)));
try testing.expect(!config.value);
// ANSI mode 4 (insert) defaults to false
const insert: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 4, .ansi = true });
try testing.expectEqual(Result.success, mode_get(t, insert, &value));
try testing.expect(!value);
config.mode = insert;
try testing.expectEqual(Result.success, get(t, .mode, @ptrCast(&config)));
try testing.expect(!config.value);
try testing.expectEqual(Result.success, mode_set(t, insert, true));
try testing.expectEqual(Result.success, mode_get(t, insert, &value));
try testing.expect(value);
config.value = true;
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
try testing.expectEqual(Result.success, get(t, .mode, @ptrCast(&config)));
try testing.expect(config.value);
}
test "mode_get null" {
var value: bool = undefined;
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 25, .ansi = false });
try testing.expectEqual(Result.invalid_value, mode_get(null, tag, &value));
}
test "mode_set null" {
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 25, .ansi = false });
try testing.expectEqual(Result.invalid_value, mode_set(null, tag, true));
}
test "mode_get unknown mode" {
test "set mode default updates current and reset value" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(
&lib.alloc.test_allocator,
@@ -2283,12 +2286,49 @@ test "mode_get unknown mode" {
));
defer free(t);
var value: bool = undefined;
const unknown: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 9999, .ansi = false });
try testing.expectEqual(Result.invalid_value, mode_get(t, unknown, &value));
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{
.value = 2027,
.ansi = false,
});
var config: ModeConfig = .{ .mode = tag, .value = true };
try testing.expectEqual(Result.success, set(
t,
.mode_default,
@ptrCast(&config),
));
try testing.expect(t.?.terminal.modes.get(.grapheme_cluster));
try testing.expect(t.?.terminal.modes.default.grapheme_cluster);
config.value = false;
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
try testing.expect(!t.?.terminal.modes.get(.grapheme_cluster));
try testing.expect(t.?.terminal.modes.default.grapheme_cluster);
// Setting the default also unconditionally replaces the current value.
config.value = true;
try testing.expectEqual(Result.success, set(
t,
.mode_default,
@ptrCast(&config),
));
try testing.expect(t.?.terminal.modes.get(.grapheme_cluster));
config.value = false;
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
vt_write(t, "\x1bc", 2);
try testing.expect(t.?.terminal.modes.get(.grapheme_cluster));
config.value = false;
try testing.expectEqual(Result.success, set(
t,
.mode_default,
@ptrCast(&config),
));
try testing.expect(!t.?.terminal.modes.get(.grapheme_cluster));
try testing.expect(!t.?.terminal.modes.default.grapheme_cluster);
}
test "mode_set unknown mode" {
test "set mode default validates configuration" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(
&lib.alloc.test_allocator,
@@ -2298,8 +2338,56 @@ test "mode_set unknown mode" {
));
defer free(t);
const unknown: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 9999, .ansi = false });
try testing.expectEqual(Result.invalid_value, mode_set(t, unknown, true));
try testing.expectEqual(Result.invalid_value, set(
t,
.mode_default,
null,
));
var config: ModeConfig = .{
.mode = @bitCast(modes.ModeTag{ .value = 9999, .ansi = false }),
.value = true,
};
try testing.expectEqual(Result.invalid_value, set(
t,
.mode_default,
@ptrCast(&config),
));
config.mode = @bitCast(modes.ModeTag{ .value = 1047, .ansi = false });
try testing.expectEqual(Result.invalid_value, set(
t,
.mode_default,
@ptrCast(&config),
));
}
test "set and get mode null" {
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 25, .ansi = false });
var config: ModeConfig = .{ .mode = tag, .value = true };
try testing.expectEqual(Result.invalid_value, set(null, .mode, @ptrCast(&config)));
try testing.expectEqual(Result.invalid_value, get(null, .mode, @ptrCast(&config)));
}
test "set and get mode validate configuration" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(
&lib.alloc.test_allocator,
&t,
80,
24,
));
defer free(t);
try testing.expectEqual(Result.invalid_value, set(t, .mode, null));
try testing.expectEqual(Result.invalid_value, get(t, .mode, null));
var config: ModeConfig = .{
.mode = @bitCast(modes.ModeTag{ .value = 9999, .ansi = false }),
.value = true,
};
try testing.expectEqual(Result.invalid_value, set(t, .mode, @ptrCast(&config)));
try testing.expectEqual(Result.invalid_value, get(t, .mode, @ptrCast(&config)));
}
test "vt_write" {
@@ -2464,8 +2552,11 @@ test "get cursor_visible" {
try testing.expect(visible);
// DEC mode 25 controls cursor visibility
const cursor_visible_mode: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 25, .ansi = false });
try testing.expectEqual(Result.success, mode_set(t, cursor_visible_mode, false));
var config: ModeConfig = .{
.mode = @bitCast(modes.ModeTag{ .value = 25, .ansi = false }),
.value = false,
};
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
try testing.expectEqual(Result.success, get(t, .cursor_visible, @ptrCast(&visible)));
try testing.expect(!visible);
}
@@ -2521,34 +2612,50 @@ test "get mouse_tracking" {
try testing.expect(!tracking);
// Enable X10 mouse (DEC mode 9)
const x10_mode: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 9, .ansi = false });
try testing.expectEqual(Result.success, mode_set(t, x10_mode, true));
var config: ModeConfig = .{
.mode = @bitCast(modes.ModeTag{ .value = 9, .ansi = false }),
.value = true,
};
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
try testing.expectEqual(Result.success, get(t, .mouse_tracking, @ptrCast(&tracking)));
try testing.expect(tracking);
// Disable X10, enable normal mouse (DEC mode 1000)
try testing.expectEqual(Result.success, mode_set(t, x10_mode, false));
const normal_mode: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 1000, .ansi = false });
try testing.expectEqual(Result.success, mode_set(t, normal_mode, true));
config.value = false;
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
config = .{
.mode = @bitCast(modes.ModeTag{ .value = 1000, .ansi = false }),
.value = true,
};
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
try testing.expectEqual(Result.success, get(t, .mouse_tracking, @ptrCast(&tracking)));
try testing.expect(tracking);
// Disable normal, enable button mouse (DEC mode 1002)
try testing.expectEqual(Result.success, mode_set(t, normal_mode, false));
const button_mode: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 1002, .ansi = false });
try testing.expectEqual(Result.success, mode_set(t, button_mode, true));
config.value = false;
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
config = .{
.mode = @bitCast(modes.ModeTag{ .value = 1002, .ansi = false }),
.value = true,
};
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
try testing.expectEqual(Result.success, get(t, .mouse_tracking, @ptrCast(&tracking)));
try testing.expect(tracking);
// Disable button, enable any mouse (DEC mode 1003)
try testing.expectEqual(Result.success, mode_set(t, button_mode, false));
const any_mode: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 1003, .ansi = false });
try testing.expectEqual(Result.success, mode_set(t, any_mode, true));
config.value = false;
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
config = .{
.mode = @bitCast(modes.ModeTag{ .value = 1003, .ansi = false }),
.value = true,
};
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
try testing.expectEqual(Result.success, get(t, .mouse_tracking, @ptrCast(&tracking)));
try testing.expect(tracking);
// Disable all - should be false again
try testing.expectEqual(Result.success, mode_set(t, any_mode, false));
config.value = false;
try testing.expectEqual(Result.success, set(t, .mode, @ptrCast(&config)));
try testing.expectEqual(Result.success, get(t, .mouse_tracking, @ptrCast(&tracking)));
try testing.expect(!tracking);
}

View File

@@ -71,6 +71,7 @@ pub const structs: std.StaticStringMap(StructInfo) = structs: {
.{ "GhosttyStyle", StructInfo.init(style_c.Style) },
.{ "GhosttyStyleColor", StructInfo.init(style_c.Color) },
.{ "GhosttyTerminalDesktopNotification", StructInfo.init(terminal.DesktopNotification) },
.{ "GhosttyTerminalModeConfig", StructInfo.init(terminal.ModeConfig) },
.{ "GhosttyTerminalProgressReport", StructInfo.init(terminal.ProgressReport) },
.{ "GhosttyTerminalScrollbar", StructInfo.init(terminal.TerminalScrollbar) },
.{ "GhosttyTerminalScrollViewport", StructInfo.init(terminal.ScrollViewport) },
@@ -221,6 +222,7 @@ test "json parses" {
try std.testing.expect(root.contains("GhosttyClipboardContent"));
try std.testing.expect(root.contains("GhosttyClipboardWrite"));
try std.testing.expect(root.contains("GhosttyFormatterTerminalOptions"));
try std.testing.expect(root.contains("GhosttyTerminalModeConfig"));
try std.testing.expect(root.contains("GhosttyReader"));
try std.testing.expect(root.contains("GhosttyWriter"));

View File

@@ -34,22 +34,18 @@ pub const ModeState = struct {
/// Set a mode to a value.
pub fn set(self: *ModeState, mode: Mode, value: bool) void {
switch (mode) {
inline else => |mode_comptime| {
const entry = comptime entryForMode(mode_comptime);
@field(self.values, entry.name) = value;
},
}
setPacked(&self.values, mode, value);
}
/// Set the reset default and current value for a mode.
pub fn setDefault(self: *ModeState, mode: Mode, value: bool) void {
setPacked(&self.values, mode, value);
setPacked(&self.default, mode, value);
}
/// Get the value of a mode.
pub fn get(self: *const ModeState, mode: Mode) bool {
switch (mode) {
inline else => |mode_comptime| {
const entry = comptime entryForMode(mode_comptime);
return @field(self.values, entry.name);
},
}
return getPacked(&self.values, mode);
}
/// Save the state of the given mode. This can then be restored
@@ -110,6 +106,24 @@ pub const ModeState = struct {
}
};
fn setPacked(values: *ModePacked, mode: Mode, value: bool) void {
switch (mode) {
inline else => |mode_comptime| {
const entry = comptime entryForMode(mode_comptime);
@field(values, entry.name) = value;
},
}
}
fn getPacked(values: *const ModePacked, mode: Mode) bool {
switch (mode) {
inline else => |mode_comptime| {
const entry = comptime entryForMode(mode_comptime);
return @field(values, entry.name);
},
}
}
/// A packed struct of all the settable modes. This shouldn't
/// be used directly but rather through the ModeState struct.
pub const ModePacked = packed_struct: {
@@ -239,8 +253,24 @@ const ModeEntry = struct {
/// set or queried. The mode enum still has it, allowing Ghostty developers
/// to develop a mode without exposing it to real users.
disabled: bool = false,
/// Whether an embedder may safely configure this bit as reset policy.
/// Modes that perform a transition or mirror additional terminal state
/// must use their semantic configuration API instead.
default_configurable: bool = true,
};
/// Return whether a mode can safely be configured as a reset default by an
/// embedder. This excludes modes whose set/reset operations have side effects
/// beyond changing the mode bit.
pub fn defaultConfigurable(mode: Mode) bool {
switch (mode) {
inline else => |mode_comptime| {
return comptime entryForMode(mode_comptime).default_configurable;
},
}
}
/// The full list of available entries. For documentation see how
/// they're used within Ghostty or google their values. It is not
/// valuable to redocument them all here.
@@ -256,46 +286,46 @@ const entries: []const ModeEntry = &.{
// DEC
.{ .name = "cursor_keys", .value = 1 }, // DECCKM
.{ .name = "132_column", .value = 3 },
.{ .name = "132_column", .value = 3, .default_configurable = false },
.{ .name = "slow_scroll", .value = 4 },
.{ .name = "reverse_colors", .value = 5 },
.{ .name = "origin", .value = 6 },
.{ .name = "origin", .value = 6, .default_configurable = false },
.{ .name = "wraparound", .value = 7, .default = true },
.{ .name = "autorepeat", .value = 8 },
.{ .name = "mouse_event_x10", .value = 9 },
.{ .name = "cursor_blinking", .value = 12 },
.{ .name = "mouse_event_x10", .value = 9, .default_configurable = false },
.{ .name = "cursor_blinking", .value = 12, .default_configurable = false },
.{ .name = "cursor_visible", .value = 25, .default = true },
.{ .name = "enable_mode_3", .value = 40 },
.{ .name = "reverse_wrap", .value = 45 },
.{ .name = "alt_screen_legacy", .value = 47 },
.{ .name = "alt_screen_legacy", .value = 47, .default_configurable = false },
.{ .name = "keypad_keys", .value = 66 },
// DEC Backarrow Key Mode (DECBKM)
// See https://vt100.net/dec/ek-vt3xx-tp-002.pdf page 170
// If `false` (the default), `backspace` emits 0x7f
// If `true`, `backspace` emits 0x08
.{ .name = "backarrow_key_mode", .value = 67 },
.{ .name = "enable_left_and_right_margin", .value = 69 },
.{ .name = "mouse_event_normal", .value = 1000 },
.{ .name = "mouse_event_button", .value = 1002 },
.{ .name = "mouse_event_any", .value = 1003 },
.{ .name = "enable_left_and_right_margin", .value = 69, .default_configurable = false },
.{ .name = "mouse_event_normal", .value = 1000, .default_configurable = false },
.{ .name = "mouse_event_button", .value = 1002, .default_configurable = false },
.{ .name = "mouse_event_any", .value = 1003, .default_configurable = false },
.{ .name = "focus_event", .value = 1004 },
.{ .name = "mouse_format_utf8", .value = 1005 },
.{ .name = "mouse_format_sgr", .value = 1006 },
.{ .name = "mouse_format_utf8", .value = 1005, .default_configurable = false },
.{ .name = "mouse_format_sgr", .value = 1006, .default_configurable = false },
.{ .name = "mouse_alternate_scroll", .value = 1007, .default = true },
.{ .name = "mouse_format_urxvt", .value = 1015 },
.{ .name = "mouse_format_sgr_pixels", .value = 1016 },
.{ .name = "mouse_format_urxvt", .value = 1015, .default_configurable = false },
.{ .name = "mouse_format_sgr_pixels", .value = 1016, .default_configurable = false },
.{ .name = "ignore_keypad_with_numlock", .value = 1035, .default = true },
.{ .name = "alt_esc_prefix", .value = 1036, .default = true },
.{ .name = "alt_sends_escape", .value = 1039 },
.{ .name = "reverse_wrap_extended", .value = 1045 },
.{ .name = "alt_screen", .value = 1047 },
.{ .name = "save_cursor", .value = 1048 },
.{ .name = "alt_screen_save_cursor_clear_enter", .value = 1049 },
.{ .name = "alt_screen", .value = 1047, .default_configurable = false },
.{ .name = "save_cursor", .value = 1048, .default_configurable = false },
.{ .name = "alt_screen_save_cursor_clear_enter", .value = 1049, .default_configurable = false },
.{ .name = "bracketed_paste", .value = 2004 },
.{ .name = "synchronized_output", .value = 2026 },
.{ .name = "synchronized_output", .value = 2026, .default_configurable = false },
.{ .name = "grapheme_cluster", .value = 2027 },
.{ .name = "report_color_scheme", .value = 2031 },
.{ .name = "report_visibility", .value = 2033 },
.{ .name = "report_visibility", .value = 2033, .default_configurable = false },
.{ .name = "in_band_size_reports", .value = 2048 },
};
@@ -327,6 +357,32 @@ test ModeState {
try testing.expect(state.get(.cursor_keys));
}
test "ModeState set default updates current and reset value" {
var state: ModeState = .{};
state.setDefault(.grapheme_cluster, true);
try testing.expect(state.get(.grapheme_cluster));
try testing.expect(state.default.grapheme_cluster);
state.set(.grapheme_cluster, false);
try testing.expect(!state.get(.grapheme_cluster));
try testing.expect(state.default.grapheme_cluster);
state.setDefault(.grapheme_cluster, true);
try testing.expect(state.get(.grapheme_cluster));
state.set(.grapheme_cluster, false);
state.reset();
try testing.expect(state.get(.grapheme_cluster));
}
test "default configurable modes" {
try testing.expect(defaultConfigurable(.grapheme_cluster));
try testing.expect(defaultConfigurable(.wraparound));
try testing.expect(!defaultConfigurable(.alt_screen));
try testing.expect(!defaultConfigurable(.cursor_blinking));
}
test "getReport known DEC mode" {
var state: ModeState = .{};
const report = state.getReport(.{ .value = 1 });