input: KeyEncoder legacy encoding handles old keyCallback logic

This commit is contained in:
Mitchell Hashimoto
2023-08-16 09:30:36 -07:00
parent f57fd99d3e
commit 6555bb1410

View File

@ -9,19 +9,102 @@ const std = @import("std");
const testing = std.testing; const testing = std.testing;
const key = @import("key.zig"); const key = @import("key.zig");
const function_keys = @import("function_keys.zig");
key: key.Key, event: key.Event,
binding_mods: key.Mods,
/// Initialize /// The state of various modes of a terminal that impact encoding.
fn init(event: key.Event) KeyEncoder { cursor_key_application: bool = false,
const effective_mods = event.effectiveMods(); keypad_key_application: bool = false,
modify_other_keys_state_2: bool = false,
/// Perform legacy encoding of the key event. "Legacy" in this case
/// is referring to the behavior of traditional terminals, plus
/// xterm's `modifyOtherKeys`, plus Paul Evans's "fixterms" spec.
/// These together combine the legacy protocol because they're all
/// meant to be extensions that do not change any existing behavior
/// and therefore safe to combine.
fn legacy(
self: *const KeyEncoder,
buf: []u8,
) ![]const u8 {
const effective_mods = self.event.effectiveMods();
const binding_mods = effective_mods.binding(); const binding_mods = effective_mods.binding();
return .{ // Legacy encoding only does press/repeat
.key = event.key, if (self.event.action != .press and
.binding_mods = binding_mods, self.event.action != .repeat) return "";
};
// If we match a PC style function key then that is our result.
if (pcStyleFunctionKey(
self.event.key,
binding_mods,
self.cursor_key_application,
self.keypad_key_application,
self.modify_other_keys_state_2,
)) |sequence| return sequence;
// If we match a control sequence, we output that directly.
if (ctrlSeq(self.event.key, binding_mods)) |char| {
// C0 sequences support alt-as-esc prefixing.
if (binding_mods.alt) {
if (buf.len < 2) return error.OutOfMemory;
buf[0] = 0x1B;
buf[1] = char;
return buf[0..2];
}
if (buf.len < 1) return error.OutOfMemory;
buf[0] = char;
return buf[0..1];
}
return "";
}
/// Determines whether the key should be encoded in the xterm
/// "PC-style Function Key" syntax (roughly). This is a hardcoded
/// table of keys and modifiers that result in a specific sequence.
fn pcStyleFunctionKey(
keyval: key.Key,
mods: key.Mods,
cursor_key_application: bool,
keypad_key_application: bool,
modify_other_keys: bool, // True if state 2
) ?[]const u8 {
const mods_int = mods.int();
for (function_keys.keys.get(keyval)) |entry| {
switch (entry.cursor) {
.any => {},
.normal => if (cursor_key_application) continue,
.application => if (!cursor_key_application) continue,
}
switch (entry.keypad) {
.any => {},
.normal => if (keypad_key_application) continue,
.application => if (!keypad_key_application) continue,
}
switch (entry.modify_other_keys) {
.any => {},
.set => if (modify_other_keys) continue,
.set_other => if (!modify_other_keys) continue,
}
const entry_mods_int = entry.mods.int();
if (entry_mods_int == 0) {
if (mods_int != 0 and !entry.mods_empty_is_any) continue;
// mods are either empty, or empty means any so we allow it.
} else if (entry_mods_int != mods_int) {
// any set mods require an exact match
continue;
}
return entry.sequence;
}
return null;
} }
/// Returns the C0 byte for the key event if it should be used. /// Returns the C0 byte for the key event if it should be used.
@ -31,11 +114,11 @@ fn init(event: key.Event) KeyEncoder {
/// This will return null if the key event should not be converted /// This will return null if the key event should not be converted
/// into a C0 byte. There are many cases for this and you should read /// into a C0 byte. There are many cases for this and you should read
/// the source code to understand them. /// the source code to understand them.
fn ctrlSeq(self: *const KeyEncoder) ?u8 { fn ctrlSeq(keyval: key.Key, mods: key.Mods) ?u8 {
// Remove alt from our modifiers because it does not impact whether // Remove alt from our modifiers because it does not impact whether
// we are generating a ctrl sequence. // we are generating a ctrl sequence.
const unalt_mods = unalt_mods: { const unalt_mods = unalt_mods: {
var unalt_mods = self.binding_mods; var unalt_mods = mods;
unalt_mods.alt = false; unalt_mods.alt = false;
break :unalt_mods unalt_mods; break :unalt_mods unalt_mods;
}; };
@ -49,7 +132,7 @@ fn ctrlSeq(self: *const KeyEncoder) ?u8 {
// with 0x1F. However, not all apprt key translation will properly // with 0x1F. However, not all apprt key translation will properly
// generate the correct value so we just hardcode this based on // generate the correct value so we just hardcode this based on
// logical key. // logical key.
return switch (self.key) { return switch (keyval) {
.space => 0, .space => 0,
.slash => 0x1F, .slash => 0x1F,
.zero => 0x30, .zero => 0x30,
@ -95,37 +178,73 @@ fn ctrlSeq(self: *const KeyEncoder) ?u8 {
}; };
} }
test "legacy: ctrl+alt+c" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .c,
.mods = .{ .ctrl = true, .alt = true },
},
};
const actual = try enc.legacy(&buf);
try testing.expectEqualStrings("\x1b\x03", actual);
}
test "legacy: ctrl+c" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .c,
.mods = .{ .ctrl = true },
},
};
const actual = try enc.legacy(&buf);
try testing.expectEqualStrings("\x03", actual);
}
test "legacy: ctrl+space" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .space,
.mods = .{ .ctrl = true },
},
};
const actual = try enc.legacy(&buf);
try testing.expectEqualStrings("\x00", actual);
}
test "legacy: ctrl+shift+backspace" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .backspace,
.mods = .{ .ctrl = true, .shift = true },
},
};
const actual = try enc.legacy(&buf);
try testing.expectEqualStrings("\x08", actual);
}
test "ctrlseq: normal ctrl c" { test "ctrlseq: normal ctrl c" {
const enc = init(.{ .key = .c, .mods = .{ .ctrl = true } }); const seq = ctrlSeq(.c, .{ .ctrl = true });
const seq = enc.ctrlSeq();
try testing.expectEqual(@as(u8, 0x03), seq.?); try testing.expectEqual(@as(u8, 0x03), seq.?);
} }
test "ctrlseq: alt should be allowed" { test "ctrlseq: alt should be allowed" {
const enc = init(.{ .key = .c, .mods = .{ .alt = true, .ctrl = true } }); const seq = ctrlSeq(.c, .{ .alt = true, .ctrl = true });
const seq = enc.ctrlSeq();
try testing.expectEqual(@as(u8, 0x03), seq.?); try testing.expectEqual(@as(u8, 0x03), seq.?);
} }
test "ctrlseq: no ctrl does nothing" { test "ctrlseq: no ctrl does nothing" {
const enc = init(.{ .key = .c, .mods = .{} }); try testing.expect(ctrlSeq(.c, .{}) == null);
try testing.expect(enc.ctrlSeq() == null);
} }
test "ctrlseq: shift does not generate ctrl seq" { test "ctrlseq: shift does not generate ctrl seq" {
{ try testing.expect(ctrlSeq(.c, .{ .shift = true }) == null);
const enc = init(.{ try testing.expect(ctrlSeq(.c, .{ .shift = true, .ctrl = true }) == null);
.key = .c,
.mods = .{ .shift = true },
});
try testing.expect(enc.ctrlSeq() == null);
}
{
const enc = init(.{
.key = .c,
.mods = .{ .shift = true, .ctrl = true },
});
try testing.expect(enc.ctrlSeq() == null);
}
} }