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
(test-my/y-or-n-p)

;; 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 – Iqbal Ansari Sep 8 '15 at 5:09
  • @IqbalAnsari but only if it's the last item on the hook. – PythonNut Sep 8 '15 at 13:46
up vote 9 down vote accepted

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 '15 at 13:31
  • Thanks, this works perfectly and is a lot less hacky than what I had in mind. – PythonNut Sep 8 '15 at 13:48
  • Found this a useful addition to the above: (define-key query-replace-map (kbd "RET") 'skip) – dardisco Jun 3 '16 at 6:17
  • This still blocks the command loop no? – clemera Dec 4 at 23:01

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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