input: converting set entries to Entry from Action

This commit is contained in:
Mitchell Hashimoto
2024-08-15 16:46:06 -07:00
committed by Mitchell Hashimoto
parent bc4eab4af7
commit 7dfad49e40
3 changed files with 93 additions and 59 deletions

View File

@ -117,13 +117,17 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
var widest_key: usize = 0; var widest_key: usize = 0;
var buf: [64]u8 = undefined; var buf: [64]u8 = undefined;
while (iter.next()) |bind| { while (iter.next()) |bind| {
const action = switch (bind.value_ptr.*) {
.leader => continue, // TODO: support this
.action, .action_unconsumed => |action| action,
};
const key = switch (bind.key_ptr.key) { const key = switch (bind.key_ptr.key) {
.translated => |k| try std.fmt.bufPrint(&buf, "{s}", .{@tagName(k)}), .translated => |k| try std.fmt.bufPrint(&buf, "{s}", .{@tagName(k)}),
.physical => |k| try std.fmt.bufPrint(&buf, "physical:{s}", .{@tagName(k)}), .physical => |k| try std.fmt.bufPrint(&buf, "physical:{s}", .{@tagName(k)}),
.unicode => |c| try std.fmt.bufPrint(&buf, "{u}", .{c}), .unicode => |c| try std.fmt.bufPrint(&buf, "{u}", .{c}),
}; };
widest_key = @max(widest_key, win.gwidth(key)); widest_key = @max(widest_key, win.gwidth(key));
try bindings.append(.{ .trigger = bind.key_ptr.*, .action = bind.value_ptr.* }); try bindings.append(.{ .trigger = bind.key_ptr.*, .action = action });
} }
std.mem.sort(Binding, bindings.items, {}, Binding.lessThan); std.mem.sort(Binding, bindings.items, {}, Binding.lessThan);

View File

@ -3317,30 +3317,45 @@ pub const Keybinds = struct {
/// Deep copy of the struct. Required by Config. /// Deep copy of the struct. Required by Config.
pub fn clone(self: *const Keybinds, alloc: Allocator) !Keybinds { pub fn clone(self: *const Keybinds, alloc: Allocator) !Keybinds {
// TODO: leaders
return .{ return .{
.set = .{ .set = .{
.bindings = try self.set.bindings.clone(alloc), .bindings = try self.set.bindings.clone(alloc),
.reverse = try self.set.reverse.clone(alloc), .reverse = try self.set.reverse.clone(alloc),
.unconsumed = try self.set.unconsumed.clone(alloc),
}, },
}; };
} }
/// Compare if two of our value are requal. Required by Config. /// Compare if two of our value are requal. Required by Config.
pub fn equal(self: Keybinds, other: Keybinds) bool { pub fn equal(self: Keybinds, other: Keybinds) bool {
// Two keybinds are considered equal if their primary bindings
// are the same. We don't compare reverse mappings and such.
const self_map = self.set.bindings; const self_map = self.set.bindings;
const other_map = other.set.bindings; const other_map = other.set.bindings;
// If the count of mappings isn't identical they can't be equal
if (self_map.count() != other_map.count()) return false; if (self_map.count() != other_map.count()) return false;
var it = self_map.iterator(); var it = self_map.iterator();
while (it.next()) |self_entry| { while (it.next()) |self_entry| {
// If the trigger isn't in the other map, they can't be equal
const other_entry = other_map.getEntry(self_entry.key_ptr.*) orelse const other_entry = other_map.getEntry(self_entry.key_ptr.*) orelse
return false; return false;
if (!equalField(
// If the entry types are different, they can't be equal
if (std.meta.activeTag(self_entry.value_ptr.*) !=
std.meta.activeTag(other_entry.value_ptr.*)) return false;
switch (self_entry.value_ptr.*) {
.leader => @panic("TODO"),
// Actions are compared by field directly
inline .action, .action_unconsumed => |_, tag| if (!equalField(
inputpkg.Binding.Action, inputpkg.Binding.Action,
self_entry.value_ptr.*, @field(self_entry.value_ptr.*, @tagName(tag)),
other_entry.value_ptr.*, @field(other_entry.value_ptr.*, @tagName(tag)),
)) return false; )) return false,
}
} }
return true; return true;

View File

@ -772,7 +772,7 @@ pub const Trigger = struct {
pub const Set = struct { pub const Set = struct {
const HashMap = std.HashMapUnmanaged( const HashMap = std.HashMapUnmanaged(
Trigger, Trigger,
Action, Entry,
Context(Trigger), Context(Trigger),
std.hash_map.default_max_load_percentage, std.hash_map.default_max_load_percentage,
); );
@ -797,23 +797,15 @@ pub const Set = struct {
/// The reverse mapping of action to binding. Note that multiple /// The reverse mapping of action to binding. Note that multiple
/// bindings can map to the same action and this map will only have /// bindings can map to the same action and this map will only have
/// the most recently added binding for an action. /// the most recently added binding for an action.
///
/// Sequenced triggers are never present in the reverse map at this time.
/// This is a conscious decision since the primary use case of the reverse
/// map is to support GUI toolkit keyboard accelerators and no mainstream
/// GUI toolkit supports sequences.
reverse: ReverseMap = .{}, reverse: ReverseMap = .{},
/// The map of triggers that explicitly do not want to be consumed
/// when matched. A trigger is "consumed" when it is not further
/// processed and potentially sent to the terminal. An "unconsumed"
/// trigger will perform both its action and also continue normal
/// encoding processing (if any).
///
/// This is stored as a separate map since unconsumed triggers are
/// rare and we don't want to bloat our map with a byte per entry
/// (for boolean state) when most entries will be consumed.
///
/// Assert: trigger in this map is also in bindings.
unconsumed: UnconsumedMap = .{},
/// The entry type for the forward mapping of trigger to action. /// The entry type for the forward mapping of trigger to action.
const Entry = union(enum) { pub const Entry = union(enum) {
/// This key is a leader key in a sequence. You must follow the given /// This key is a leader key in a sequence. You must follow the given
/// set to find the next key in the sequence. /// set to find the next key in the sequence.
leader: *Set, leader: *Set,
@ -823,12 +815,32 @@ pub const Set = struct {
/// should not consume the input. /// should not consume the input.
action: Action, action: Action,
action_unconsumed: Action, action_unconsumed: Action,
/// Implements the formatter for the fmt package. This encodes the
/// action back into the format used by parse.
pub fn format(
self: Entry,
comptime layout: []const u8,
opts: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = layout;
_ = opts;
switch (self) {
.leader => @panic("TODO"),
.action, .action_unconsumed => |action| {
// action implements the format
try writer.print("{s}", .{action});
},
}
}
}; };
pub fn deinit(self: *Set, alloc: Allocator) void { pub fn deinit(self: *Set, alloc: Allocator) void {
self.bindings.deinit(alloc); self.bindings.deinit(alloc);
self.reverse.deinit(alloc); self.reverse.deinit(alloc);
self.unconsumed.deinit(alloc);
self.* = undefined; self.* = undefined;
} }
@ -906,7 +918,6 @@ pub const Set = struct {
assert(action != .unbind); assert(action != .unbind);
const gop = try self.bindings.getOrPut(alloc, t); const gop = try self.bindings.getOrPut(alloc, t);
if (!consumed) try self.unconsumed.put(alloc, t, {});
// If we have an existing binding for this trigger, we have to // If we have an existing binding for this trigger, we have to
// update the reverse mapping to remove the old action. // update the reverse mapping to remove the old action.
@ -919,19 +930,21 @@ pub const Set = struct {
break :it; break :it;
} }
} }
// We also have to remove the unconsumed state if it exists.
if (consumed) _ = self.unconsumed.remove(t);
} }
gop.value_ptr.* = action; // TODO: i hate this error handling
gop.value_ptr.* = if (consumed) .{
.action = action,
} else .{
.action_unconsumed = action,
};
errdefer _ = self.bindings.remove(t); errdefer _ = self.bindings.remove(t);
try self.reverse.put(alloc, action, t); try self.reverse.put(alloc, action, t);
errdefer _ = self.reverse.remove(action); errdefer _ = self.reverse.remove(action);
} }
/// Get a binding for a given trigger. /// Get a binding for a given trigger.
pub fn get(self: Set, t: Trigger) ?Action { pub fn get(self: Set, t: Trigger) ?Entry {
return self.bindings.get(t); return self.bindings.get(t);
} }
@ -941,35 +954,38 @@ pub const Set = struct {
return self.reverse.get(a); return self.reverse.get(a);
} }
/// Returns true if the given trigger should be consumed. Requires
/// that trigger is in the set to be valid so this should only follow
/// a non-null get.
pub fn getConsumed(self: Set, t: Trigger) bool {
return self.unconsumed.get(t) == null;
}
/// Remove a binding for a given trigger. /// Remove a binding for a given trigger.
pub fn remove(self: *Set, t: Trigger) void { pub fn remove(self: *Set, t: Trigger) void {
const action = self.bindings.get(t) orelse return; const entry = self.bindings.get(t) orelse return;
_ = self.bindings.remove(t); _ = self.bindings.remove(t);
_ = self.unconsumed.remove(t);
// Look for a matching action in bindings and use that. switch (entry) {
.leader => @panic("TODO"),
// For an action we need to fix up the reverse mapping.
// Note: we'd LIKE to replace this with the most recent binding but // Note: we'd LIKE to replace this with the most recent binding but
// our hash map obviously has no concept of ordering so we have to // our hash map obviously has no concept of ordering so we have to
// choose whatever. Maybe a switch to an array hash map here. // choose whatever. Maybe a switch to an array hash map here.
.action, .action_unconsumed => |action| {
const action_hash = action.hash(); const action_hash = action.hash();
var it = self.bindings.iterator(); var it = self.bindings.iterator();
while (it.next()) |entry| { while (it.next()) |it_entry| {
if (entry.value_ptr.hash() == action_hash) { switch (it_entry.value_ptr.*) {
self.reverse.putAssumeCapacity(action, entry.key_ptr.*); .leader => {},
.action, .action_unconsumed => |action_search| {
if (action_search.hash() == action_hash) {
self.reverse.putAssumeCapacity(action, it_entry.key_ptr.*);
break; break;
} }
},
}
} else { } else {
// No over trigger points to this action so we remove // No over trigger points to this action so we remove
// the reverse mapping completely. // the reverse mapping completely.
_ = self.reverse.remove(action); _ = self.reverse.remove(action);
} }
},
}
} }
/// The hash map context for the set. This defines how the hash map /// The hash map context for the set. This defines how the hash map
@ -1307,7 +1323,7 @@ test "set: parseAndPut typical binding" {
// Creates forward mapping // Creates forward mapping
{ {
const action = s.get(.{ .key = .{ .translated = .a } }).?; const action = s.get(.{ .key = .{ .translated = .a } }).?.action;
try testing.expect(action == .new_window); try testing.expect(action == .new_window);
} }
@ -1330,9 +1346,8 @@ test "set: parseAndPut unconsumed binding" {
// Creates forward mapping // Creates forward mapping
{ {
const trigger: Trigger = .{ .key = .{ .translated = .a } }; const trigger: Trigger = .{ .key = .{ .translated = .a } };
const action = s.get(trigger).?; const action = s.get(trigger).?.action_unconsumed;
try testing.expect(action == .new_window); try testing.expect(action == .new_window);
try testing.expect(!s.getConsumed(trigger));
} }
// Creates reverse mapping // Creates reverse mapping
@ -1417,11 +1432,11 @@ test "set: consumed state" {
defer s.deinit(alloc); defer s.deinit(alloc);
try s.put(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} }); try s.put(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} });
try testing.expect(s.getConsumed(.{ .key = .{ .translated = .a } })); try testing.expect(s.get(.{ .key = .{ .translated = .a } }).? == .action);
try s.putUnconsumed(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} }); try s.putUnconsumed(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} });
try testing.expect(!s.getConsumed(.{ .key = .{ .translated = .a } })); try testing.expect(s.get(.{ .key = .{ .translated = .a } }).? == .action_unconsumed);
try s.put(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} }); try s.put(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} });
try testing.expect(s.getConsumed(.{ .key = .{ .translated = .a } })); try testing.expect(s.get(.{ .key = .{ .translated = .a } }).? == .action);
} }