mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 11:35:48 +00:00
libghostty: expose terminal modes and DECRPM report encoding through the C API. (#11579)
Previously libghostty-vt had no way for C consumers to query, set, or
report on terminal modes. Callers that needed to respond to DECRPM
requests or inspect mode state had no public interface to do so.
This adds three layers of mode support to the C API:
- `GhosttyMode` — a 16-bit packed type with inline helpers to construct
and inspect mode tags, plus `GHOSTTY_MODE_*` macros for all supported
ANSI and DEC private modes.
- `ghostty_terminal_mode_get` / `ghostty_terminal_mode_set` — query and
set mode values on a terminal handle.
- `ghostty_mode_report_encode` — encode a DECRPM response sequence (`CSI
[?] Ps1 ; Ps2 $ y`) into a caller-provided buffer.
## Example
```c
#include <stdio.h>
#include <ghostty/vt.h>
int main() {
char buf[32];
size_t written = 0;
// Query a terminal's cursor visibility and encode a DECRPM report
GhosttyMode mode = GHOSTTY_MODE_CURSOR_VISIBLE;
bool value = false;
ghostty_terminal_mode_get(terminal, mode, &value);
GhosttyModeReportState state = value
? GHOSTTY_MODE_REPORT_SET
: GHOSTTY_MODE_REPORT_RESET;
if (ghostty_mode_report_encode(mode, state, buf, sizeof(buf), &written)
== GHOSTTY_SUCCESS) {
// writes ESC[?25;1$y or ESC[?25;2$y
fwrite(buf, 1, written, stdout);
}
}
```
This commit is contained in:
@@ -99,6 +99,7 @@ extern "C" {
|
||||
#include <ghostty/vt/osc.h>
|
||||
#include <ghostty/vt/sgr.h>
|
||||
#include <ghostty/vt/key.h>
|
||||
#include <ghostty/vt/modes.h>
|
||||
#include <ghostty/vt/mouse.h>
|
||||
#include <ghostty/vt/paste.h>
|
||||
#include <ghostty/vt/wasm.h>
|
||||
|
||||
239
include/ghostty/vt/modes.h
Normal file
239
include/ghostty/vt/modes.h
Normal file
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* @file modes.h
|
||||
*
|
||||
* Terminal mode utilities - pack and unpack ANSI/DEC mode identifiers.
|
||||
*/
|
||||
|
||||
#ifndef GHOSTTY_VT_MODES_H
|
||||
#define GHOSTTY_VT_MODES_H
|
||||
|
||||
/** @defgroup modes Mode Utilities
|
||||
*
|
||||
* Utilities for working with terminal modes. A mode is a compact
|
||||
* 16-bit representation of a terminal mode identifier that encodes both
|
||||
* the numeric mode value (up to 15 bits) and whether the mode is an ANSI
|
||||
* mode or a DEC private mode (?-prefixed).
|
||||
*
|
||||
* The packed layout (least-significant bit first) is:
|
||||
* - Bits 0–14: mode value (u15)
|
||||
* - Bit 15: ANSI flag (0 = DEC private mode, 1 = ANSI mode)
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* @code{.c}
|
||||
* #include <stdio.h>
|
||||
* #include <ghostty/vt.h>
|
||||
*
|
||||
* int main() {
|
||||
* // Create a mode for DEC mode 25 (cursor visible)
|
||||
* GhosttyMode tag = ghostty_mode_new(25, false);
|
||||
* printf("value=%u ansi=%d packed=0x%04x\n",
|
||||
* ghostty_mode_value(tag),
|
||||
* ghostty_mode_ansi(tag),
|
||||
* tag);
|
||||
*
|
||||
* // Create a mode for ANSI mode 4 (insert mode)
|
||||
* GhosttyMode ansi_tag = ghostty_mode_new(4, true);
|
||||
* printf("value=%u ansi=%d packed=0x%04x\n",
|
||||
* ghostty_mode_value(ansi_tag),
|
||||
* ghostty_mode_ansi(ansi_tag),
|
||||
* ansi_tag);
|
||||
*
|
||||
* return 0;
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* ## DECRPM Report Encoding
|
||||
*
|
||||
* Use ghostty_mode_report_encode() to encode a DECRPM response into a
|
||||
* caller-provided buffer:
|
||||
*
|
||||
* @code{.c}
|
||||
* #include <stdio.h>
|
||||
* #include <ghostty/vt.h>
|
||||
*
|
||||
* int main() {
|
||||
* char buf[32];
|
||||
* size_t written = 0;
|
||||
*
|
||||
* // Encode a report that DEC mode 25 (cursor visible) is set
|
||||
* GhosttyResult result = ghostty_mode_report_encode(
|
||||
* GHOSTTY_MODE_CURSOR_VISIBLE,
|
||||
* GHOSTTY_MODE_REPORT_SET,
|
||||
* buf, sizeof(buf), &written);
|
||||
*
|
||||
* if (result == GHOSTTY_SUCCESS) {
|
||||
* printf("Encoded %zu bytes: ", written);
|
||||
* fwrite(buf, 1, written, stdout);
|
||||
* printf("\n"); // prints: ESC[?25;1$y
|
||||
* }
|
||||
*
|
||||
* return 0;
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <ghostty/vt/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @name ANSI Modes
|
||||
* Modes for standard ANSI modes.
|
||||
* @{
|
||||
*/
|
||||
#define GHOSTTY_MODE_KAM (ghostty_mode_new(2, true)) /**< Keyboard action (disable keyboard) */
|
||||
#define GHOSTTY_MODE_INSERT (ghostty_mode_new(4, true)) /**< Insert mode */
|
||||
#define GHOSTTY_MODE_SRM (ghostty_mode_new(12, true)) /**< Send/receive mode */
|
||||
#define GHOSTTY_MODE_LINEFEED (ghostty_mode_new(20, true)) /**< Linefeed/new line mode */
|
||||
/** @} */
|
||||
|
||||
/** @name DEC Private Modes
|
||||
* Modes for DEC private modes (?-prefixed).
|
||||
* @{
|
||||
*/
|
||||
#define GHOSTTY_MODE_DECCKM (ghostty_mode_new(1, false)) /**< Cursor keys */
|
||||
#define GHOSTTY_MODE_132_COLUMN (ghostty_mode_new(3, false)) /**< 132/80 column mode */
|
||||
#define GHOSTTY_MODE_SLOW_SCROLL (ghostty_mode_new(4, false)) /**< Slow scroll */
|
||||
#define GHOSTTY_MODE_REVERSE_COLORS (ghostty_mode_new(5, false)) /**< Reverse video */
|
||||
#define GHOSTTY_MODE_ORIGIN (ghostty_mode_new(6, false)) /**< Origin mode */
|
||||
#define GHOSTTY_MODE_WRAPAROUND (ghostty_mode_new(7, false)) /**< Auto-wrap mode */
|
||||
#define GHOSTTY_MODE_AUTOREPEAT (ghostty_mode_new(8, false)) /**< Auto-repeat keys */
|
||||
#define GHOSTTY_MODE_X10_MOUSE (ghostty_mode_new(9, false)) /**< X10 mouse reporting */
|
||||
#define GHOSTTY_MODE_CURSOR_BLINKING (ghostty_mode_new(12, false)) /**< Cursor blink */
|
||||
#define GHOSTTY_MODE_CURSOR_VISIBLE (ghostty_mode_new(25, false)) /**< Cursor visible (DECTCEM) */
|
||||
#define GHOSTTY_MODE_ENABLE_MODE_3 (ghostty_mode_new(40, false)) /**< Allow 132 column mode */
|
||||
#define GHOSTTY_MODE_REVERSE_WRAP (ghostty_mode_new(45, false)) /**< Reverse wrap */
|
||||
#define GHOSTTY_MODE_ALT_SCREEN_LEGACY (ghostty_mode_new(47, false)) /**< Alternate screen (legacy) */
|
||||
#define GHOSTTY_MODE_KEYPAD_KEYS (ghostty_mode_new(66, false)) /**< Application keypad */
|
||||
#define GHOSTTY_MODE_LEFT_RIGHT_MARGIN (ghostty_mode_new(69, false)) /**< Left/right margin mode */
|
||||
#define GHOSTTY_MODE_NORMAL_MOUSE (ghostty_mode_new(1000, false)) /**< Normal mouse tracking */
|
||||
#define GHOSTTY_MODE_BUTTON_MOUSE (ghostty_mode_new(1002, false)) /**< Button-event mouse tracking */
|
||||
#define GHOSTTY_MODE_ANY_MOUSE (ghostty_mode_new(1003, false)) /**< Any-event mouse tracking */
|
||||
#define GHOSTTY_MODE_FOCUS_EVENT (ghostty_mode_new(1004, false)) /**< Focus in/out events */
|
||||
#define GHOSTTY_MODE_UTF8_MOUSE (ghostty_mode_new(1005, false)) /**< UTF-8 mouse format */
|
||||
#define GHOSTTY_MODE_SGR_MOUSE (ghostty_mode_new(1006, false)) /**< SGR mouse format */
|
||||
#define GHOSTTY_MODE_ALT_SCROLL (ghostty_mode_new(1007, false)) /**< Alternate scroll mode */
|
||||
#define GHOSTTY_MODE_URXVT_MOUSE (ghostty_mode_new(1015, false)) /**< URxvt mouse format */
|
||||
#define GHOSTTY_MODE_SGR_PIXELS_MOUSE (ghostty_mode_new(1016, false)) /**< SGR-Pixels mouse format */
|
||||
#define GHOSTTY_MODE_NUMLOCK_KEYPAD (ghostty_mode_new(1035, false)) /**< Ignore keypad with NumLock */
|
||||
#define GHOSTTY_MODE_ALT_ESC_PREFIX (ghostty_mode_new(1036, false)) /**< Alt key sends ESC prefix */
|
||||
#define GHOSTTY_MODE_ALT_SENDS_ESC (ghostty_mode_new(1039, false)) /**< Alt sends escape */
|
||||
#define GHOSTTY_MODE_REVERSE_WRAP_EXT (ghostty_mode_new(1045, false)) /**< Extended reverse wrap */
|
||||
#define GHOSTTY_MODE_ALT_SCREEN (ghostty_mode_new(1047, false)) /**< Alternate screen */
|
||||
#define GHOSTTY_MODE_SAVE_CURSOR (ghostty_mode_new(1048, false)) /**< Save cursor (DECSC) */
|
||||
#define GHOSTTY_MODE_ALT_SCREEN_SAVE (ghostty_mode_new(1049, false)) /**< Alt screen + save cursor + clear */
|
||||
#define GHOSTTY_MODE_BRACKETED_PASTE (ghostty_mode_new(2004, false)) /**< Bracketed paste mode */
|
||||
#define GHOSTTY_MODE_SYNC_OUTPUT (ghostty_mode_new(2026, false)) /**< Synchronized output */
|
||||
#define GHOSTTY_MODE_GRAPHEME_CLUSTER (ghostty_mode_new(2027, false)) /**< Grapheme cluster mode */
|
||||
#define GHOSTTY_MODE_COLOR_SCHEME_REPORT (ghostty_mode_new(2031, false)) /**< Report color scheme */
|
||||
#define GHOSTTY_MODE_IN_BAND_RESIZE (ghostty_mode_new(2048, false)) /**< In-band size reports */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* A packed 16-bit terminal mode.
|
||||
*
|
||||
* Encodes a mode value (bits 0–14) and an ANSI flag (bit 15) into a
|
||||
* single 16-bit integer. Use the inline helper functions to construct
|
||||
* and inspect modes rather than manipulating bits directly.
|
||||
*/
|
||||
typedef uint16_t GhosttyMode;
|
||||
|
||||
/**
|
||||
* Create a mode from a mode value and ANSI flag.
|
||||
*
|
||||
* @param value The numeric mode value (0–32767)
|
||||
* @param ansi true for an ANSI mode, false for a DEC private mode
|
||||
* @return The packed mode
|
||||
*
|
||||
* @ingroup modes
|
||||
*/
|
||||
static inline GhosttyMode ghostty_mode_new(uint16_t value, bool ansi) {
|
||||
return (GhosttyMode)((value & 0x7FFF) | ((uint16_t)ansi << 15));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the numeric mode value from a mode.
|
||||
*
|
||||
* @param mode The mode
|
||||
* @return The mode value (0–32767)
|
||||
*
|
||||
* @ingroup modes
|
||||
*/
|
||||
static inline uint16_t ghostty_mode_value(GhosttyMode mode) {
|
||||
return mode & 0x7FFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a mode represents an ANSI mode.
|
||||
*
|
||||
* @param mode The mode
|
||||
* @return true if this is an ANSI mode, false if it is a DEC private mode
|
||||
*
|
||||
* @ingroup modes
|
||||
*/
|
||||
static inline bool ghostty_mode_ansi(GhosttyMode mode) {
|
||||
return (mode >> 15) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* DECRPM report state values.
|
||||
*
|
||||
* These correspond to the Ps2 parameter in a DECRPM response
|
||||
* sequence (CSI ? Ps1 ; Ps2 $ y).
|
||||
*/
|
||||
typedef enum {
|
||||
/** Mode is not recognized */
|
||||
GHOSTTY_MODE_REPORT_NOT_RECOGNIZED = 0,
|
||||
/** Mode is set (enabled) */
|
||||
GHOSTTY_MODE_REPORT_SET = 1,
|
||||
/** Mode is reset (disabled) */
|
||||
GHOSTTY_MODE_REPORT_RESET = 2,
|
||||
/** Mode is permanently set */
|
||||
GHOSTTY_MODE_REPORT_PERMANENTLY_SET = 3,
|
||||
/** Mode is permanently reset */
|
||||
GHOSTTY_MODE_REPORT_PERMANENTLY_RESET = 4,
|
||||
} GhosttyModeReportState;
|
||||
|
||||
/**
|
||||
* Encode a DECRPM (DEC Private Mode Report) response sequence.
|
||||
*
|
||||
* Writes a mode report escape sequence into the provided buffer.
|
||||
* The generated sequence has the form:
|
||||
* - DEC private mode: CSI ? Ps1 ; Ps2 $ y
|
||||
* - ANSI mode: CSI Ps1 ; Ps2 $ y
|
||||
*
|
||||
* If the buffer is too small, the function returns GHOSTTY_OUT_OF_SPACE
|
||||
* and writes the required buffer size to @p out_written. The caller can
|
||||
* then retry with a sufficiently sized buffer.
|
||||
*
|
||||
* @param mode The mode identifying the mode to report on
|
||||
* @param state The report state for this mode
|
||||
* @param buf Output buffer to write the encoded sequence into (may be NULL)
|
||||
* @param buf_len Size of the output buffer in bytes
|
||||
* @param[out] out_written On success, the number of bytes written. On
|
||||
* GHOSTTY_OUT_OF_SPACE, the required buffer size.
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_SPACE if the buffer
|
||||
* is too small
|
||||
*/
|
||||
GhosttyResult ghostty_mode_report_encode(
|
||||
GhosttyMode mode,
|
||||
GhosttyModeReportState state,
|
||||
char* buf,
|
||||
size_t buf_len,
|
||||
size_t* out_written);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif /* GHOSTTY_VT_MODES_H */
|
||||
@@ -7,10 +7,12 @@
|
||||
#ifndef GHOSTTY_VT_TERMINAL_H
|
||||
#define GHOSTTY_VT_TERMINAL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/modes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -192,7 +194,42 @@ void ghostty_terminal_vt_write(GhosttyTerminal terminal,
|
||||
* @ingroup terminal
|
||||
*/
|
||||
void ghostty_terminal_scroll_viewport(GhosttyTerminal terminal,
|
||||
GhosttyTerminalScrollViewport behavior);
|
||||
GhosttyTerminalScrollViewport behavior);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
GhosttyResult ghostty_terminal_mode_set(GhosttyTerminal terminal,
|
||||
GhosttyMode mode,
|
||||
bool value);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
@@ -165,6 +165,7 @@ comptime {
|
||||
@export(&c.osc_command_type, .{ .name = "ghostty_osc_command_type" });
|
||||
@export(&c.osc_command_data, .{ .name = "ghostty_osc_command_data" });
|
||||
@export(&c.focus_encode, .{ .name = "ghostty_focus_encode" });
|
||||
@export(&c.mode_report_encode, .{ .name = "ghostty_mode_report_encode" });
|
||||
@export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" });
|
||||
@export(&c.color_rgb_get, .{ .name = "ghostty_color_rgb_get" });
|
||||
@export(&c.sgr_new, .{ .name = "ghostty_sgr_new" });
|
||||
@@ -186,6 +187,8 @@ comptime {
|
||||
@export(&c.terminal_resize, .{ .name = "ghostty_terminal_resize" });
|
||||
@export(&c.terminal_vt_write, .{ .name = "ghostty_terminal_vt_write" });
|
||||
@export(&c.terminal_scroll_viewport, .{ .name = "ghostty_terminal_scroll_viewport" });
|
||||
@export(&c.terminal_mode_get, .{ .name = "ghostty_terminal_mode_get" });
|
||||
@export(&c.terminal_mode_set, .{ .name = "ghostty_terminal_mode_set" });
|
||||
|
||||
// On Wasm we need to export our allocator convenience functions.
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub const color = @import("color.zig");
|
||||
pub const focus = @import("focus.zig");
|
||||
pub const formatter = @import("formatter.zig");
|
||||
pub const modes = @import("modes.zig");
|
||||
pub const osc = @import("osc.zig");
|
||||
pub const key_event = @import("key_event.zig");
|
||||
pub const key_encode = @import("key_encode.zig");
|
||||
@@ -23,6 +24,8 @@ pub const color_rgb_get = color.rgb_get;
|
||||
|
||||
pub const focus_encode = focus.encode;
|
||||
|
||||
pub const mode_report_encode = modes.report_encode;
|
||||
|
||||
pub const formatter_terminal_new = formatter.terminal_new;
|
||||
pub const formatter_format_buf = formatter.format_buf;
|
||||
pub const formatter_format_alloc = formatter.format_alloc;
|
||||
@@ -90,11 +93,14 @@ pub const terminal_reset = terminal.reset;
|
||||
pub const terminal_resize = terminal.resize;
|
||||
pub const terminal_vt_write = terminal.vt_write;
|
||||
pub const terminal_scroll_viewport = terminal.scroll_viewport;
|
||||
pub const terminal_mode_get = terminal.mode_get;
|
||||
pub const terminal_mode_set = terminal.mode_set;
|
||||
|
||||
test {
|
||||
_ = color;
|
||||
_ = focus;
|
||||
_ = formatter;
|
||||
_ = modes;
|
||||
_ = osc;
|
||||
_ = key_event;
|
||||
_ = key_encode;
|
||||
|
||||
103
src/terminal/c/modes.zig
Normal file
103
src/terminal/c/modes.zig
Normal file
@@ -0,0 +1,103 @@
|
||||
const std = @import("std");
|
||||
const modes = @import("../modes.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
/// C: GhosttyModeReportState
|
||||
pub const ReportState = enum(c_int) {
|
||||
_,
|
||||
|
||||
fn toZig(self: ReportState) ?modes.Report.State {
|
||||
return std.meta.intToEnum(
|
||||
modes.Report.State,
|
||||
@intFromEnum(self),
|
||||
) catch null;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn report_encode(
|
||||
tag: modes.ModeTag.Backing,
|
||||
state: ReportState,
|
||||
out_: ?[*]u8,
|
||||
out_len: usize,
|
||||
out_written: *usize,
|
||||
) callconv(.c) Result {
|
||||
const mode_tag: modes.ModeTag = @bitCast(tag);
|
||||
const report: modes.Report = .{
|
||||
.tag = mode_tag,
|
||||
.state = state.toZig() orelse return .invalid_value,
|
||||
};
|
||||
|
||||
var writer: std.Io.Writer = .fixed(if (out_) |out| out[0..out_len] else &.{});
|
||||
report.encode(&writer) catch |err| switch (err) {
|
||||
error.WriteFailed => {
|
||||
var discarding: std.Io.Writer.Discarding = .init(&.{});
|
||||
report.encode(&discarding.writer) catch unreachable;
|
||||
out_written.* = @intCast(discarding.count);
|
||||
return .out_of_space;
|
||||
},
|
||||
};
|
||||
|
||||
out_written.* = writer.end;
|
||||
return .success;
|
||||
}
|
||||
|
||||
test "encode DEC mode set" {
|
||||
var buf: [modes.Report.max_size]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 1, .ansi = false });
|
||||
const result = report_encode(tag, @enumFromInt(1), &buf, buf.len, &written);
|
||||
try std.testing.expectEqual(.success, result);
|
||||
try std.testing.expectEqualStrings("\x1B[?1;1$y", buf[0..written]);
|
||||
}
|
||||
|
||||
test "encode DEC mode reset" {
|
||||
var buf: [modes.Report.max_size]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 1, .ansi = false });
|
||||
const result = report_encode(tag, @enumFromInt(2), &buf, buf.len, &written);
|
||||
try std.testing.expectEqual(.success, result);
|
||||
try std.testing.expectEqualStrings("\x1B[?1;2$y", buf[0..written]);
|
||||
}
|
||||
|
||||
test "encode ANSI mode" {
|
||||
var buf: [modes.Report.max_size]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 4, .ansi = true });
|
||||
const result = report_encode(tag, @enumFromInt(1), &buf, buf.len, &written);
|
||||
try std.testing.expectEqual(.success, result);
|
||||
try std.testing.expectEqualStrings("\x1B[4;1$y", buf[0..written]);
|
||||
}
|
||||
|
||||
test "encode not recognized" {
|
||||
var buf: [modes.Report.max_size]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 9999, .ansi = false });
|
||||
const result = report_encode(tag, @enumFromInt(0), &buf, buf.len, &written);
|
||||
try std.testing.expectEqual(.success, result);
|
||||
try std.testing.expectEqualStrings("\x1B[?9999;0$y", buf[0..written]);
|
||||
}
|
||||
|
||||
test "encode with insufficient buffer" {
|
||||
var buf: [1]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 1, .ansi = false });
|
||||
const result = report_encode(tag, @enumFromInt(1), &buf, buf.len, &written);
|
||||
try std.testing.expectEqual(.out_of_space, result);
|
||||
try std.testing.expect(written > 1);
|
||||
}
|
||||
|
||||
test "encode with invalid state" {
|
||||
var buf: [modes.Report.max_size]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 1, .ansi = false });
|
||||
const result = report_encode(tag, @enumFromInt(99), &buf, buf.len, &written);
|
||||
try std.testing.expectEqual(.invalid_value, result);
|
||||
}
|
||||
|
||||
test "encode with null buffer" {
|
||||
var written: usize = 0;
|
||||
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 1, .ansi = false });
|
||||
const result = report_encode(tag, @enumFromInt(1), null, 0, &written);
|
||||
try std.testing.expectEqual(.out_of_space, result);
|
||||
try std.testing.expect(written > 0);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ const testing = std.testing;
|
||||
const lib_alloc = @import("../../lib/allocator.zig");
|
||||
const CAllocator = lib_alloc.Allocator;
|
||||
const ZigTerminal = @import("../Terminal.zig");
|
||||
const modes = @import("../modes.zig");
|
||||
const size = @import("../size.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
@@ -98,6 +99,30 @@ pub fn reset(terminal_: Terminal) callconv(.c) void {
|
||||
t.fullReset();
|
||||
}
|
||||
|
||||
pub fn mode_get(
|
||||
terminal_: Terminal,
|
||||
tag: modes.ModeTag.Backing,
|
||||
out_value: *bool,
|
||||
) callconv(.c) Result {
|
||||
const t = terminal_ orelse return .invalid_value;
|
||||
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(.c) Result {
|
||||
const t = terminal_ orelse return .invalid_value;
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn free(terminal_: Terminal) callconv(.c) void {
|
||||
const t = terminal_ orelse return;
|
||||
|
||||
@@ -272,6 +297,87 @@ test "resize invalid value" {
|
||||
try testing.expectEqual(Result.invalid_value, resize(t, 80, 0));
|
||||
}
|
||||
|
||||
test "mode_get and mode_set" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
},
|
||||
));
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
try testing.expectEqual(Result.success, mode_set(t, insert, true));
|
||||
try testing.expectEqual(Result.success, mode_get(t, insert, &value));
|
||||
try testing.expect(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" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
},
|
||||
));
|
||||
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));
|
||||
}
|
||||
|
||||
test "mode_set unknown mode" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
},
|
||||
));
|
||||
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));
|
||||
}
|
||||
|
||||
test "vt_write" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
@@ -245,6 +245,9 @@ const ModeEntry = struct {
|
||||
/// 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.
|
||||
///
|
||||
/// NOTE: When adding a new mode entry, also add a corresponding
|
||||
/// GHOSTTY_MODE_* macro in include/ghostty/vt/modes.h.
|
||||
const entries: []const ModeEntry = &.{
|
||||
// ANSI
|
||||
.{ .name = "disable_keyboard", .value = 2, .ansi = true }, // KAM
|
||||
|
||||
Reference in New Issue
Block a user