aboutsummaryrefslogtreecommitdiffstats
path: root/lisp
diff options
context:
space:
mode:
authorJuri Linkov2018-01-21 23:45:43 +0200
committerNoam Postavsky2018-08-04 11:37:39 -0400
commitcc233365a925dcf9fa7270630819f2e6e75280da (patch)
tree7d3ecdf237cfe9a014d35034bc32cd1ec55ebfff /lisp
parentf0b8e64fb7720a9376bde80cc59fe37b0df83b9d (diff)
downloademacs-cc233365a925dcf9fa7270630819f2e6e75280da.tar.gz
emacs-cc233365a925dcf9fa7270630819f2e6e75280da.zip
New function read-answer (Bug#31782)
* lisp/emacs-lisp/map-ynp.el (read-answer-short): New defcustom. (read-answer): New function. * lisp/subr.el (assoc-delete-all): New function. * etc/NEWS: Announce them. * lisp/dired.el (dired-delete-file): Use read-answer. (dired--yes-no-all-quit-help): Remove function. (dired-delete-help): Remove defconst. (backported from master, "New function read-answer (bug#30073)" and "Respect non-saved value of `read-short-answer' (Bug#31782)")
Diffstat (limited to 'lisp')
-rw-r--r--lisp/dired.el41
-rw-r--r--lisp/emacs-lisp/map-ynp.el128
-rw-r--r--lisp/subr.el15
3 files changed, 151 insertions, 33 deletions
diff --git a/lisp/dired.el b/lisp/dired.el
index c421e51ffd1..2520ed2a109 100644
--- a/lisp/dired.el
+++ b/lisp/dired.el
@@ -2995,37 +2995,6 @@ Any other value means to ask for each directory."
2995;; Match anything but `.' and `..'. 2995;; Match anything but `.' and `..'.
2996(defvar dired-re-no-dot "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*") 2996(defvar dired-re-no-dot "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*")
2997 2997
2998(defconst dired-delete-help
2999 "Type:
3000`yes' to delete recursively the current directory,
3001`no' to skip to next,
3002`all' to delete all remaining directories with no more questions,
3003`quit' to exit,
3004`help' to show this help message.")
3005
3006(defun dired--yes-no-all-quit-help (prompt &optional help-msg)
3007 "Ask a question with valid answers: yes, no, all, quit, help.
3008PROMPT must end with '? ', for instance, 'Delete it? '.
3009If optional arg HELP-MSG is non-nil, then is a message to show when
3010the user answers 'help'. Otherwise, default to `dired-delete-help'."
3011 (let ((valid-answers (list "yes" "no" "all" "quit"))
3012 (answer "")
3013 (input-fn (lambda ()
3014 (read-string
3015 (format "%s [yes, no, all, quit, help] " prompt)))))
3016 (setq answer (funcall input-fn))
3017 (when (string= answer "help")
3018 (with-help-window "*Help*"
3019 (with-current-buffer "*Help*"
3020 (insert (or help-msg dired-delete-help)))))
3021 (while (not (member answer valid-answers))
3022 (unless (string= answer "help")
3023 (beep)
3024 (message "Please answer `yes' or `no' or `all' or `quit'")
3025 (sleep-for 2))
3026 (setq answer (funcall input-fn)))
3027 answer))
3028
3029;; Delete file, possibly delete a directory and all its files. 2998;; Delete file, possibly delete a directory and all its files.
3030;; This function is useful outside of dired. One could change its name 2999;; This function is useful outside of dired. One could change its name
3031;; to e.g. recursive-delete-file and put it somewhere else. 3000;; to e.g. recursive-delete-file and put it somewhere else.
@@ -3055,11 +3024,17 @@ TRASH non-nil means to trash the file instead of deleting, provided
3055 "trash" 3024 "trash"
3056 "delete") 3025 "delete")
3057 (dired-make-relative file)))) 3026 (dired-make-relative file))))
3058 (pcase (dired--yes-no-all-quit-help prompt) ; Prompt user. 3027 (pcase (read-answer
3028 prompt
3029 '(("yes" ?y "delete recursively the current directory")
3030 ("no" ?n "skip to next")
3031 ("all" ?! "delete all remaining directories with no more questions")
3032 ("quit" ?q "exit")))
3059 ('"all" (setq recursive 'always dired-recursive-deletes recursive)) 3033 ('"all" (setq recursive 'always dired-recursive-deletes recursive))
3060 ('"yes" (if (eq recursive 'top) (setq recursive 'always))) 3034 ('"yes" (if (eq recursive 'top) (setq recursive 'always)))
3061 ('"no" (setq recursive nil)) 3035 ('"no" (setq recursive nil))
3062 ('"quit" (keyboard-quit))))) 3036 ('"quit" (keyboard-quit))
3037 (_ (keyboard-quit))))) ; catch all unknown answers
3063 (setq recursive nil)) ; Empty dir or recursive is nil. 3038 (setq recursive nil)) ; Empty dir or recursive is nil.
3064 (delete-directory file recursive trash)))) 3039 (delete-directory file recursive trash))))
3065 3040
diff --git a/lisp/emacs-lisp/map-ynp.el b/lisp/emacs-lisp/map-ynp.el
index 2a7eddedad7..8260af57278 100644
--- a/lisp/emacs-lisp/map-ynp.el
+++ b/lisp/emacs-lisp/map-ynp.el
@@ -256,4 +256,132 @@ the current %s and exit."
256 ;; Return the number of actions that were taken. 256 ;; Return the number of actions that were taken.
257 actions)) 257 actions))
258 258
259
260;; read-answer is a general-purpose question-asker that supports
261;; either long or short answers.
262
263;; For backward compatibility check if short y/n answers are preferred.
264(defcustom read-answer-short 'auto
265 "If non-nil, `read-answer' accepts single-character answers.
266If t, accept short (single key-press) answers to the question.
267If nil, require long answers. If `auto', accept short answers if
268the function cell of `yes-or-no-p' is set to `y-or-on-p'."
269 :type '(choice (const :tag "Accept short answers" t)
270 (const :tag "Require long answer" nil)
271 (const :tag "Guess preference" auto))
272 :version "26.2"
273 :group 'minibuffer)
274
275(defconst read-answer-map--memoize (make-hash-table :weakness 'key :test 'equal))
276
277(defun read-answer (question answers)
278 "Read an answer either as a complete word or its character abbreviation.
279Ask user a question and accept an answer from the list of possible answers.
280
281QUESTION should end in a space; this function adds a list of answers to it.
282
283ANSWERS is an alist with elements in the following format:
284 (LONG-ANSWER SHORT-ANSWER HELP-MESSAGE)
285where
286 LONG-ANSWER is a complete answer,
287 SHORT-ANSWER is an abbreviated one-character answer,
288 HELP-MESSAGE is a string describing the meaning of the answer.
289
290Example:
291 \\='((\"yes\" ?y \"perform the action\")
292 (\"no\" ?n \"skip to the next\")
293 (\"all\" ?! \"accept all remaining without more questions\")
294 (\"help\" ?h \"show help\")
295 (\"quit\" ?q \"exit\"))
296
297When `read-answer-short' is non-nil, accept short answers.
298
299Return a long answer even in case of accepting short ones.
300
301When `use-dialog-box' is t, pop up a dialog window to get user input."
302 (let* ((short (if (eq read-answer-short 'auto)
303 (eq (symbol-function 'yes-or-no-p) 'y-or-n-p)
304 read-answer-short))
305 (answers-with-help
306 (if (assoc "help" answers)
307 answers
308 (append answers '(("help" ?? "show this help message")))))
309 (answers-without-help
310 (assoc-delete-all "help" (copy-alist answers-with-help)))
311 (prompt
312 (format "%s(%s) " question
313 (mapconcat (lambda (a)
314 (if short
315 (format "%c" (nth 1 a))
316 (nth 0 a)))
317 answers-with-help ", ")))
318 (message
319 (format "Please answer %s."
320 (mapconcat (lambda (a)
321 (format "`%s'" (if short
322 (string (nth 1 a))
323 (nth 0 a))))
324 answers-with-help " or ")))
325 (short-answer-map
326 (when short
327 (or (gethash answers read-answer-map--memoize)
328 (puthash answers
329 (let ((map (make-sparse-keymap)))
330 (set-keymap-parent map minibuffer-local-map)
331 (dolist (a answers-with-help)
332 (define-key map (vector (nth 1 a))
333 (lambda ()
334 (interactive)
335 (delete-minibuffer-contents)
336 (insert (nth 0 a))
337 (exit-minibuffer))))
338 (define-key map [remap self-insert-command]
339 (lambda ()
340 (interactive)
341 (delete-minibuffer-contents)
342 (beep)
343 (message message)
344 (sleep-for 2)))
345 map)
346 read-answer-map--memoize))))
347 answer)
348 (while (not (assoc (setq answer (downcase
349 (cond
350 ((and (display-popup-menus-p)
351 last-input-event ; not during startup
352 (listp last-nonmenu-event)
353 use-dialog-box)
354 (x-popup-dialog
355 t
356 (cons question
357 (mapcar (lambda (a)
358 (cons (capitalize (nth 0 a))
359 (nth 0 a)))
360 answers-with-help))))
361 (short
362 (read-from-minibuffer
363 prompt nil short-answer-map nil
364 'yes-or-no-p-history))
365 (t
366 (read-from-minibuffer
367 prompt nil nil nil
368 'yes-or-no-p-history)))))
369 answers-without-help))
370 (if (string= answer "help")
371 (with-help-window "*Help*"
372 (with-current-buffer "*Help*"
373 (insert "Type:\n"
374 (mapconcat
375 (lambda (a)
376 (format "`%s'%s to %s"
377 (if short (string (nth 1 a)) (nth 0 a))
378 (if short (format " (%s)" (nth 0 a)) "")
379 (nth 2 a)))
380 answers-with-help ",\n")
381 ".\n")))
382 (beep)
383 (message message)
384 (sleep-for 2)))
385 answer))
386
259;;; map-ynp.el ends here 387;;; map-ynp.el ends here
diff --git a/lisp/subr.el b/lisp/subr.el
index f8ac70edefa..7582b6cdb85 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -705,6 +705,21 @@ Non-strings in LIST are ignored."
705 (setq list (cdr list))) 705 (setq list (cdr list)))
706 list) 706 list)
707 707
708(defun assoc-delete-all (key alist)
709 "Delete from ALIST all elements whose car is `equal' to KEY.
710Return the modified alist.
711Elements of ALIST that are not conses are ignored."
712 (while (and (consp (car alist))
713 (equal (car (car alist)) key))
714 (setq alist (cdr alist)))
715 (let ((tail alist) tail-cdr)
716 (while (setq tail-cdr (cdr tail))
717 (if (and (consp (car tail-cdr))
718 (equal (car (car tail-cdr)) key))
719 (setcdr tail (cdr tail-cdr))
720 (setq tail tail-cdr))))
721 alist)
722
708(defun assq-delete-all (key alist) 723(defun assq-delete-all (key alist)
709 "Delete from ALIST all elements whose car is `eq' to KEY. 724 "Delete from ALIST all elements whose car is `eq' to KEY.
710Return the modified alist. 725Return the modified alist.