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 }