I have a function that offers to do handy things. Right now, it's using y-or-n-p to ask if it should take action. The problem is that y-or-n-p blocks the command loop, which is inconvenient if you want to pass on the action. Instead, I'd like y to execute the action, but let everything else through.

(defun my/y-or-n-p (prompt callback)
  (message prompt)
  (set-transient-map `(keymap (121 . ,callback))))

(defun test-my/y-or-n-p ()
  (my/y-or-n-p "message?" (lambda () (interactive) (message "message hi!"))))

;; this works

;; this does not
(add-hook 'emacs-startup-hook #'test-my/y-or-n-p)

There are two problems with my code:

  • The prompt in the echo area can be overwritten by other messages
  • Doesn't seem to work in emacs-startup-hook (and maybe other hooks)

Does anyone know what's wrong with it?

  • I just tried your example and it worked for me in the emacs-startup-hook Sep 8, 2015 at 5:09
  • @IqbalAnsari but only if it's the last item on the hook.
    – PythonNut
    Sep 8, 2015 at 13:46

1 Answer 1


Something like the following should work

(defun my-lenient-y-or-n-p (prompt)
  (let ((query-replace-map (copy-keymap query-replace-map)))
    (define-key query-replace-map [t] 'skip)
    (y-or-n-p prompt)))

If you look at the documentation of y-or-n-p, it mentions that it looks up the query-replace-map to decide which action to take for a given key. I am quoting it here for completeness (emphasis mine)


To be precise, this function translates user input into responses by consulting the bindings in query-replace-map; see the documentation of that variable for more information. In this case, the useful bindings are act, skip, recenter, scroll-up, scroll-down, and quit. An act response means yes, and a skip response means no. A quit response means to invoke keyboard-quit. If the user enters recenter, scroll-up, or scroll-down responses, perform the requested window recentering or scrolling and ask again.


So basically if we can invoke y-or-n-p with modified query-replace-map which defines the binding skip as the default we should get what you want.

We can achieve this by copying the query-replace-map (so that global value is unchanged) and defining the binding skip for all undefined keys. So how do we define a default binding in a keymap, for this see the documentation of define-keymap (emphasis mine)

(...) KEY is a string or a vector of symbols and characters, representing a sequence of keystrokes and events. Non-ASCII characters with codes above 127 (such as ISO Latin-1) can be represented by vectors. Two types of vector have special meanings: [remap COMMAND] remaps any key binding for COMMAND. [t] creates a default definition, which applies to any event with no other definition in KEYMAP.

So binding [t] to skip in the copy of query-replace-map should do the trick for us. Of course we let bind the new keymap around the call to y-or-no-p so that the modified keymap is used instead of the global definition

  • 1
    Good explanation, including mentioning [t].
    – Drew
    Sep 8, 2015 at 13:31
  • Thanks, this works perfectly and is a lot less hacky than what I had in mind.
    – PythonNut
    Sep 8, 2015 at 13:48
  • Found this a useful addition to the above: (define-key query-replace-map (kbd "RET") 'skip)
    – dardisco
    Jun 3, 2016 at 6:17
  • This still blocks the command loop no?
    – clemera
    Dec 4, 2018 at 23:01

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.