51349
|
1 ;;; map-ynp.el --- general-purpose boolean question-asker
|
|
2
|
|
3 ;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 2000 Free Software Foundation, Inc.
|
|
4
|
|
5 ;; Author: Roland McGrath <roland@gnu.org>
|
|
6 ;; Maintainer: FSF
|
|
7 ;; Keywords: lisp, extensions
|
|
8
|
|
9 ;; This file is part of GNU Emacs.
|
|
10
|
|
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
12 ;; it under the terms of the GNU General Public License as published by
|
|
13 ;; the Free Software Foundation; either version 2, or (at your option)
|
|
14 ;; any later version.
|
|
15
|
|
16 ;; GNU Emacs is distributed in the hope that it will be useful,
|
|
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19 ;; GNU General Public License for more details.
|
|
20
|
|
21 ;; You should have received a copy of the GNU General Public License
|
|
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
24 ;; Boston, MA 02111-1307, USA.
|
|
25
|
|
26 ;;; Commentary:
|
|
27
|
|
28 ;; map-y-or-n-p is a general-purpose question-asking function.
|
|
29 ;; It asks a series of y/n questions (a la y-or-n-p), and decides to
|
|
30 ;; apply an action to each element of a list based on the answer.
|
|
31 ;; The nice thing is that you also get some other possible answers
|
|
32 ;; to use, reminiscent of query-replace: ! to answer y to all remaining
|
|
33 ;; questions; ESC or q to answer n to all remaining questions; . to answer
|
|
34 ;; y once and then n for the remainder; and you can get help with C-h.
|
|
35
|
|
36 ;;; Code:
|
|
37
|
|
38 (defun map-y-or-n-p (prompter actor list &optional help action-alist
|
|
39 no-cursor-in-echo-area)
|
|
40 "Ask a series of boolean questions.
|
|
41 Takes args PROMPTER ACTOR LIST, and optional args HELP and ACTION-ALIST.
|
|
42
|
|
43 LIST is a list of objects, or a function of no arguments to return the next
|
|
44 object or nil.
|
|
45
|
|
46 If PROMPTER is a string, the prompt is \(format PROMPTER OBJECT\). If not
|
|
47 a string, PROMPTER is a function of one arg (an object from LIST), which
|
|
48 returns a string to be used as the prompt for that object. If the return
|
|
49 value is not a string, it may be nil to ignore the object or non-nil to act
|
|
50 on the object without asking the user.
|
|
51
|
|
52 ACTOR is a function of one arg (an object from LIST),
|
|
53 which gets called with each object that the user answers `yes' for.
|
|
54
|
|
55 If HELP is given, it is a list (OBJECT OBJECTS ACTION),
|
|
56 where OBJECT is a string giving the singular noun for an elt of LIST;
|
|
57 OBJECTS is the plural noun for elts of LIST, and ACTION is a transitive
|
|
58 verb describing ACTOR. The default is \(\"object\" \"objects\" \"act on\"\).
|
|
59
|
|
60 At the prompts, the user may enter y, Y, or SPC to act on that object;
|
|
61 n, N, or DEL to skip that object; ! to act on all following objects;
|
|
62 ESC or q to exit (skip all following objects); . (period) to act on the
|
|
63 current object and then exit; or \\[help-command] to get help.
|
|
64
|
|
65 If ACTION-ALIST is given, it is an alist (KEY FUNCTION HELP) of extra keys
|
|
66 that will be accepted. KEY is a character; FUNCTION is a function of one
|
|
67 arg (an object from LIST); HELP is a string. When the user hits KEY,
|
|
68 FUNCTION is called. If it returns non-nil, the object is considered
|
|
69 \"acted upon\", and the next object from LIST is processed. If it returns
|
|
70 nil, the prompt is repeated for the same object.
|
|
71
|
|
72 Final optional argument NO-CURSOR-IN-ECHO-AREA non-nil says not to set
|
|
73 `cursor-in-echo-area' while prompting.
|
|
74
|
|
75 This function uses `query-replace-map' to define the standard responses,
|
|
76 but not all of the responses which `query-replace' understands
|
|
77 are meaningful here.
|
|
78
|
|
79 Returns the number of actions taken."
|
|
80 (let* ((actions 0)
|
|
81 user-keys mouse-event map prompt char elt tail def
|
|
82 ;; Non-nil means we should use mouse menus to ask.
|
|
83 use-menus
|
|
84 delayed-switch-frame
|
|
85 (next (if (or (and list (symbolp list))
|
|
86 (subrp list)
|
|
87 (byte-code-function-p list)
|
|
88 (and (consp list)
|
|
89 (eq (car list) 'lambda)))
|
|
90 (function (lambda ()
|
|
91 (setq elt (funcall list))))
|
|
92 (function (lambda ()
|
|
93 (if list
|
|
94 (progn
|
|
95 (setq elt (car list)
|
|
96 list (cdr list))
|
|
97 t)
|
|
98 nil))))))
|
|
99 (if (and (listp last-nonmenu-event)
|
|
100 use-dialog-box)
|
|
101 ;; Make a list describing a dialog box.
|
|
102 (let ((object (if help (capitalize (nth 0 help))))
|
|
103 (objects (if help (capitalize (nth 1 help))))
|
|
104 (action (if help (capitalize (nth 2 help)))))
|
|
105 (setq map `(("Yes" . act) ("No" . skip) ("Quit" . exit)
|
|
106 (,(if help (concat action " " object " And Quit")
|
|
107 "Do it and Quit") . act-and-exit)
|
|
108 (,(if help (concat action " All " objects)
|
|
109 "Do All") . automatic)
|
|
110 ,@(mapcar (lambda (elt)
|
|
111 (cons (capitalize (nth 2 elt))
|
|
112 (vector (nth 1 elt))))
|
|
113 action-alist))
|
|
114 use-menus t
|
|
115 mouse-event last-nonmenu-event))
|
|
116 (setq user-keys (if action-alist
|
|
117 (concat (mapconcat (function
|
|
118 (lambda (elt)
|
|
119 (key-description
|
|
120 (char-to-string (car elt)))))
|
|
121 action-alist ", ")
|
|
122 " ")
|
|
123 "")
|
|
124 ;; Make a map that defines each user key as a vector containing
|
|
125 ;; its definition.
|
|
126 map (cons 'keymap
|
|
127 (append (mapcar (lambda (elt)
|
|
128 (cons (car elt) (vector (nth 1 elt))))
|
|
129 action-alist)
|
|
130 query-replace-map))))
|
|
131 (unwind-protect
|
|
132 (progn
|
|
133 (if (stringp prompter)
|
|
134 (setq prompter `(lambda (object)
|
|
135 (format ,prompter object))))
|
|
136 (while (funcall next)
|
|
137 (setq prompt (funcall prompter elt))
|
|
138 (cond ((stringp prompt)
|
|
139 ;; Prompt the user about this object.
|
|
140 (setq quit-flag nil)
|
|
141 (if use-menus
|
|
142 (setq def (or (x-popup-dialog (or mouse-event use-menus)
|
|
143 (cons prompt map))
|
|
144 'quit))
|
|
145 ;; Prompt in the echo area.
|
|
146 (let ((cursor-in-echo-area (not no-cursor-in-echo-area))
|
|
147 (message-log-max nil))
|
|
148 (message "%s(y, n, !, ., q, %sor %s) "
|
|
149 prompt user-keys
|
|
150 (key-description (vector help-char)))
|
|
151 (if minibuffer-auto-raise
|
|
152 (raise-frame (window-frame (minibuffer-window))))
|
|
153 (while (progn
|
|
154 (setq char (read-event))
|
|
155 ;; If we get -1, from end of keyboard
|
|
156 ;; macro, try again.
|
|
157 (equal char -1)))
|
|
158 ;; Show the answer to the question.
|
|
159 (message "%s(y, n, !, ., q, %sor %s) %s"
|
|
160 prompt user-keys
|
|
161 (key-description (vector help-char))
|
|
162 (single-key-description char)))
|
|
163 (setq def (lookup-key map (vector char))))
|
|
164 (cond ((eq def 'exit)
|
|
165 (setq next (function (lambda () nil))))
|
|
166 ((eq def 'act)
|
|
167 ;; Act on the object.
|
|
168 (funcall actor elt)
|
|
169 (setq actions (1+ actions)))
|
|
170 ((eq def 'skip)
|
|
171 ;; Skip the object.
|
|
172 )
|
|
173 ((eq def 'act-and-exit)
|
|
174 ;; Act on the object and then exit.
|
|
175 (funcall actor elt)
|
|
176 (setq actions (1+ actions)
|
|
177 next (function (lambda () nil))))
|
|
178 ((eq def 'quit)
|
|
179 (setq quit-flag t)
|
|
180 (setq next `(lambda ()
|
|
181 (setq next ',next)
|
|
182 ',elt)))
|
|
183 ((eq def 'automatic)
|
|
184 ;; Act on this and all following objects.
|
|
185 (if (funcall prompter elt)
|
|
186 (progn
|
|
187 (funcall actor elt)
|
|
188 (setq actions (1+ actions))))
|
|
189 (while (funcall next)
|
|
190 (if (funcall prompter elt)
|
|
191 (progn
|
|
192 (funcall actor elt)
|
|
193 (setq actions (1+ actions))))))
|
|
194 ((eq def 'help)
|
|
195 (with-output-to-temp-buffer "*Help*"
|
|
196 (princ
|
|
197 (let ((object (if help (nth 0 help) "object"))
|
|
198 (objects (if help (nth 1 help) "objects"))
|
|
199 (action (if help (nth 2 help) "act on")))
|
|
200 (concat
|
|
201 (format "Type SPC or `y' to %s the current %s;
|
|
202 DEL or `n' to skip the current %s;
|
|
203 RET or `q' to exit (skip all remaining %s);
|
|
204 C-g to quit (cancel the operation);
|
|
205 ! to %s all remaining %s;\n"
|
|
206 action object object objects action
|
|
207 objects)
|
|
208 (mapconcat (function
|
|
209 (lambda (elt)
|
|
210 (format "%s to %s"
|
|
211 (single-key-description
|
|
212 (nth 0 elt))
|
|
213 (nth 2 elt))))
|
|
214 action-alist
|
|
215 ";\n")
|
|
216 (if action-alist ";\n")
|
|
217 (format "or . (period) to %s \
|
|
218 the current %s and exit."
|
|
219 action object))))
|
|
220 (save-excursion
|
|
221 (set-buffer standard-output)
|
|
222 (help-mode)))
|
|
223
|
|
224 (setq next `(lambda ()
|
|
225 (setq next ',next)
|
|
226 ',elt)))
|
|
227 ((vectorp def)
|
|
228 ;; A user-defined key.
|
|
229 (if (funcall (aref def 0) elt) ;Call its function.
|
|
230 ;; The function has eaten this object.
|
|
231 (setq actions (1+ actions))
|
|
232 ;; Regurgitated; try again.
|
|
233 (setq next `(lambda ()
|
|
234 (setq next ',next)
|
|
235 ',elt))))
|
|
236 ((and (consp char)
|
|
237 (eq (car char) 'switch-frame))
|
|
238 ;; switch-frame event. Put it off until we're done.
|
|
239 (setq delayed-switch-frame char)
|
|
240 (setq next `(lambda ()
|
|
241 (setq next ',next)
|
|
242 ',elt)))
|
|
243 (t
|
|
244 ;; Random char.
|
|
245 (message "Type %s for help."
|
|
246 (key-description (vector help-char)))
|
|
247 (beep)
|
|
248 (sit-for 1)
|
|
249 (setq next `(lambda ()
|
|
250 (setq next ',next)
|
|
251 ',elt)))))
|
|
252 (prompt
|
|
253 (funcall actor elt)
|
|
254 (setq actions (1+ actions))))))
|
|
255 (if delayed-switch-frame
|
|
256 (setq unread-command-events
|
|
257 (cons delayed-switch-frame unread-command-events))))
|
|
258 ;; Clear the last prompt from the minibuffer.
|
|
259 (let ((message-log-max nil))
|
|
260 (message ""))
|
|
261 ;; Return the number of actions that were taken.
|
|
262 actions))
|
|
263
|
|
264 ;;; map-ynp.el ends here
|