0

I am trying to have a global override for a binding, where I am checking for some specific conditions to hold. If they don't, I would like that binding to do whatever it was going to without my intercept. I.e., something like the following:

(global-set-key <existing-binding> 'foo-wrapper)
(defun foo-wrapper ()
  (interactive)
  (if my-condition-holds #'my-foo <whatever existing-binding would have otherwise done here>)))) ;; 

How can I programmatically delegate to <whatever existing-binding would have otherwise done here>?

Please note: I am looking to not hard-code any commands into the wrapper that are not part of my custom logic. I'm looking at keybindings that are frequently overridden in various mode maps, and it seems impractical to have to manually figure out which command to dispatch.

Edited to add: concrete example is special handling of parentheses. In this case, <existing-binding> is simply (kbd "("), and my-condition could be something like (and current-prefix-arg (eq major-mode 'foo-mode)). Crucially, if my-condition does not hold, I need to delegate to the default behavior, which is highly context-specific: it may be handled by paredit, or smartparens, or evil, or be a self-insert-command.

1

I had this problem before and came up with this solution. Make a minor mode where you define your conditional commands:

(defvar conditional-overriding-mode-map (make-sparse-keymap))

(define-minor-mode conditional-overriding-mode
  "a minor mode for commands that only work if a certain condition holds."
  t nil conditional-overriding-mode-map)

The argument t means that the minor mode is on by default. Then bind keys in this minor mode as follows:

(define-key conditional-overriding-mode-map KEY
  (lambda ()
    (interactive)
    (if COND
        COMMAND
      (conditional-overriding-mode 'toggle)
      (execute-kbd-macro (this-command-keys))
      (conditional-overriding-mode 'toggle))))

What this does is what you want. You will have to adjust this if you want to pass arguments to your COMMAND or to whatever happens when COND fails.

For this to work all the time conditional-overriding-mode has to be at the front of minor-mode-map-alist. The answer in How to set a rule for the order of minor-mode-map-alist gives a way to do this.

| improve this answer | |
  • After trying a few methods suggested, I believe this is the most robust and straightforward approach. Note that the else branch can be made cleaner like this: (let ((conditional-overriding-mode nil)) (call-interactively (key-binding (this-command-keys)))) – Leo Alekseyev Jul 9 at 8:10
2

The easiest way is to just call whatever function would have been called when the original key binding was used. Suppose it was function foo:

(global-set-key <existing-binding> 'foo-wrapper)
(defun foo-wrapper (arg1 arg2)
  (interactive)
  (if my-condition-holds
      (my-foo arg1 arg2)
    (foo arg1 arg2)))

Note that you have to pass along any arguments that foo needs, and my-foo might need them as well.

There's another way to do this called advice. Advice is an additional function that is called before, after, around, or in some other combination with the original function. This happens automatically and transparently any time the original function is called, and when done correctly shows up in the documentation for the original function. It's documented in chapter 13.11 Advising Emacs Lisp Functions in the Elisp manual, which is also included with Emacs (use C-h i to open the info browser).

Again, assuming the original function is called foo, then you could write something like this:

(define-advice foo (:before-until (arg1 arg2) my-foo)
  "documentation for your advice here"
  (when my-condition-holds
    (progn
      (my-foo arg1 arg2)
      t)))

After this, whenever foo is called, your advice will be called first. Your advice should return t if it handled everything itself, or nil if the original function should be called. If my-foo returns anything non-nil, then you could simplify it to this:

(define-advice foo (:before-until (arg1 arg2) my-foo)
  "documentation for your advice here"
  (when my-condition-holds
    (my-foo arg1 arg2)))

Either way, the name my-foo and the docstring that you supply will show up as part of the documentation for the original function foo when you type C-h f foo.

Edit:

Since you've edited your question to include details about what the condition looks like, there is a better answer. When the condition involves checking the current major mode you should simply add a new key binding to that mode's keymap (usually of the form foo-mode-map) instead of wrapping anything. That way Emacs implicitly does this part of the check for you; that keymap won't be active in other modes so you won't have to check. On the other hand, if you still need to check for other conditions such as the prefix arg then you might need to wrap the function anyway.

Another Edit:

Oh, and I forgot to mention that if you're going to want this modification to be present in multiple modes, then instead of modifying multiple keymaps you might want to define a minor mode instead. You can add your new binding to the minor mode's keymap, which will be active any time the minor mode is active. See the define-minor-mode macro as well as chapter 23.3 Minor Modes of the Elisp manual as well as chapter 23.3 Choosing File Modes of the Emacs manual.

| improve this answer | |
  • (global-set-key [remap foo] 'foo-wrapper) or (substitute-key-definition 'foo 'foo-wrapper global-map) – Drew Jul 7 at 4:30
  • @Drew in most modes, the binding that I am trying to change resolves to self-insert-command, so it doesn't seem like remap is appropriate here. For the same reason, I was trying to avoid using advice -- it seems that advising self-insert-command is a bad idea but I may be wrong about that – Leo Alekseyev Jul 7 at 10:26
  • If testing your condition is quick, then advising self-insert-command might not make Emacs noticeably slower, but that is a risk. Perhaps if you asked a more specific question we could give a more specific answer. – db48x Jul 7 at 13:10
  • @db48x: What's the difference, in effect, between (global-set-key <existing-binding> 'foo-wrapper) and (global-set-key [remap foo] 'foo-wrapper)? If you're changing a global map binding then your "in most modes" comment doesn't have any sense, does it? You're not talking about changing a binding in any mode, it seems; you're talking about changing the global binding. – Drew Jul 7 at 15:09
  • @db48x edited to add specific details – Leo Alekseyev Jul 8 at 0:05
0

You can do this:

;; Dynamic bindings. A filter function returns the command to
;; call.  If the function returns nil, Emacs treats it as if
;; no binding exists in that keymap, and continues looking for
;; a binding elsewhere.
(define-key <map> <key>
  `(menu-item "" <my-cmd> :filter ,(lambda (cmd)
                                     (if <my-predicate> cmd))))

E.g.:

(define-key my-dynamic-override-map (kbd "(")
  `(menu-item "" my-foo :filter ,(lambda (cmd)
                                   (if (my-predicate) cmd))))

You (probably) won't want to use the global key map, because any other active keymap with a binding would take precedence; so you'd typically be using a minor mode key map, and taking steps to keep that at the front of the minor mode map priority list (which is what I do).

The other answers have already indicated how to do that with a minor mode, so for variety I'm using the even-higher-priority emulation-mode-map-alists here. I don't feel it's quite intended for this usage, but I suspect it'd be fine in practice.

(defun my-foo ()
  (interactive)
  (insert "foo"))

(defun my-predicate ()
  (eql 0 (random 5)))

(defun my-filter (cmd)
  (if (my-predicate) cmd))

(defvar my-dynamic-override-map (make-sparse-keymap))

(define-key my-dynamic-override-map (kbd "(")
  `(menu-item "" my-foo :filter ,#'my-filter))

(defvar my-emulation-mode-map-alist
  `((my-dynamic-override-map . ,my-dynamic-override-map)))

(add-to-list 'emulation-mode-map-alists 'my-emulation-mode-map-alist)

(If you use a minor mode instead, simply define the key in the minor mode's keymap instead of the separate keymap I've made here, and ignore the 'emulation-mode-map' parts.)

See also: How can I 'layer' a keybinding?

| improve this answer | |
  • Unfortunately this answer seems to have a flaw: current-prefix-arg is always nil within my-filter even if I am typing C-u (. I specifically am trying to detect prefix arg in my predicate. – Leo Alekseyev Jul 9 at 5:32
  • I presume that current-prefix-arg is not set until the command to call has been established. Cursory testing suggests that in this instance you can use prefix-arg. – phils Jul 9 at 5:55
  • prefix-arg is indeed set but for some reason I cannot get the predicate to work with it. As in, when I use it in the predicate, the predicate always returns nil. Extremely strange. I am going with the non-exotic and seemingly robust (call-interactively (key-binding (this-command-keys))) in a minor mode. – Leo Alekseyev Jul 9 at 8:07
  • That sounds like a bug in the predicate, then? Feel free to show the code. – phils Jul 9 at 8:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.