mirror of
https://github.com/aphrody-labs/opentui.git
synced 2026-10-13 21:08:53 +00:00
native: state the input ABI's REPLIES and overlap rules
Two gaps in the ot_session_input_* contract survived independent refutation: - REPLIES also decides how a complete CSI 1 ; N R is classified: a cursor report, or modified F3. The header described only its timeout effect, and did not say that setup and resume queries count as outstanding. - feed copies paste bytes from the input into payload in the same call, so the buffers must not overlap. Documentation only; the behavior is unchanged. The comptime check compared one constant per family. Check every parser constant against its OT_INPUT_* twin by name instead, which needs key.kp0 renamed to kp_0. The busy-Context test now tells one rejected ESC from a parsed one by its expiry.
This commit is contained in:
5 files changed
+48
-41
No files matched your search
@@ -961,7 +961,7 @@ typedef struct ot_input_drain {
|
||||
* at least OT_INPUT_EVENTS_MIN and payload_capacity at least
|
||||
* OT_INPUT_PAYLOAD_BYTES_MIN; the call consumes input until either runs low and
|
||||
* reports consumed. Records and payload are borrowed for the call and never
|
||||
* retained. Accepted in any open session state; closing, closed, failed, and
|
||||
* retained. bytes, records, payload, and out_drain must not overlap. Accepted in any open session state; closing, closed, failed, and
|
||||
* cancelled sessions reject. No allocation, no I/O. */
|
||||
ot_status ot_session_input_feed(
|
||||
ot_context *context,
|
||||
@@ -976,10 +976,12 @@ ot_status ot_session_input_feed(
|
||||
ot_input_drain *out_drain);
|
||||
|
||||
/* flags is a combination of OT_INPUT_EXPECT_* bits; other bits must be zero.
|
||||
* REPLIES keeps a partial CSI reply waiting past the timeout while the host
|
||||
* awaits one. KITTY_KEYBOARD does the same for Kitty keys; the session also
|
||||
* applies it while its terminal has Kitty keyboard enabled. Changing flags
|
||||
* emits nothing; the next feed resolves a unit that may no longer wait. */
|
||||
* Set REPLIES while any query is outstanding, including the capability queries
|
||||
* that setup and resume publish. It keeps a partial CSI reply waiting past the
|
||||
* timeout, and it makes a complete CSI 1 ; N R (N >= 2) a CURSOR_POSITION reply
|
||||
* instead of a modified F3 key. KITTY_KEYBOARD keeps a partial Kitty key waiting;
|
||||
* the session also applies it while its terminal has Kitty keyboard enabled.
|
||||
* Changing flags emits nothing; the next feed resolves a unit that may no longer wait. */
|
||||
ot_status ot_session_input_expect(ot_context *context, const ot_handle *session, uint32_t flags);
|
||||
|
||||
/* Drop any partial unit and mouse button state. Expectations are kept. */
|
||||
|
||||
@@ -2834,6 +2834,7 @@ ot_status ot_session_pump(
|
||||
* OT_INPUT_PAYLOAD_BYTES_MIN; the call consumes input until either runs low and
|
||||
* reports consumed. A bracketed paste arrives as one or more PASTE records from
|
||||
* START to END. Records and payload are borrowed for the call and never retained.
|
||||
* bytes, records, payload, and out_drain must not overlap.
|
||||
* Accepted in any open session state; closing, closed, failed, and cancelled
|
||||
* sessions reject. A rejected call leaves out_drain, the records, and parser
|
||||
* state unchanged. No allocation, no I/O. */
|
||||
@@ -2850,10 +2851,12 @@ ot_status ot_session_input_feed(
|
||||
ot_input_drain *out_drain);
|
||||
|
||||
/* flags is a combination of OT_INPUT_EXPECT_* bits; other bits must be zero.
|
||||
* REPLIES keeps a partial CSI reply waiting past the timeout while the host
|
||||
* awaits one. KITTY_KEYBOARD does the same for Kitty keys; the session also
|
||||
* applies it while its terminal has Kitty keyboard enabled. Changing flags
|
||||
* emits nothing; the next feed resolves a unit that may no longer wait. */
|
||||
* Set REPLIES while any query is outstanding, including the capability queries
|
||||
* that setup and resume publish. It keeps a partial CSI reply waiting past the
|
||||
* timeout, and it makes a complete CSI 1 ; N R (N >= 2) a CURSOR_POSITION reply
|
||||
* instead of a modified F3 key. KITTY_KEYBOARD keeps a partial Kitty key waiting;
|
||||
* the session also applies it while its terminal has Kitty keyboard enabled.
|
||||
* Changing flags emits nothing; the next feed resolves a unit that may no longer wait. */
|
||||
ot_status ot_session_input_expect(ot_context *context, const ot_handle *session, uint32_t flags);
|
||||
|
||||
/* Drop any partial unit and mouse button state. Expectations are kept. */
|
||||
|
||||
@@ -13,31 +13,31 @@ comptime {
|
||||
if (@offsetOf(Event, field.name) != @offsetOf(c.ot_input_event, field.name))
|
||||
@compileError("ot_input_event differs from input_parser.Event: " ++ field.name);
|
||||
}
|
||||
const Kind = input_parser.Kind;
|
||||
const flags = input_parser.flags;
|
||||
const checks = .{
|
||||
.{ c.OT_INPUT_KEY, @intFromEnum(Kind.key) },
|
||||
.{ c.OT_INPUT_MOUSE, @intFromEnum(Kind.mouse) },
|
||||
.{ c.OT_INPUT_PASTE, @intFromEnum(Kind.paste) },
|
||||
.{ c.OT_INPUT_FOCUS, @intFromEnum(Kind.focus) },
|
||||
.{ c.OT_INPUT_REPLY, @intFromEnum(Kind.reply) },
|
||||
.{ c.OT_INPUT_KEY_RELEASE, @intFromEnum(input_parser.KeyAction.release) },
|
||||
.{ c.OT_INPUT_MOUSE_SCROLL, @intFromEnum(input_parser.MouseAction.scroll) },
|
||||
.{ c.OT_INPUT_MOUSE_BUTTON_NONE, input_parser.mouse_button_none },
|
||||
.{ c.OT_INPUT_MOD_NUM_LOCK, @as(u8, @bitCast(input_parser.Modifiers{ .num_lock = true })) },
|
||||
.{ c.OT_INPUT_KEY_TEXT_TRUNCATED, flags.key_text_truncated },
|
||||
.{ c.OT_INPUT_PASTE_END, flags.paste_end },
|
||||
.{ c.OT_INPUT_REPLY_CURSOR_POSITION, flags.reply_cursor_position },
|
||||
.{ c.OT_INPUT_REPLY_APC, @intFromEnum(input_parser.Protocol.apc) },
|
||||
.{ c.OT_INPUT_KEY_KP_BEGIN, input_parser.key.kp_begin },
|
||||
.{ c.OT_INPUT_KEY_F35, input_parser.key.f(35) },
|
||||
.{ c.OT_INPUT_KEY_ISO_LEVEL5_SHIFT, input_parser.key.functional_last },
|
||||
.{ c.OT_INPUT_EXPECT_KITTY_KEYBOARD, @as(u8, @bitCast(input_parser.Expectations{ .kitty_keyboard = true })) },
|
||||
.{ c.OT_INPUT_EVENTS_MIN, input_parser.events_per_byte_max },
|
||||
.{ c.OT_INPUT_PAYLOAD_BYTES_MIN, input_parser.payload_per_byte_max },
|
||||
.{ c.OT_INPUT_TIMEOUT_NS, input_parser.timeout_ns },
|
||||
};
|
||||
for (checks) |check| if (check[0] != check[1]) @compileError("opentui.h input constant differs from input_parser");
|
||||
@setEvalBranchQuota(20_000);
|
||||
// Every parser constant has an equal header twin named OT_INPUT_<prefix><NAME>.
|
||||
for (std.meta.tags(input_parser.Kind)) |tag| checkTwin("", @tagName(tag), @intFromEnum(tag));
|
||||
for (std.meta.tags(input_parser.KeyAction)) |tag| checkTwin("KEY_", @tagName(tag), @intFromEnum(tag));
|
||||
for (std.meta.tags(input_parser.MouseAction)) |tag| checkTwin("MOUSE_", @tagName(tag), @intFromEnum(tag));
|
||||
for (std.meta.tags(input_parser.Protocol)) |tag| checkTwin("REPLY_", @tagName(tag), @intFromEnum(tag));
|
||||
for (std.meta.fields(input_parser.Modifiers), 0..) |field, bit| checkTwin("MOD_", field.name, 1 << bit);
|
||||
for (std.meta.fields(input_parser.Expectations)[0..2], 0..) |field, bit| checkTwin("EXPECT_", field.name, 1 << bit);
|
||||
for (@typeInfo(input_parser.flags).@"struct".decls) |decl| checkTwin("", decl.name, @field(input_parser.flags, decl.name));
|
||||
for (@typeInfo(input_parser.key).@"struct".decls) |decl| {
|
||||
// Range bounds and the F(n) helper have no twin of their own.
|
||||
if (std.mem.startsWith(u8, decl.name, "functional_") or std.mem.eql(u8, decl.name, "f")) continue;
|
||||
checkTwin("KEY_", decl.name, @field(input_parser.key, decl.name));
|
||||
}
|
||||
checkTwin("", "mouse_button_none", input_parser.mouse_button_none);
|
||||
checkTwin("", "events_min", input_parser.events_per_byte_max);
|
||||
checkTwin("", "payload_bytes_min", input_parser.payload_per_byte_max);
|
||||
checkTwin("", "timeout_ns", input_parser.timeout_ns);
|
||||
}
|
||||
|
||||
fn checkTwin(comptime prefix: []const u8, comptime name: []const u8, comptime value: u64) void {
|
||||
comptime var upper: [name.len]u8 = undefined;
|
||||
_ = std.ascii.upperString(&upper, name);
|
||||
const twin = "OT_INPUT_" ++ prefix ++ upper;
|
||||
if (!@hasDecl(c, twin) or @field(c, twin) != value) @compileError(twin ++ " differs from input_parser");
|
||||
}
|
||||
|
||||
const expect_flags: u32 = c.OT_INPUT_EXPECT_REPLIES | c.OT_INPUT_EXPECT_KITTY_KEYBOARD;
|
||||
@@ -227,7 +227,9 @@ test "Context input ABI rejects malformed calls, foreign handles, and closed ses
|
||||
try std.testing.expectEqual(c.OT_CONTEXT_BUSY, f.feed("\x1b", 1));
|
||||
context.core.mutating = false;
|
||||
try std.testing.expectEqual(c.OT_OK, f.feed("\x1b", 1));
|
||||
try std.testing.expectEqual(@as(u32, 0), f.drain.count);
|
||||
try std.testing.expectEqual(c.OT_OK, f.feed("", 1 + c.OT_INPUT_TIMEOUT_NS));
|
||||
// One plain Escape: a parsed rejected ESC would make this Alt+Escape.
|
||||
try std.testing.expectEqual(@as(u8, 0), f.records[0].modifiers);
|
||||
const thread = try std.Thread.spawn(.{}, struct {
|
||||
fn run(owner: *abi.ContextHandle, session: *const c.ot_handle) void {
|
||||
std.testing.expectEqual(c.OT_WRONG_THREAD, ot_session_input_feed(owner, session, null, 0, 0, null, 0, null, 0, null)) catch unreachable;
|
||||
|
||||
@@ -75,7 +75,7 @@ pub const key = struct {
|
||||
pub const end: u32 = 57357;
|
||||
pub const menu: u32 = 57363;
|
||||
pub const f1: u32 = 57364;
|
||||
pub const kp0: u32 = 57399;
|
||||
pub const kp_0: u32 = 57399;
|
||||
pub const kp_begin: u32 = 57427;
|
||||
pub const functional_first: u32 = 57344;
|
||||
pub const functional_last: u32 = 57454;
|
||||
@@ -1041,7 +1041,7 @@ fn functionalKey(code: u32) ?u32 {
|
||||
/// What a printable keypad key inserts.
|
||||
fn keypadText(code: u32) ?u8 {
|
||||
return switch (code) {
|
||||
key.kp0...key.kp0 + 9 => @as(u8, @intCast('0' + code - key.kp0)),
|
||||
key.kp_0...key.kp_0 + 9 => @as(u8, @intCast('0' + code - key.kp_0)),
|
||||
57409 => '.',
|
||||
57410 => '/',
|
||||
57411 => '*',
|
||||
|
||||
@@ -569,7 +569,7 @@ test "input parser modifyOtherKeys" {
|
||||
.{ "\x1b[27;2;127~", k(K.backspace, SHIFT) },
|
||||
.{ "\x1b[27;5;8~", k(K.backspace, CTRL) },
|
||||
.{ "\x1b[27;2;53~", k('5', SHIFT) },
|
||||
.{ "\x1b[27;5;57399~", kt(K.kp0, CTRL, "0") },
|
||||
.{ "\x1b[27;5;57399~", kt(K.kp_0, CTRL, "0") },
|
||||
.{ "\x1b[27;5;3~", reply("\x1b[27;5;3~") },
|
||||
.{ "\x1b[27;5~", reply("\x1b[27;5~") },
|
||||
});
|
||||
@@ -674,9 +674,9 @@ test "input parser Kitty keys report modifiers, events, and text" {
|
||||
.{ "\x1b[57349;5u", k(K.delete, CTRL) },
|
||||
.{ "\x1b[57376u", k(K.f(13), NONE) },
|
||||
.{ "\x1b[57414u", kt(57414, NONE, "") },
|
||||
.{ "\x1b[57399u", kt(K.kp0, NONE, "0") },
|
||||
.{ "\x1b[57400;5u", kt(K.kp0 + 1, CTRL, "1") },
|
||||
.{ "\x1b[57400;1;120u", kt(K.kp0 + 1, NONE, "x") },
|
||||
.{ "\x1b[57399u", kt(K.kp_0, NONE, "0") },
|
||||
.{ "\x1b[57400;5u", kt(K.kp_0 + 1, CTRL, "1") },
|
||||
.{ "\x1b[57400;1;120u", kt(K.kp_0 + 1, NONE, "x") },
|
||||
.{ "\x1b[57409u", kt(57409, NONE, ".") },
|
||||
.{ "\x1b[57416u", kt(57416, NONE, ",") },
|
||||
.{ "\x1b[57441u", k(57441, NONE) },
|
||||
|
||||
Reference in new issue
Block a user