Add physical keyboard parity aliases

This commit is contained in:
slhx agent
2026-06-21 14:30:31 +02:00
parent 616eea670b
commit e96d33edb9
5 changed files with 156 additions and 10 deletions
+131
View File
@@ -928,6 +928,12 @@ pub const Client = struct {
if (std.mem.eql(u8, key_name, "escape")) return self.handleInput("\x1b");
if (std.mem.eql(u8, key_name, "left")) return self.handleInput("\x1b[D");
if (std.mem.eql(u8, key_name, "right")) return self.handleInput("\x1b[C");
if (std.mem.eql(u8, key_name, "up")) return self.handleInput("\x1b[A");
if (std.mem.eql(u8, key_name, "down")) return self.handleInput("\x1b[B");
if (std.mem.eql(u8, key_name, "home")) return self.handleInput("\x1b[H");
if (std.mem.eql(u8, key_name, "end")) return self.handleInput("\x1b[F");
if (std.mem.eql(u8, key_name, "page_up")) return self.handleInput("\x1b[5~");
if (std.mem.eql(u8, key_name, "page_down")) return self.handleInput("\x1b[6~");
if (key_name.len == 1) return self.handleInput(key_name);
return Error.UnknownTraceEvent;
}
@@ -1021,6 +1027,7 @@ pub const Client = struct {
if (std.mem.eql(u8, text, "U")) return self.redoEdit();
if (std.mem.eql(u8, text, "0")) return self.applyProtocol("command move_line_start");
if (std.mem.eql(u8, text, "$")) return self.applyProtocol("command move_line_end");
if (std.mem.eql(u8, text, "%")) return self.jumpToMatch(null);
if (std.mem.eql(u8, text, "m")) return self.openPrefix(.match);
if (std.mem.eql(u8, text, "g")) return self.openPrefix(.go);
if (std.mem.eql(u8, text, "j")) return self.repeatProtocol("command move_down", self.takeRepeat());
@@ -1042,6 +1049,10 @@ pub const Client = struct {
.arrow_right => try self.repeatProtocol("command move_right", self.takeRepeat()),
.arrow_up => try self.repeatProtocol("command move_up", self.takeRepeat()),
.arrow_down => try self.repeatProtocol("command move_down", self.takeRepeat()),
.home => try self.applyProtocol("command move_line_start"),
.end => try self.applyProtocol("command move_line_end"),
.page_up => try self.repeatProtocol("command move_up", @max(@as(usize, 1), self.viewport.height - 2)),
.page_down => try self.repeatProtocol("command move_down", @max(@as(usize, 1), self.viewport.height - 2)),
else => self.unknownPrefixOrInput("normal"),
},
.unknown => self.unknownPrefixOrInput("normal"),
@@ -1060,6 +1071,10 @@ pub const Client = struct {
.backspace => try self.applyMutatingProtocol("command delete_backward"),
.arrow_left => try self.applyProtocol("command move_left"),
.arrow_right => try self.applyProtocol("command move_right"),
.arrow_up => try self.applyProtocol("command move_up"),
.arrow_down => try self.applyProtocol("command move_down"),
.home => try self.applyProtocol("command move_line_start"),
.end => try self.applyProtocol("command move_line_end"),
else => {},
},
.unknown => {},
@@ -1074,6 +1089,7 @@ pub const Client = struct {
self.message = "normal";
return;
}
if (std.mem.eql(u8, text, "%")) return self.jumpToMatch(null);
if (std.mem.eql(u8, text, "m")) return self.openPrefix(.match);
if (std.mem.eql(u8, text, "g")) return self.openPrefix(.go);
if (text.len == 1) {
@@ -1092,6 +1108,10 @@ pub const Client = struct {
},
.arrow_left => try self.applyProtocol("command move_left"),
.arrow_right => try self.applyProtocol("command move_right"),
.arrow_up => try self.applyProtocol("command move_up"),
.arrow_down => try self.applyProtocol("command move_down"),
.home => try self.applyProtocol("command move_line_start"),
.end => try self.applyProtocol("command move_line_end"),
else => {},
},
.unknown => self.unknownPrefixOrInput("select"),
@@ -1110,6 +1130,8 @@ pub const Client = struct {
.key => |key| switch (key) {
.arrow_down => try self.applyProtocol("command list_down"),
.arrow_up => try self.applyProtocol("command list_up"),
.page_down => try self.repeatProtocol("command list_down", @max(@as(usize, 1), self.viewport.height - 2)),
.page_up => try self.repeatProtocol("command list_up", @max(@as(usize, 1), self.viewport.height - 2)),
.enter => try self.openActivePanelItem(),
.escape => try self.closeActivePanel(),
else => {},
@@ -4581,3 +4603,112 @@ test "regular: provider edit rows parse source scope and replacement" {
defer std.testing.allocator.free(action_row);
try std.testing.expectEqualStrings("zls", try lsp_mod.providerFromActionPanelRow(action_row));
}
test "regular: physical arrows home end page and escape match editor movement" {
var client = try Client.init(std.testing.allocator, .{ .width = 64, .height = 5 });
defer client.deinit();
try client.handleTraceLine("open one\ntwo\nthree\nfour\nfive");
var snap = try client.session.snapshot();
const start = snap.cursor_byte;
try client.handleTraceLine("key page_down");
snap = try client.session.snapshot();
try std.testing.expect(snap.cursor_byte > start);
try client.handleTraceLine("key home");
snap = try client.session.snapshot();
const line_start = snap.cursor_byte;
try client.handleTraceLine("key end");
snap = try client.session.snapshot();
try std.testing.expect(snap.cursor_byte >= line_start);
try client.handleTraceLine("key page_up");
snap = try client.session.snapshot();
try std.testing.expect(snap.cursor_byte <= line_start);
try client.handleInput("i");
try client.handleInput("x");
try client.handleTraceLine("key escape");
try client.handleInput("u");
snap = try client.session.snapshot();
try std.testing.expect(std.mem.indexOf(u8, snap.bytes, "onxe") == null);
}
test "regular: physical digit count matches repeated arrow movement" {
var physical = try Client.init(std.testing.allocator, .{ .width = 64, .height = 6 });
defer physical.deinit();
try physical.handleTraceLine("open a\nb\nc\nd");
try physical.handleInput("3");
try physical.handleTraceLine("key down");
const physical_snap = try physical.session.snapshot();
var repeated = try Client.init(std.testing.allocator, .{ .width = 64, .height = 6 });
defer repeated.deinit();
try repeated.handleTraceLine("open a\nb\nc\nd");
try repeated.handleTraceLine("key down");
try repeated.handleTraceLine("key down");
try repeated.handleTraceLine("key down");
const repeated_snap = try repeated.session.snapshot();
try std.testing.expectEqual(repeated_snap.cursor_byte, physical_snap.cursor_byte);
}
test "regular: physical percent and mobile match rail jump to same pair" {
var physical = try Client.init(std.testing.allocator, .{ .width = 64, .height = 6 });
defer physical.deinit();
try physical.handleTraceLine("open (abc)");
try physical.handleInput("%");
var snap = try physical.session.snapshot();
try std.testing.expectEqual(@as(usize, 4), snap.cursor_byte);
var mobile = try Client.init(std.testing.allocator, .{ .width = 64, .height = 6 });
defer mobile.deinit();
try mobile.handleTraceLine("open (abc)");
try mobile.handleInput("m");
try mobile.handleInput("m");
snap = try mobile.session.snapshot();
try std.testing.expectEqual(@as(usize, 4), snap.cursor_byte);
}
test "regular: insert/select modes accept arrows while Space n remains mobile escape" {
var client = try Client.init(std.testing.allocator, .{ .width = 64, .height = 6 });
defer client.deinit();
try client.handleTraceLine("open ab\ncd");
try client.handleInput("i");
try client.handleTraceLine("key end");
try client.handleInput("X");
try client.handleInput(" ");
try client.handleInput("n");
var snap = try client.session.snapshot();
try std.testing.expectEqualStrings("abX\ncd", snap.bytes);
try client.handleInput("s");
try client.handleTraceLine("key up");
try client.handleTraceLine("key home");
try client.handleInput("n");
snap = try client.session.snapshot();
try std.testing.expect(snap.selection != null or snap.cursor_byte == 0);
}
test "regular: panels accept physical page keys and enter without breaking arrows" {
var client = try Client.init(std.testing.allocator, .{ .width = 64, .height = 4 });
defer client.deinit();
try client.handleTraceLine("list_open files a.zig|b.zig|c.zig|d.zig|e.zig");
try client.handleTraceLine("key page_down");
const after_down = try client.session.activeListItem();
try std.testing.expect(!std.mem.eql(u8, after_down, "a.zig"));
try client.handleTraceLine("key up");
const after_up = try client.session.activeListItem();
try std.testing.expect(!std.mem.eql(u8, after_down, after_up));
}
test "adversarial: unknown physical key names stay rejected and leader help stays narrow" {
var client = try Client.init(std.testing.allocator, .{ .width = 72, .height = 5 });
defer client.deinit();
try std.testing.expectError(Error.UnknownTraceEvent, client.handleTraceLine("key f1"));
try client.handleInput(" ");
try std.testing.expect(std.mem.indexOf(u8, client.leader.status(), "digits/%/Esc ok") != null);
const frame = try client.render(std.testing.allocator);
defer std.testing.allocator.free(frame);
try assertLinesFit(frame, 72);
}