aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJuri Linkov2018-01-21 23:45:43 +0200
committerJuri Linkov2018-01-21 23:45:43 +0200
commitafba4ccb8b8c6347a44efd0b9f4d6fb85756f85b (patch)
tree42b97dca576f2020dc18a33ccabcc7ff1ddf2c93
parent9ae0e4aa1aee3d7ff2546e34aa83536f72f8c06a (diff)
downloademacs-afba4ccb8b8c6347a44efd0b9f4d6fb85756f85b.tar.gz
emacs-afba4ccb8b8c6347a44efd0b9f4d6fb85756f85b.zip
New function read-answer (bug#30073)
* lisp/emacs-lisp/map-ynp.el (read-answer): New function. (read-answer-short): New defcustom. * lisp/dired.el (dired-delete-file): Use read-answer. (dired--yes-no-all-quit-help): Remove function. (dired-delete-help): Remove defconst. * lisp/subr.el (assoc-delete-all): New function.
-rw-r--r--etc/NEWS3
-rw-r--r--lisp/dired.el41
-rw-r--r--lisp/emacs-lisp/map-ynp.el122
-rw-r--r--lisp/subr.el15
-rw-r--r--test/lisp/dired-aux-tests.el2
-rw-r--r--test/lisp/dired-tests.el22
6 files changed, 160 insertions, 45 deletions
diff --git a/etc/NEWS b/etc/NEWS
index ed07b105e44..d30f0b087cd 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -240,6 +240,9 @@ file name extensions.
240** The ecomplete sorting has changed to a decay-based algorithm. This 240** The ecomplete sorting has changed to a decay-based algorithm. This
241can be controlled by the new `ecomplete-sort-predicate' variable. 241can be controlled by the new `ecomplete-sort-predicate' variable.
242 242
243** The new function 'read-answer' accepts either long or short answers
244depending on the new customizable variable 'read-answer-short'.
245
243 246
244* Changes in Emacs 27.1 on Non-Free Operating Systems 247* Changes in Emacs 27.1 on Non-Free Operating Systems
245 248
diff --git a/lisp/dired.el b/lisp/dired.el
index b853d64c563..eebf8362cfc 100644
--- a/lisp/dired.el
+++ b/lisp/dired.el
@@ -2997,37 +2997,6 @@ Any other value means to ask for each directory."
2997;; Match anything but `.' and `..'. 2997;; Match anything but `.' and `..'.
2998(defvar dired-re-no-dot "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*") 2998(defvar dired-re-no-dot "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*")
2999 2999
3000(defconst dired-delete-help
3001 "Type:
3002`yes' to delete recursively the current directory,
3003`no' to skip to next,
3004`all' to delete all remaining directories with no more questions,
3005`quit' to exit,
3006`help' to show this help message.")
3007
3008(defun dired--yes-no-all-quit-help (prompt &optional help-msg)
3009 "Ask a question with valid answers: yes, no, all, quit, help.
3010PROMPT must end with '? ', for instance, 'Delete it? '.
3011If optional arg HELP-MSG is non-nil, then is a message to show when
3012the user answers 'help'. Otherwise, default to `dired-delete-help'."
3013 (let ((valid-answers (list "yes" "no" "all" "quit"))
3014 (answer "")
3015 (input-fn (lambda ()
3016 (read-string
3017 (format "%s [yes, no, all, quit, help] " prompt)))))
3018 (setq answer (funcall input-fn))
3019 (when (string= answer "help")
3020 (with-help-window "*Help*"
3021 (with-current-buffer "*Help*"
3022 (insert (or help-msg dired-delete-help)))))
3023 (while (not (member answer valid-answers))
3024 (unless (string= answer "help")
3025 (beep)
3026 (message "Please answer `yes' or `no' or `all' or `quit'")
3027 (sleep-for 2))
3028 (setq answer (funcall input-fn)))
3029 answer))
3030
3031;; Delete file, possibly delete a directory and all its files. 3000;; Delete file, possibly delete a directory and all its files.
3032;; This function is useful outside of dired. One could change its name 3001;; This function is useful outside of dired. One could change its name
3033;; to e.g. recursive-delete-file and put it somewhere else. 3002;; to e.g. recursive-delete-file and put it somewhere else.
@@ -3057,11 +3026,17 @@ TRASH non-nil means to trash the file instead of deleting, provided
3057 "trash" 3026 "trash"
3058 "delete") 3027 "delete")
3059 (dired-make-relative file)))) 3028 (dired-make-relative file))))
3060 (pcase (dired--yes-no-all-quit-help prompt) ; Prompt user. 3029 (pcase (read-answer
3030 prompt
3031 '(("yes" ?y "delete recursively the current directory")
3032 ("no" ?n "skip to next")
3033 ("all" ?! "delete all remaining directories with no more questions")
3034 ("quit" ?q "exit")))
3061 ('"all" (setq recursive 'always dired-recursive-deletes recursive)) 3035 ('"all" (setq recursive 'always dired-recursive-deletes recursive))
3062 ('"yes" (if (eq recursive 'top) (setq recursive 'always))) 3036 ('"yes" (if (eq recursive 'top) (setq recursive 'always)))
3063 ('"no" (setq recursive nil)) 3037 ('"no" (setq recursive nil))
3064 ('"quit" (keyboard-quit))))) 3038 ('"quit" (keyboard-quit))
3039 (_ (keyboard-quit))))) ; catch all unknown answers
3065 (setq recursive nil)) ; Empty dir or recursive is nil. 3040 (setq recursive nil)) ; Empty dir or recursive is nil.
3066 (delete-directory file recursive trash)))) 3041 (delete-directory file recursive trash))))
3067 3042
diff --git a/lisp/emacs-lisp/map-ynp.el b/lisp/emacs-lisp/map-ynp.el
index dd80524a152..61c04ff7b3e 100644
--- a/lisp/emacs-lisp/map-ynp.el
+++ b/lisp/emacs-lisp/map-ynp.el
@@ -252,4 +252,126 @@ C-g to quit (cancel the whole command);
252 ;; Return the number of actions that were taken. 252 ;; Return the number of actions that were taken.
253 actions)) 253 actions))
254 254
255
256;; read-answer is a general-purpose question-asker that supports
257;; either long or short answers.
258
259;; For backward compatibility check if short y/n answers are preferred.
260(defcustom read-answer-short (eq (symbol-function 'yes-or-no-p) 'y-or-n-p)
261 "If non-nil, accept short answers to the question."
262 :type 'boolean
263 :version "27.1"
264 :group 'minibuffer)
265
266(defconst read-answer-map--memoize (make-hash-table :weakness 'key :test 'equal))
267
268(defun read-answer (question answers)
269 "Read an answer either as a complete word or its character abbreviation.
270Ask user a question and accept an answer from the list of possible answers.
271
272QUESTION should end in a space; this function adds a list of answers to it.
273
274ANSWERS is an alist with elements in the following format:
275 (LONG-ANSWER SHORT-ANSWER HELP-MESSAGE)
276where
277 LONG-ANSWER is a complete answer,
278 SHORT-ANSWER is an abbreviated one-character answer,
279 HELP-MESSAGE is a string describing the meaning of the answer.
280
281Example:
282 \\='((\"yes\" ?y \"perform the action\")
283 (\"no\" ?n \"skip to the next\")
284 (\"all\" ?! \"accept all remaining without more questions\")
285 (\"help\" ?h \"show help\")
286 (\"quit\" ?q \"exit\"))
287
288When `read-answer-short' is non-nil, accept short answers.
289
290Return a long answer even in case of accepting short ones.
291
292When `use-dialog-box' is t, pop up a dialog window to get user input."
293 (custom-reevaluate-setting 'read-answer-short)
294 (let* ((short read-answer-short)
295 (answers-with-help
296 (if (assoc "help" answers)
297 answers
298 (append answers '(("help" ?? "show this help message")))))
299 (answers-without-help
300 (assoc-delete-all "help" (copy-alist answers-with-help)))
301 (prompt
302 (format "%s(%s) " question
303 (mapconcat (lambda (a)
304 (if short
305 (format "%c" (nth 1 a))
306 (nth 0 a)))
307 answers-with-help ", ")))
308 (message
309 (format "Please answer %s."
310 (mapconcat (lambda (a)
311 (format "`%s'" (if short
312 (string (nth 1 a))
313 (nth 0 a))))
314 answers-with-help " or ")))
315 (short-answer-map
316 (when short
317 (or (gethash answers read-answer-map--memoize)
318 (puthash answers
319 (let ((map (make-sparse-keymap)))
320 (set-keymap-parent map minibuffer-local-map)
321 (dolist (a answers-with-help)
322 (define-key map (vector (nth 1 a))
323 (lambda ()
324 (interactive)
325 (delete-minibuffer-contents)
326 (insert (nth 0 a))
327 (exit-minibuffer))))
328 (define-key map [remap self-insert-command]
329 (lambda ()
330 (interactive)
331 (delete-minibuffer-contents)
332 (beep)
333 (message message)
334 (sleep-for 2)))
335 map)
336 read-answer-map--memoize))))
337 answer)
338 (while (not (assoc (setq answer (downcase
339 (cond
340 ((and (display-popup-menus-p)
341 last-input-event ; not during startup
342 (listp last-nonmenu-event)
343 use-dialog-box)
344 (x-popup-dialog
345 t
346 (cons question
347 (mapcar (lambda (a)
348 (cons (capitalize (nth 0 a))
349 (nth 0 a)))
350 answers-with-help))))
351 (short
352 (read-from-minibuffer
353 prompt nil short-answer-map nil
354 'yes-or-no-p-history))
355 (t
356 (read-from-minibuffer
357 prompt nil nil nil
358 'yes-or-no-p-history)))))
359 answers-without-help))
360 (if (string= answer "help")
361 (with-help-window "*Help*"
362 (with-current-buffer "*Help*"
363 (insert "Type:\n"
364 (mapconcat
365 (lambda (a)
366 (format "`%s'%s to %s"
367 (if short (string (nth 1 a)) (nth 0 a))
368 (if short (format " (%s)" (nth 0 a)) "")
369 (nth 2 a)))
370 answers-with-help ",\n")
371 ".\n")))
372 (beep)
373 (message message)
374 (sleep-for 2)))
375 answer))
376
255;;; map-ynp.el ends here 377;;; map-ynp.el ends here
diff --git a/lisp/subr.el b/lisp/subr.el
index 46cf5a34ccc..092850a44d9 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.
diff --git a/test/lisp/dired-aux-tests.el b/test/lisp/dired-aux-tests.el
index 89cb7b6111d..ab6d1cb0564 100644
--- a/test/lisp/dired-aux-tests.el
+++ b/test/lisp/dired-aux-tests.el
@@ -59,7 +59,7 @@
59 (unwind-protect 59 (unwind-protect
60 (if ,yes-or-no 60 (if ,yes-or-no
61 (cl-letf (((symbol-function 'yes-or-no-p) 61 (cl-letf (((symbol-function 'yes-or-no-p)
62 (lambda (prompt) (eq ,yes-or-no 'yes)))) 62 (lambda (_prompt) (eq ,yes-or-no 'yes))))
63 ,@body) 63 ,@body)
64 ,@body) 64 ,@body)
65 ;; clean up 65 ;; clean up
diff --git a/test/lisp/dired-tests.el b/test/lisp/dired-tests.el
index c0242137b3a..bb0e1bc3880 100644
--- a/test/lisp/dired-tests.el
+++ b/test/lisp/dired-tests.el
@@ -384,9 +384,9 @@
384 (dired-test-with-temp-dirs 384 (dired-test-with-temp-dirs
385 'just-empty-dirs 385 'just-empty-dirs
386 (let (asked) 386 (let (asked)
387 (advice-add 'dired--yes-no-all-quit-help 387 (advice-add 'read-answer
388 :override 388 :override
389 (lambda (_) (setq asked t) "") 389 (lambda (_q _a) (setq asked t) "")
390 '((name . dired-test-bug27940-advice))) 390 '((name . dired-test-bug27940-advice)))
391 (dired default-directory) 391 (dired default-directory)
392 (dired-toggle-marks) 392 (dired-toggle-marks)
@@ -395,44 +395,44 @@
395 (progn 395 (progn
396 (should-not asked) 396 (should-not asked)
397 (should-not (dired-get-marked-files))) ; All dirs deleted. 397 (should-not (dired-get-marked-files))) ; All dirs deleted.
398 (advice-remove 'dired--yes-no-all-quit-help 'dired-test-bug27940-advice)))) 398 (advice-remove 'read-answer 'dired-test-bug27940-advice))))
399 ;; Answer yes 399 ;; Answer yes
400 (dired-test-with-temp-dirs 400 (dired-test-with-temp-dirs
401 nil 401 nil
402 (advice-add 'dired--yes-no-all-quit-help :override (lambda (_) "yes") 402 (advice-add 'read-answer :override (lambda (_q _a) "yes")
403 '((name . dired-test-bug27940-advice))) 403 '((name . dired-test-bug27940-advice)))
404 (dired default-directory) 404 (dired default-directory)
405 (dired-toggle-marks) 405 (dired-toggle-marks)
406 (dired-do-delete nil) 406 (dired-do-delete nil)
407 (unwind-protect 407 (unwind-protect
408 (should-not (dired-get-marked-files)) ; All dirs deleted. 408 (should-not (dired-get-marked-files)) ; All dirs deleted.
409 (advice-remove 'dired--yes-no-all-quit-help 'dired-test-bug27940-advice))) 409 (advice-remove 'read-answer 'dired-test-bug27940-advice)))
410 ;; Answer no 410 ;; Answer no
411 (dired-test-with-temp-dirs 411 (dired-test-with-temp-dirs
412 nil 412 nil
413 (advice-add 'dired--yes-no-all-quit-help :override (lambda (_) "no") 413 (advice-add 'read-answer :override (lambda (_q _a) "no")
414 '((name . dired-test-bug27940-advice))) 414 '((name . dired-test-bug27940-advice)))
415 (dired default-directory) 415 (dired default-directory)
416 (dired-toggle-marks) 416 (dired-toggle-marks)
417 (dired-do-delete nil) 417 (dired-do-delete nil)
418 (unwind-protect 418 (unwind-protect
419 (should (= 5 (length (dired-get-marked-files)))) ; Just the empty dirs deleted. 419 (should (= 5 (length (dired-get-marked-files)))) ; Just the empty dirs deleted.
420 (advice-remove 'dired--yes-no-all-quit-help 'dired-test-bug27940-advice))) 420 (advice-remove 'read-answer 'dired-test-bug27940-advice)))
421 ;; Answer all 421 ;; Answer all
422 (dired-test-with-temp-dirs 422 (dired-test-with-temp-dirs
423 nil 423 nil
424 (advice-add 'dired--yes-no-all-quit-help :override (lambda (_) "all") 424 (advice-add 'read-answer :override (lambda (_q _a) "all")
425 '((name . dired-test-bug27940-advice))) 425 '((name . dired-test-bug27940-advice)))
426 (dired default-directory) 426 (dired default-directory)
427 (dired-toggle-marks) 427 (dired-toggle-marks)
428 (dired-do-delete nil) 428 (dired-do-delete nil)
429 (unwind-protect 429 (unwind-protect
430 (should-not (dired-get-marked-files)) ; All dirs deleted. 430 (should-not (dired-get-marked-files)) ; All dirs deleted.
431 (advice-remove 'dired--yes-no-all-quit-help 'dired-test-bug27940-advice))) 431 (advice-remove 'read-answer 'dired-test-bug27940-advice)))
432 ;; Answer quit 432 ;; Answer quit
433 (dired-test-with-temp-dirs 433 (dired-test-with-temp-dirs
434 nil 434 nil
435 (advice-add 'dired--yes-no-all-quit-help :override (lambda (_) "quit") 435 (advice-add 'read-answer :override (lambda (_q _a) "quit")
436 '((name . dired-test-bug27940-advice))) 436 '((name . dired-test-bug27940-advice)))
437 (dired default-directory) 437 (dired default-directory)
438 (dired-toggle-marks) 438 (dired-toggle-marks)
@@ -440,7 +440,7 @@
440 (dired-do-delete nil)) 440 (dired-do-delete nil))
441 (unwind-protect 441 (unwind-protect
442 (should (= 6 (length (dired-get-marked-files)))) ; All empty dirs but zeta-empty-dir deleted. 442 (should (= 6 (length (dired-get-marked-files)))) ; All empty dirs but zeta-empty-dir deleted.
443 (advice-remove 'dired--yes-no-all-quit-help 'dired-test-bug27940-advice)))) 443 (advice-remove 'read-answer 'dired-test-bug27940-advice))))
444 444
445 445
446(provide 'dired-tests) 446(provide 'dired-tests)