lib/xkb/src/keysym/case.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const keysym = @import("root.zig");
3 const data = @import("generated/root.zig").case;
4
5 const Keysym = keysym.Keysym;
6 const Mapping = data.Mapping;
7
8 pub fn lower(value: Keysym) Keysym {
9 return mapped(data.lower[0..], value) orelse value;
10 }
11
12 pub fn upper(value: Keysym) Keysym {
13 return mapped(data.upper[0..], value) orelse value;
14 }
15
16 pub fn isLower(value: Keysym) bool {
17 return contains(data.upper[0..], value) and !contains(data.lower[0..], value);
18 }
19
20 pub fn isUpperOrTitle(value: Keysym) bool {
21 return contains(data.lower[0..], value);
22 }
23
24 fn mapped(entries: []const Mapping, value: Keysym) ?Keysym {
25 const index = find(entries, value) orelse return null;
26 return @fromBackingInt(@intCast(entries[index].output));
27 }
28
29 fn contains(entries: []const Mapping, value: Keysym) bool {
30 return find(entries, value) != null;
31 }
32
33 fn find(entries: []const Mapping, value: Keysym) ?usize {
34 const raw = @backingInt(value);
35 var low: usize = 0;
36 var high: usize = entries.len;
37 while (low < high) {
38 const middle = low + (high - low) / 2;
39 const entry = entries[middle];
40 if (raw < entry.input) {
41 high = middle;
42 } else if (raw > entry.input) {
43 low = middle + 1;
44 } else {
45 return middle;
46 }
47 }
48 return null;
49 }
50
51 test "every pinned simple case mapping resolves" {
52 try std.testing.expectEqual(@as(usize, data.lower_count), data.lower.len);
53 try std.testing.expectEqual(@as(usize, data.upper_count), data.upper.len);
54 for (data.lower) |entry| {
55 const output: Keysym = @fromBackingInt(@intCast(entry.output));
56 try std.testing.expectEqual(output, lower(@fromBackingInt(@intCast(entry.input))));
57 try std.testing.expectEqual(output, lower(output));
58 }
59 for (data.upper) |entry| {
60 const output: Keysym = @fromBackingInt(@intCast(entry.output));
61 try std.testing.expectEqual(output, upper(@fromBackingInt(@intCast(entry.input))));
62 try std.testing.expectEqual(output, upper(output));
63 }
64 }
65
66 test "legacy Unicode and titlecase mappings retain XKB values" {
67 const cases = [_]struct { input: u32, lower: u32, upper: u32 }{
68 .{ .input = 0x00000041, .lower = 0x00000061, .upper = 0x00000041 },
69 .{ .input = 0x000000df, .lower = 0x000000df, .upper = 0x01001e9e },
70 .{ .input = 0x01001e9e, .lower = 0x000000df, .upper = 0x01001e9e },
71 .{ .input = 0x01000130, .lower = 0x00000069, .upper = 0x01000130 },
72 .{ .input = 0x010001f2, .lower = 0x010001f3, .upper = 0x010001f1 },
73 .{ .input = 0x000007c1, .lower = 0x000007e1, .upper = 0x000007c1 },
74 };
75 for (cases) |case| {
76 const input: Keysym = @fromBackingInt(@intCast(case.input));
77 try std.testing.expectEqual(@as(Keysym, @fromBackingInt(@intCast(case.lower))), lower(input));
78 try std.testing.expectEqual(@as(Keysym, @fromBackingInt(@intCast(case.upper))), upper(input));
79 }
80 }
81
82 test "lower upper and title predicates retain XKB boundaries" {
83 const cases = [_]struct { input: u32, is_lower: bool, is_upper_or_title: bool }{
84 .{ .input = 0x00000041, .is_lower = false, .is_upper_or_title = true },
85 .{ .input = 0x00000061, .is_lower = true, .is_upper_or_title = false },
86 .{ .input = 0x000000df, .is_lower = true, .is_upper_or_title = false },
87 .{ .input = 0x01001e9e, .is_lower = false, .is_upper_or_title = true },
88 .{ .input = 0x010001f1, .is_lower = false, .is_upper_or_title = true },
89 .{ .input = 0x010001f2, .is_lower = false, .is_upper_or_title = true },
90 .{ .input = 0x010001f3, .is_lower = true, .is_upper_or_title = false },
91 .{ .input = 0x01001f80, .is_lower = true, .is_upper_or_title = false },
92 .{ .input = 0x01001f88, .is_lower = false, .is_upper_or_title = true },
93 .{ .input = 0x0000ff0d, .is_lower = false, .is_upper_or_title = false },
94 .{ .input = 0x010005d0, .is_lower = false, .is_upper_or_title = false },
95 };
96 for (cases) |case| {
97 const input: Keysym = @fromBackingInt(@intCast(case.input));
98 try std.testing.expectEqual(case.is_lower, isLower(input));
99 try std.testing.expectEqual(case.is_upper_or_title, isUpperOrTitle(input));
100 }
101 }