input: add Set.parseAndPut

This commit is contained in:
Mitchell Hashimoto
2024-08-15 15:41:07 -07:00
committed by Mitchell Hashimoto
parent a798a26063
commit bc4eab4af7
2 changed files with 108 additions and 9 deletions

View File

@ -3311,15 +3311,8 @@ pub const Keybinds = struct {
return;
}
const binding = try inputpkg.Binding.parse(value);
switch (binding.action) {
.unbind => self.set.remove(binding.trigger),
else => if (binding.consumed) {
try self.set.put(alloc, binding.trigger, binding.action);
} else {
try self.set.putUnconsumed(alloc, binding.trigger, binding.action);
},
}
// Let our much better tested binding package handle parsing and storage.
try self.set.parseAndPut(alloc, value);
}
/// Deep copy of the struct. Required by Config.

View File

@ -72,6 +72,10 @@ pub const Parser = struct {
.consumed = !self.unconsumed,
} };
}
pub fn reset(self: *Parser) void {
self.trigger_it.i = 0;
}
};
/// An iterator that yields each trigger in a sequence of triggers. For
@ -828,6 +832,44 @@ pub const Set = struct {
self.* = undefined;
}
/// Parse a user input binding and add it to the set. This will handle
/// the "unbind" case, ensure consumed/unconsumed fields are set correctly,
/// handle sequences, etc.
///
/// If an error is returned, the set is unmodified and safe to reuse.
pub fn parseAndPut(
self: *Set,
alloc: Allocator,
input: []const u8,
) (Allocator.Error || Error)!void {
// To make cleanup easier, we ensure that the full sequence is
// valid before making any set modifications. This is more expensive
// computationally but it makes cleanup way, way easier.
var it = try Parser.init(input);
while (try it.next()) |_| {}
it.reset();
// Now we know the input is valid, we can add it to the set.
var set: *Set = self;
while (it.next() catch unreachable) |elem| switch (elem) {
.leader => |t| {
_ = t;
@panic("TODO");
},
.binding => |b| switch (b.action) {
// TODO: unbinding sequences doesn't remove their leaders
.unbind => set.remove(b.trigger),
else => if (b.consumed) {
try set.put(alloc, b.trigger, b.action);
} else {
try set.putUnconsumed(alloc, b.trigger, b.action);
},
},
};
}
/// Add a binding to the set. If the binding already exists then
/// this will overwrite it.
pub fn put(
@ -1254,6 +1296,70 @@ test "parse: sequences" {
}
}
test "set: parseAndPut typical binding" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "a=new_window");
// Creates forward mapping
{
const action = s.get(.{ .key = .{ .translated = .a } }).?;
try testing.expect(action == .new_window);
}
// Creates reverse mapping
{
const trigger = s.getTrigger(.{ .new_window = {} }).?;
try testing.expect(trigger.key.translated == .a);
}
}
test "set: parseAndPut unconsumed binding" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "unconsumed:a=new_window");
// Creates forward mapping
{
const trigger: Trigger = .{ .key = .{ .translated = .a } };
const action = s.get(trigger).?;
try testing.expect(action == .new_window);
try testing.expect(!s.getConsumed(trigger));
}
// Creates reverse mapping
{
const trigger = s.getTrigger(.{ .new_window = {} }).?;
try testing.expect(trigger.key.translated == .a);
}
}
test "set: parseAndPut removed binding" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "a=new_window");
try s.parseAndPut(alloc, "a=unbind");
// Creates forward mapping
{
const trigger: Trigger = .{ .key = .{ .translated = .a } };
try testing.expect(s.get(trigger) == null);
}
try testing.expect(s.getTrigger(.{ .new_window = {} }) == null);
}
test "set: maintains reverse mapping" {
const testing = std.testing;
const alloc = testing.allocator;