mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 21:40:29 +00:00
input: leaf_chained tagged union value
This commit is contained in:
53
src/App.zig
53
src/App.zig
@@ -357,15 +357,17 @@ pub fn keyEvent(
|
||||
// Get the keybind entry for this event. We don't support key sequences
|
||||
// so we can look directly in the top-level set.
|
||||
const entry = rt_app.config.keybind.set.getEvent(event) orelse return false;
|
||||
const leaf: input.Binding.Set.Leaf = switch (entry.value_ptr.*) {
|
||||
const leaf: input.Binding.Set.GenericLeaf = switch (entry.value_ptr.*) {
|
||||
// Sequences aren't supported. Our configuration parser verifies
|
||||
// this for global keybinds but we may still get an entry for
|
||||
// a non-global keybind.
|
||||
.leader => return false,
|
||||
|
||||
// Leaf entries are good
|
||||
.leaf => |leaf| leaf,
|
||||
inline .leaf, .leaf_chained => |leaf| leaf.generic(),
|
||||
};
|
||||
const actions: []const input.Binding.Action = leaf.actionsSlice();
|
||||
assert(actions.len > 0);
|
||||
|
||||
// If we aren't focused, then we only process global keybinds.
|
||||
if (!self.focused and !leaf.flags.global) return false;
|
||||
@@ -373,13 +375,7 @@ pub fn keyEvent(
|
||||
// Global keybinds are done using performAll so that they
|
||||
// can target all surfaces too.
|
||||
if (leaf.flags.global) {
|
||||
self.performAllAction(rt_app, leaf.action) catch |err| {
|
||||
log.warn("error performing global keybind action action={s} err={}", .{
|
||||
@tagName(leaf.action),
|
||||
err,
|
||||
});
|
||||
};
|
||||
|
||||
self.performAllChainedAction(rt_app, actions);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -389,14 +385,20 @@ pub fn keyEvent(
|
||||
|
||||
// If we are focused, then we process keybinds only if they are
|
||||
// app-scoped. Otherwise, we do nothing. Surface-scoped should
|
||||
// be processed by Surface.keyEvent.
|
||||
const app_action = leaf.action.scoped(.app) orelse return false;
|
||||
self.performAction(rt_app, app_action) catch |err| {
|
||||
log.warn("error performing app keybind action action={s} err={}", .{
|
||||
@tagName(app_action),
|
||||
err,
|
||||
});
|
||||
};
|
||||
// be processed by Surface.keyEvent. For chained actions, all
|
||||
// actions must be app-scoped.
|
||||
for (actions) |action| if (action.scoped(.app) == null) return false;
|
||||
for (actions) |action| {
|
||||
self.performAction(
|
||||
rt_app,
|
||||
action.scoped(.app).?,
|
||||
) catch |err| {
|
||||
log.warn("error performing app keybind action action={s} err={}", .{
|
||||
@tagName(action),
|
||||
err,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -454,6 +456,23 @@ pub fn performAction(
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a chained action. We will continue executing each action
|
||||
/// even if there is a failure in a prior action.
|
||||
pub fn performAllChainedAction(
|
||||
self: *App,
|
||||
rt_app: *apprt.App,
|
||||
actions: []const input.Binding.Action,
|
||||
) void {
|
||||
for (actions) |action| {
|
||||
self.performAllAction(rt_app, action) catch |err| {
|
||||
log.warn("error performing chained action action={s} err={}", .{
|
||||
@tagName(action),
|
||||
err,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform an app-wide binding action. If the action is surface-specific
|
||||
/// then it will be performed on all surfaces. To perform only app-scoped
|
||||
/// actions, use performAction.
|
||||
|
||||
@@ -2866,7 +2866,7 @@ fn maybeHandleBinding(
|
||||
};
|
||||
|
||||
// Determine if this entry has an action or if its a leader key.
|
||||
const leaf: input.Binding.Set.Leaf = switch (entry.value_ptr.*) {
|
||||
const leaf: input.Binding.Set.GenericLeaf = switch (entry.value_ptr.*) {
|
||||
.leader => |set| {
|
||||
// Setup the next set we'll look at.
|
||||
self.keyboard.sequence_set = set;
|
||||
@@ -2893,9 +2893,8 @@ fn maybeHandleBinding(
|
||||
return .consumed;
|
||||
},
|
||||
|
||||
.leaf => |leaf| leaf,
|
||||
inline .leaf, .leaf_chained => |leaf| leaf.generic(),
|
||||
};
|
||||
const action = leaf.action;
|
||||
|
||||
// consumed determines if the input is consumed or if we continue
|
||||
// encoding the key (if we have a key to encode).
|
||||
@@ -2917,36 +2916,58 @@ fn maybeHandleBinding(
|
||||
// An action also always resets the sequence set.
|
||||
self.keyboard.sequence_set = null;
|
||||
|
||||
// Setup our actions
|
||||
const actions = leaf.actionsSlice();
|
||||
|
||||
// Attempt to perform the action
|
||||
log.debug("key event binding flags={} action={f}", .{
|
||||
log.debug("key event binding flags={} action={any}", .{
|
||||
leaf.flags,
|
||||
action,
|
||||
actions,
|
||||
});
|
||||
const performed = performed: {
|
||||
// If this is a global or all action, then we perform it on
|
||||
// the app and it applies to every surface.
|
||||
if (leaf.flags.global or leaf.flags.all) {
|
||||
try self.app.performAllAction(self.rt_app, action);
|
||||
self.app.performAllChainedAction(
|
||||
self.rt_app,
|
||||
actions,
|
||||
);
|
||||
|
||||
// "All" actions are always performed since they are global.
|
||||
break :performed true;
|
||||
}
|
||||
|
||||
break :performed try self.performBindingAction(action);
|
||||
// Perform each action. We are performed if ANY of the chained
|
||||
// actions perform.
|
||||
var performed: bool = false;
|
||||
for (actions) |action| {
|
||||
if (self.performBindingAction(action)) |_| {
|
||||
performed = true;
|
||||
} else |err| {
|
||||
log.info(
|
||||
"key binding action failed action={t} err={}",
|
||||
.{ action, err },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
break :performed performed;
|
||||
};
|
||||
|
||||
if (performed) {
|
||||
// If we performed an action and it was a closing action,
|
||||
// our "self" pointer is not safe to use anymore so we need to
|
||||
// just exit immediately.
|
||||
if (closingAction(action)) {
|
||||
for (actions) |action| if (closingAction(action)) {
|
||||
log.debug("key binding is a closing binding, halting key event processing", .{});
|
||||
return .closed;
|
||||
}
|
||||
};
|
||||
|
||||
// If our action was "ignore" then we return the special input
|
||||
// effect of "ignored".
|
||||
if (action == .ignore) return .ignored;
|
||||
for (actions) |action| if (action == .ignore) {
|
||||
return .ignored;
|
||||
};
|
||||
}
|
||||
|
||||
// If we have the performable flag and the action was not performed,
|
||||
@@ -2970,7 +2991,8 @@ fn maybeHandleBinding(
|
||||
// Store our last trigger so we don't encode the release event
|
||||
self.keyboard.last_trigger = event.bindingHash();
|
||||
|
||||
if (insp_ev) |ev| ev.binding = action;
|
||||
// TODO: Inspector must support chained events
|
||||
if (insp_ev) |ev| ev.binding = actions[0];
|
||||
return .consumed;
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ pub const App = struct {
|
||||
while (it.next()) |entry| {
|
||||
switch (entry.value_ptr.*) {
|
||||
.leader => {},
|
||||
.leaf => |leaf| if (leaf.flags.global) return true,
|
||||
inline .leaf, .leaf_chained => |leaf| if (leaf.flags.global) return true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -326,7 +326,6 @@ fn iterateBindings(
|
||||
|
||||
switch (bind.value_ptr.*) {
|
||||
.leader => |leader| {
|
||||
|
||||
// Recursively iterate on the set of bindings for this leader key
|
||||
var n_iter = leader.bindings.iterator();
|
||||
const sub_bindings, const max_width = try iterateBindings(alloc, &n_iter, win);
|
||||
@@ -353,6 +352,9 @@ fn iterateBindings(
|
||||
.action = leaf.action,
|
||||
});
|
||||
},
|
||||
.leaf_chained => {
|
||||
// TODO: Show these.
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6749,6 +6749,21 @@ pub const Keybinds = struct {
|
||||
other_leaf,
|
||||
)) return false;
|
||||
},
|
||||
|
||||
.leaf_chained => {
|
||||
const self_chain = self_entry.value_ptr.*.leaf_chained;
|
||||
const other_chain = other_entry.value_ptr.*.leaf_chained;
|
||||
|
||||
if (self_chain.flags != other_chain.flags) return false;
|
||||
if (self_chain.actions.items.len != other_chain.actions.items.len) return false;
|
||||
for (self_chain.actions.items, other_chain.actions.items) |a1, a2| {
|
||||
if (!equalField(
|
||||
inputpkg.Binding.Action,
|
||||
a1,
|
||||
a2,
|
||||
)) return false;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ const SequenceIterator = struct {
|
||||
|
||||
/// Returns true if there are no more triggers to parse.
|
||||
pub fn done(self: *const SequenceIterator) bool {
|
||||
return self.i >= self.input.len;
|
||||
return self.i > self.input.len;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1953,6 +1953,9 @@ pub const Set = struct {
|
||||
/// to take along with the flags that may define binding behavior.
|
||||
leaf: Leaf,
|
||||
|
||||
/// A set of actions to take in response to a trigger.
|
||||
leaf_chained: LeafChained,
|
||||
|
||||
/// Implements the formatter for the fmt package. This encodes the
|
||||
/// action back into the format used by parse.
|
||||
pub fn format(
|
||||
@@ -2018,6 +2021,8 @@ pub const Set = struct {
|
||||
buffer.print("={f}", .{leaf.action}) catch return error.OutOfMemory;
|
||||
try formatter.formatEntry([]const u8, buffer.buffer[0..buffer.end]);
|
||||
},
|
||||
|
||||
.leaf_chained => @panic("TODO"),
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2044,6 +2049,47 @@ pub const Set = struct {
|
||||
std.hash.autoHash(&hasher, self.flags);
|
||||
return hasher.final();
|
||||
}
|
||||
|
||||
pub fn generic(self: *const Leaf) GenericLeaf {
|
||||
return .{
|
||||
.flags = self.flags,
|
||||
.actions = .{ .single = .{self.action} },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Leaf node of a set that triggers multiple actions in sequence.
|
||||
pub const LeafChained = struct {
|
||||
actions: std.ArrayList(Action),
|
||||
flags: Flags,
|
||||
|
||||
pub fn deinit(self: *LeafChained, alloc: Allocator) void {
|
||||
self.actions.deinit(alloc);
|
||||
}
|
||||
|
||||
pub fn generic(self: *const LeafChained) GenericLeaf {
|
||||
return .{
|
||||
.flags = self.flags,
|
||||
.actions = .{ .many = self.actions.items },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// A generic leaf node that can be used to unify the handling of
|
||||
/// leaf and leaf_chained.
|
||||
pub const GenericLeaf = struct {
|
||||
flags: Flags,
|
||||
actions: union(enum) {
|
||||
single: [1]Action,
|
||||
many: []const Action,
|
||||
},
|
||||
|
||||
pub fn actionsSlice(self: *const GenericLeaf) []const Action {
|
||||
return switch (self.actions) {
|
||||
.single => |*arr| arr,
|
||||
.many => |slice| slice,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// A full key-value entry for the set.
|
||||
@@ -2057,6 +2103,9 @@ pub const Set = struct {
|
||||
s.deinit(alloc);
|
||||
alloc.destroy(s);
|
||||
},
|
||||
|
||||
.leaf_chained => |*l| l.deinit(alloc),
|
||||
|
||||
.leaf => {},
|
||||
};
|
||||
|
||||
@@ -2133,7 +2182,7 @@ pub const Set = struct {
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
},
|
||||
|
||||
.leaf => {
|
||||
.leaf, .leaf_chained => {
|
||||
// Remove the existing action. Fallthrough as if
|
||||
// we don't have a leader.
|
||||
set.remove(alloc, t);
|
||||
@@ -2163,6 +2212,7 @@ pub const Set = struct {
|
||||
leaf.action,
|
||||
leaf.flags,
|
||||
) catch {},
|
||||
.leaf_chained => @panic("TODO"),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -2184,7 +2234,9 @@ pub const Set = struct {
|
||||
),
|
||||
},
|
||||
|
||||
.chain => @panic("TODO"),
|
||||
.chain => {
|
||||
// TODO: Do this, ignore for now.
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2236,6 +2288,12 @@ pub const Set = struct {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Chained leaves aren't in the reverse mapping so we just
|
||||
// clear it out.
|
||||
.leaf_chained => |*l| {
|
||||
l.deinit(alloc);
|
||||
},
|
||||
};
|
||||
|
||||
gop.value_ptr.* = .{ .leaf = .{
|
||||
@@ -2312,7 +2370,7 @@ pub const Set = struct {
|
||||
}
|
||||
|
||||
fn removeExact(self: *Set, alloc: Allocator, t: Trigger) void {
|
||||
const entry = self.bindings.get(t) orelse return;
|
||||
var entry = self.bindings.get(t) orelse return;
|
||||
_ = self.bindings.remove(t);
|
||||
|
||||
switch (entry) {
|
||||
@@ -2334,7 +2392,7 @@ pub const Set = struct {
|
||||
var it = self.bindings.iterator();
|
||||
while (it.next()) |it_entry| {
|
||||
switch (it_entry.value_ptr.*) {
|
||||
.leader => {},
|
||||
.leader, .leaf_chained => {},
|
||||
.leaf => |leaf_search| {
|
||||
if (leaf_search.action.hash() == action_hash) {
|
||||
self.reverse.putAssumeCapacity(leaf.action, it_entry.key_ptr.*);
|
||||
@@ -2348,6 +2406,12 @@ pub const Set = struct {
|
||||
_ = self.reverse.remove(leaf.action);
|
||||
}
|
||||
},
|
||||
|
||||
// Chained leaves are never in our reverse mapping so no
|
||||
// cleanup is required.
|
||||
.leaf_chained => |*l| {
|
||||
l.deinit(alloc);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2366,6 +2430,8 @@ pub const Set = struct {
|
||||
// contain allocated strings).
|
||||
.leaf => |*s| s.* = try s.clone(alloc),
|
||||
|
||||
.leaf_chained => @panic("TODO"),
|
||||
|
||||
// Must be deep cloned.
|
||||
.leader => |*s| {
|
||||
const ptr = try alloc.create(Set);
|
||||
@@ -3356,6 +3422,31 @@ test "set: consumed state" {
|
||||
try testing.expect(s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*.leaf.flags.consumed);
|
||||
}
|
||||
|
||||
// test "set: parseAndPut chain" {
|
||||
// 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, "chain=new_tab");
|
||||
//
|
||||
// // Creates forward mapping
|
||||
// {
|
||||
// const action = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*.leaf;
|
||||
// try testing.expect(action.action == .new_window);
|
||||
// try testing.expectEqual(Flags{}, action.flags);
|
||||
// }
|
||||
//
|
||||
// // Does not create reverse mapping, because reverse mappings are only for
|
||||
// // non-chain actions.
|
||||
// {
|
||||
// const trigger = s.getTrigger(.new_window);
|
||||
// try testing.expect(trigger == null);
|
||||
// }
|
||||
// }
|
||||
|
||||
test "set: getEvent physical" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
Reference in New Issue
Block a user