diff --git a/src/Surface.zig b/src/Surface.zig index ce70d56ff..5d25a61e9 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1941,16 +1941,24 @@ fn maybeHandleBinding( return .closed; } - // If we have the performable flag and the - // action was not performed do nothing at all + // If we have the performable flag and the action was not performed, + // then we act as though a binding didn't exist. if (leaf.flags.performable and !performed) { + // If we're in a sequence, we treat this as if we pressed a key + // that doesn't exist in the sequence. Reset our sequence and flush + // any queued events. + if (self.keyboard.bindings != null) { + self.keyboard.bindings = null; + self.endKeySequence(.flush, .retain); + } + return null; } // If we consume this event, then we are done. If we don't consume // it, we processed the action but we still want to process our // encodings, too. - if (consumed) { + if (performed and consumed) { // If we had queued events, we deinit them since we consumed self.endKeySequence(.drop, .retain); diff --git a/src/config/Config.zig b/src/config/Config.zig index dca3bec0d..9692caae1 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -907,6 +907,15 @@ class: ?[:0]const u8 = null, /// Since they are not associated with a specific terminal surface, /// they're never encoded. /// +/// * `performable:` - Only consume the input if the action is able to be +/// performed. For example, the `copy_to_clipboard` action will only +/// consume the input if there is a selection to copy. If there is no +/// selection, Ghostty behaves as if the keybind was not set. This has +/// no effect with `global:` or `all:`-prefixed keybinds. For key +/// sequences, this will reset the sequence if the action is not +/// performable (acting identically to not having a keybind set at +/// all). +/// /// Keybind triggers are not unique per prefix combination. For example, /// `ctrl+a` and `global:ctrl+a` are not two separate keybinds. The keybind /// set later will overwrite the keybind set earlier. In this case, the diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 3380896b4..529ca1902 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -37,8 +37,9 @@ pub const Flags = packed struct { /// See the keybind config documentation for more information. global: bool = false, - /// True if this binding can be performed then the action is - /// triggered otherwise it acts as if it doesn't exist. + /// True if this binding should only be triggered if the action can be + /// performed. If the action can't be performed then the binding acts as + /// if it doesn't exist. performable: bool = false, }; @@ -1654,6 +1655,16 @@ test "parse: triggers" { .flags = .{ .consumed = false }, }, try parseSingle("unconsumed:physical:a+shift=ignore")); + // performable keys + try testing.expectEqual(Binding{ + .trigger = .{ + .mods = .{ .shift = true }, + .key = .{ .translated = .a }, + }, + .action = .{ .ignore = {} }, + .flags = .{ .performable = true }, + }, try parseSingle("performable:shift+a=ignore")); + // invalid key try testing.expectError(Error.InvalidFormat, parseSingle("foo=ignore"));