13

When part of a defun,

(interactive "c(C)hoose (A)n (O)ption")

will prompt the user for a single character; RET is not required. How can I replicate this reading behavior without the need for interactive?

0

4 Answers 4

9

Rather than read-char I recommend read-key. The difference is that read-key obeys all the usual remappings such as input-decode-map and function-key-map, so it will work properly in a tty.

1
  • 1
    Paired with the information in another answer, this seems to be the most precise answer to the question asked :) glucas's comment provides a good function though :) read-char-choice Dec 7, 2014 at 18:24
5

In addition to built-in ways to read single events such as read-char and read-char-exclusive, here's an option to read a single character, but also specify which characters are acceptable input:

(defun read-char-picky (prompt chars &optional inherit-input-method seconds)
  "Read characters like in `read-char-exclusive', but if input is
not one of CHARS, return nil.  CHARS may be a list of characters,
single-character strings, or a string of characters."
  (let ((chars (mapcar (lambda (x)
                         (if (characterp x) x (string-to-char x)))
                       (append chars nil)))
        (char  (read-char-exclusive prompt inherit-input-method seconds)))
    (when (memq char chars)
      char)))

So all of the following will accept either "C", "A", or "O":

(read-char-picky "(C)hoose (A)n (O)ption: " "CAO")
(read-char-picky "(C)hoose (A)n (O)ption: " '("C" "A" "O"))
(read-char-picky "(C)hoose (A)n (O)ption: " '(?C ?A ?O))

And here's an example way to loop for the correct input into a response variable:

(let (response)
  (while (null (setq response
                     (read-char-picky "(C)hoose (A)n (O)ption: " "CAO")))
    (message "Please pick one of \"C\", \"A\", or \"O\"!")
    (sit-for .5))
  response)
2
  • 2
    There is also read-char-choice which reads one of a given set of characters.
    – glucas
    Dec 6, 2014 at 21:05
  • @glucas: ah, nuts, you're right. Looks like I reinvented the wheel.
    – Dan
    Dec 6, 2014 at 21:08
5

The question was answered long ago, but this additional answer may provide some assistance to other searchers.

read-char-choice allows you to specify a list of choices. The fn will not return until the user selects one of those valid options.

(read-char-choice "prompt here (A, B, or C)? " '(?A ?B ?C))

In the degenerate case in which the options are simply Y or N (case insenstive), there is y-or-n-p.

Both read-char-choice and y-or-n-p are rigid, and insist on a valid answer. In the former case, it must one of the options you specify (like A, B or C in my example), and in latter case, it must be Y or N . If the user presses enter or any other key, the y-or-n-p will ask again. The read-char-choice will just sit there, silent. Neither provides a way to just return a default. To get that behavior, I think you've got to build your own interaction with read-char or read-key.

In my experience, the problem with read-char and read-key alone, is that while they display the prompt in the minibuffer, the cursor remains in the main editing buffer. This is disorienting to the user, and is also unlike the behavior of read-string .

To avoid THAT, you can let the variable cursor-in-echo-area prior to calling read-key to display the cursor in the minibuffer.

(defun my-y-or-n-with-default (raw-prompt &optional default-yes)
  "displays PROMPT in the minibuffer, prompts for a y or n,
returns t or nil accordingly. If neither Y or N is entered, then
if DEFAULT-YES, returns t, else nil."
  (let* ((options-string (if default-yes "Y or n" "y or N"))
         (prompt (concat raw-prompt "(" options-string ")? "))
         (cursor-in-echo-area t)
         (key (read-key (propertize prompt 'face 'minibuffer-prompt)))
         (isyes (or (eq key ?y) (eq key ?Y)))
         (isno (or (eq key ?n) (eq key ?N))))
    (if (not (or isyes isno))
        default-yes
      isyes)))
4

call-interactively is what interprets the (interactive "cPROMPT") specification, the c option is dispatched to read-char. Therefore, the following should work in a non-interactive context:

(read-char "(C)hoose (A)n (O)ption")

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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