1 ;;; working --- Display a "working" message in the minibuffer.
3 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
4 ;; 2004, 2007, 2008, 2009 Eric M. Ludlam
6 ;; Author: Eric M. Ludlam <zappo@gnu.org>
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 ;; Boston, MA 02110-1301, USA.
27 ;; Working lets Emacs Lisp programmers easily display working messages.
28 ;; These messages typically come in the form of a percentile, or generic
29 ;; doodles if a maximum is unknown.
31 ;; The working entry points are quite simple. If you have a loop that needs
32 ;; to display a status as it goes along, it would look like this:
34 ;; (working-status-forms "Doing stuff" "done"
36 ;; (working-status (calc-percentile))
38 ;; (working-status t))
40 ;; If you cannot calculate a percentile, use the function
41 ;; `working-dynamic-status' instead, and pass in what you know. For
42 ;; both status printing functions, the first argument is optional,
43 ;; and you may pass in additional arguments as `format' elements
44 ;; to the first argument of `working-status-forms'.
46 ;; See the examples at the end of the buffer.
48 ;;; Backwards Compatibility:
50 ;; If you want to use working in your program, but don't want to force people
51 ;; to install working, use could add this at the beginning of your program for
55 ;; (condition-case nil
59 ;; (defmacro working-status-forms (message donestr &rest forms)
60 ;; "Contain a block of code during which a working status is shown."
61 ;; (list 'let (list (list 'msg message) (list 'dstr donestr)
63 ;; (cons 'progn forms)))
65 ;; (defun working-status (&optional percent &rest args)
66 ;; "Called within the macro `working-status-forms', show the status."
67 ;; (message "%s%s" (apply 'format msg args)
68 ;; (if (eq percent t) (concat "... " dstr)
69 ;; (format "... %3d%%"
71 ;; (floor (* 100.0 (/ (float (point))
72 ;; (point-max)))))))))
74 ;; (defun working-dynamic-status (&optional number &rest args)
75 ;; "Called within the macro `working-status-forms', show the status."
76 ;; (message "%s%s" (apply 'format msg args)
77 ;; (format "... %c" (aref [ ?- ?/ ?| ?\\ ] (% ref1 4))))
78 ;; (setq ref1 (1+ ref1)))
80 ;; (put 'working-status-forms 'lisp-indent-function 2)))))
82 ;; Depending on what features you use, it is, of course, easy to
83 ;; reduce the total size of the above by omitting those features you
90 ;; 1.1 Working messages are no longer logged.
91 ;; Added a generic animation display funciton:
92 ;; Convert celeron to animator
93 ;; Added a bounce display
94 ;; Made working robust under a multi-frame environment (speedbar)
96 ;; 1.2 Fix up documentation.
97 ;; Updated dotgrowth function for exceptionally large numbers of dots.
98 ;; Added the percentage bubble displays.
100 ;; 1.3 Added `working-status-timeout' and `working-status-call-process'.
101 ;; Added test fns `working-wait-for-keypress' and `working-verify-sleep'.
105 ;; 1.5 Use features from the fame library.
112 (defgroup working nil
113 "Working messages display."
118 ;;; User configurable variables
120 (defcustom working-status-percentage-type 'working-bar-percent-display
121 "*Function used to display the percent status.
122 Functions provided in `working' are:
123 `working-percent-display'
124 `working-bar-display'
125 `working-bar-percent-display'
126 `working-percent-bar-display'
127 `working-bubble-display'
128 `working-bubble-precent-display'
129 `working-celeron-percent-display'"
131 :type '(choice (const working-percent-display)
132 (const working-bar-display)
133 (const working-bar-percent-display)
134 (const working-percent-bar-display)
135 (const working-bubble-display)
136 (const working-bubble-percent-display)
137 (const working-celeron-percent-display)
140 (defcustom working-status-dynamic-type 'working-celeron-display
141 "*Function used to display an animation indicating progress being made.
142 Dynamic working types occur when the program does not know how long
143 it will take ahead of time. Functions provided in `working' are:
144 `working-number-display'
145 `working-text-display'
146 `working-spinner-display'
147 `working-dotgrowth-display'
148 `working-celeron-display'
149 `working-bounce-display'"
151 :type '(choice (const working-number-display)
152 (const working-text-display)
153 (const working-spinner-display)
154 (const working-dotgrowth-display)
155 (const working-celeron-display)
156 (const working-bounce-display)
159 (defcustom working-percentage-step 2
160 "*Percentage display step.
161 A number representing how large a step must be taken when working a
162 percentage display. A number such as `2' means `2%'."
168 ;; When the user doesn't want messages in the minibuffer, hack the mode
169 ;; line of the current buffer.
170 (if (featurep 'xemacs)
171 (eval-and-compile (defalias 'working-mode-line-update 'redraw-modeline))
172 (eval-and-compile (defalias 'working-mode-line-update 'force-mode-line-update)))
174 (defvar working-mode-line-message nil
175 "Message used by working when showing status in the mode line.")
177 (if (boundp 'global-mode-string)
179 ;; If this variable exists, use it to push the working message into
180 ;; an interesting part of the mode line.
181 (if (null global-mode-string)
182 (setq global-mode-string (list "")))
183 (setq global-mode-string
184 (append global-mode-string '(working-mode-line-message))))
185 ;; Else, use minor mode trickery to get a reliable way of doing the
186 ;; same thing across many versions of Emacs.
187 (setq minor-mode-alist (cons
188 '(working-mode-line-message working-mode-line-message)
192 (defvar working-use-echo-area-p t
193 "*Non-nil use the echo area to display working messages.")
195 ;;; Variables used in stages
197 (defvar working-message nil
198 "Message stored when in a status loop.")
199 (defvar working-donestring nil
200 "Done string stored when in a status loop.")
201 (defvar working-ref1 nil
202 "A reference number used in a status loop.")
203 (defvar working-last-percent 0
204 "A reference number used in a status loop.")
206 ;;; Programmer functions
210 ((fboundp 'noninteractive)
211 ;; Silence the XEmacs byte compiler
212 (defvar noninteractive))
213 ((boundp 'noninteractive)
214 ;; Silence the Emacs byte compiler
215 (defun noninteractive nil))
218 (defsubst working-noninteractive ()
219 "Return non-nil if running without interactive terminal."
220 (if (boundp 'noninteractive)
224 (defun working-message-echo (&rest args)
225 "Print but don't log a one-line message at the bottom of the screen.
226 See the function `message' for details on ARGS."
227 (or (working-noninteractive)
228 (apply 'fame-message-nolog args)))
230 (defalias 'working-current-message 'fame-current-message)
231 (defalias 'working-temp-message 'fame-temp-message)
233 (defun working-message (&rest args)
234 "Display a message using `working-message-echo' or in mode line.
235 See the function `message' for details on ARGS."
236 (if working-use-echo-area-p
237 (apply 'working-message-echo args)
238 (when (not working-mode-line-message)
239 ;; If we start out nil, put stuff in to show we are up to
240 (setq working-mode-line-message "Working...")
241 (working-mode-line-update)
246 (cond ((fboundp 'run-with-timer)
247 (eval-and-compile (defalias 'working-run-with-timer 'run-with-timer))
248 (eval-and-compile (defalias 'working-cancel-timer 'cancel-timer))
250 ;;Add compatibility here
252 ;; This gets the message out but has no timers.
253 (defun working-run-with-timer (&rest foo)
254 (working-message working-message))
255 (defun working-cancel-timer (&rest foo)
256 (working-message "%s%s"
258 working-donestring)))
261 (defmacro working-status-forms (message donestr &rest forms)
262 "Contain a block of code during which a working status is shown.
263 MESSAGE is the message string to use and DONESTR is the completed text
264 to use when the functions `working-status' is called from FORMS."
265 (let ((current-message (make-symbol "working-current-message")))
266 `(let ((,current-message (working-current-message))
267 (working-message ,message)
268 (working-donestring ,donestr)
270 (working-last-percent 0))
273 (setq working-mode-line-message nil)
274 (if working-use-echo-area-p
275 (message ,current-message)
276 (working-mode-line-update)
279 (put 'working-status-forms 'lisp-indent-function 2)
281 (defmacro working-status-timeout (timeout message donestr &rest forms)
282 "Contain a block of code during which working status is shown.
283 The code may call `sit-for' or `accept-process-output', so a timer
284 is needed to update the message.
285 TIMEOUT is the length of time to wait between message updates.
286 MESSAGE is the message string to use and DONESTR is the completed text
287 to use when the functions `working-status' is called from FORMS."
288 (let ((current-message (make-symbol "working-current-message")))
289 `(let* ((,current-message (working-current-message))
290 (working-message ,message)
291 (working-donestring ,donestr)
295 (working-run-with-timer time time 'working-dynamic-status)))
298 (working-cancel-timer working-timer)
299 (working-dynamic-status t)
300 (setq working-mode-line-message nil)
301 (if working-use-echo-area-p
302 (message ,current-message)
303 (working-mode-line-update)
306 (put 'working-status-timeout 'lisp-indent-function 3)
308 (defun working-status-call-process
309 (timeout message donestr program &optional infile buffer display &rest args)
310 "Display working messages while running a process.
311 TIMEOUT is how fast to display the messages.
312 MESSAGE is the message to show, and DONESTR is the string to add when done.
313 CALLPROCESSARGS are the same style of args as passed to `call-process'.
314 The are: PROGRAM, INFILE, BUFFER, DISPLAY, and ARGS.
315 Since it actually calls `start-process', not all features will work."
316 (working-status-timeout timeout message donestr
317 (let ((proc (apply 'start-process "working"
318 (if (listp buffer) (car buffer) buffer)
320 (set-process-sentinel proc 'list)
321 (while (eq (process-status proc) 'run)
322 (accept-process-output proc)
323 ;; accept-process-output caused my solaris Emacs 20.3 to crash.
324 ;; If this is unreliable for you, use the below which will work
325 ;; in that situation.
326 ;; (if (not (sit-for timeout)) (read-event))
329 (defun working-status (&optional percent &rest args)
330 "Called within the macro `working-status-forms', show the status.
331 If PERCENT is nil, then calculate PERCENT from the value of `point' in
332 the current buffer. If it is a number or float, use it as the raw
334 Additional ARGS are passed to fill on % elements of MESSAGE from the
335 macro `working-status-forms'."
336 (when (and working-message working-status-percentage-type)
338 (floor (* 100.0 (/ (float (point)) (point-max)))))))
340 (> (- p working-last-percent) working-percentage-step))
341 (let* ((m1 (apply 'format working-message args))
342 (m2 (funcall working-status-percentage-type (length m1) p)))
343 (working-message "%s%s" m1 m2)
344 (setq working-last-percent p))))))
346 (defun working-dynamic-status (&optional number &rest args)
347 "Called within the macro `working-status-forms', show the status.
348 If NUMBER is nil, then increment a local NUMBER from 0 with each call.
349 If it is a number or float, use it as the raw percentile.
350 Additional ARGS are passed to fill on % elements of MESSAGE from the
351 macro `working-status-forms'."
352 (when (and working-message working-status-dynamic-type)
353 (let* ((n (or number working-ref1))
354 (m1 (apply 'format working-message args))
355 (m2 (funcall working-status-dynamic-type (length m1) n)))
356 (working-message "%s%s" m1 m2)
357 (setq working-ref1 (1+ working-ref1)))))
361 (defun working-message-frame-width ()
362 "Return the width of the frame the working message will be in."
363 (let* ((mbw (cond ((fboundp 'frame-parameter)
364 (frame-parameter (selected-frame) 'minibuffer))
365 ((fboundp 'frame-property)
366 (frame-property (selected-frame) 'minibuffer))))
367 (fr (if (windowp mbw)
369 default-minibuffer-frame)))
372 ;;; Percentage display types.
374 (defun working-percent-display (length percent)
375 "Return the percentage of the buffer that is done in a string.
376 LENGTH is the amount of display that has been used. PERCENT
377 is t to display the done string, or the percentage to display."
378 (cond ((eq percent t) (concat "... " working-donestring))
379 ;; All the % signs because it then gets passed to message.
380 (t (format "... %3d%%" percent))))
382 (defun working-bar-display (length percent)
383 "Return a string with a bar-graph showing percent.
384 LENGTH is the amount of display that has been used. PERCENT
385 is t to display the done string, or the percentage to display."
386 (let ((bs (- (working-message-frame-width) length 5)))
387 (cond ((eq percent t)
388 (concat ": [" (make-string bs ?#) "] " working-donestring))
390 (t (let ((bsl (floor (* (/ percent 100.0) bs))))
393 (make-string (- bs bsl) ?.)
396 (defun working-bar-percent-display (length percent)
397 "Return a string with a bar-graph and percentile showing percentage.
398 LENGTH is the amount of display that has been used. PERCENT
399 is t to display the done string, or the percentage to display."
400 (let* ((ps (if (eq percent t)
401 (concat "... " working-donestring)
402 (working-percent-display length percent)))
403 (psl (+ 2 length (length ps))))
404 (cond ((eq percent t)
405 (concat (working-bar-display psl 100) " " ps))
407 (setq working-ref1 (length ps))
408 (concat (working-bar-display psl percent) " " ps)))))
410 (defun working-percent-bar-display (length percent)
411 "Return a string with a percentile and bar-graph showing percentage.
412 LENGTH is the amount of display that has been used. PERCENT
413 is t to display the done string, or the percentage to display."
414 (let* ((ps (if (eq percent t)
415 (concat "... " working-donestring)
416 (working-percent-display length percent)))
417 (psl (+ 1 length (length ps))))
418 (cond ((eq percent t)
419 (concat ps " " (working-bar-display psl 100)))
421 (setq working-ref1 (length ps))
422 (concat ps " " (working-bar-display psl percent))))))
424 (defun working-bubble-display (length percent)
425 "Return a string with a bubble graph indicating the precent completed.
426 LENGTH is the amount of the display that has been used. PERCENT
427 is t to display the done string, or the percentage to display."
429 (concat " [@@@@@@@@@@@@@@@@@@@@] " working-donestring)
431 (bubbles [ ?. ?- ?o ?O ?@ ]))
433 (setq bs (concat bs (make-string (/ (floor percent) 5) ?@))))
435 (char-to-string (aref bubbles (% (floor percent) 5)))))
436 (if (< (/ (floor percent) 5) 20)
437 (setq bs (concat bs (make-string (- 19 (/ (floor percent) 5)) ? ))))
440 (defun working-bubble-percent-display (length percent)
441 "Return a string with a percentile and bubble graph showing percentage.
442 LENGTH is the amount of display that has been used. PERCENT
443 is t to display the done string, or the percentage to display."
444 (let* ((ps (if (eq percent t)
445 (concat " ... " working-donestring)
446 (working-percent-display length percent)))
447 (psl (+ 1 length (length ps))))
448 (cond ((eq percent t)
449 (concat (working-bubble-display psl t)))
451 (setq working-ref1 (length ps))
452 (concat (working-bubble-display psl percent) ps)))))
454 (defun working-celeron-percent-display (length percent)
455 "Return a string with a celeron and string showing percent.
456 LENGTH is the amount of display that has been used. PERCENT
457 is t to display the done string, or the percentage to display."
459 (cond ((eq percent t) (working-celeron-display length t))
460 ;; All the % signs because it then gets passed to message.
461 (t (format "%s %3d%%"
462 (working-celeron-display length 0)
464 (setq working-ref1 (1+ working-ref1))))
466 ;;; Dynamic display types.
468 (defun working-number-display (length number)
469 "Return a string displaying the number of things that happened.
470 LENGTH is the amount of display that has been used. NUMBER
471 is t to display the done string, or the number to display."
472 (cond ((eq number t) (concat "... " working-donestring))
473 ;; All the % signs because it then gets passed to message.
474 (t (format "... %d" number))))
476 (defun working-text-display (length text)
477 "Return a string displaying the name of things that happened.
478 LENGTH is the amount of display that has been used. TEXT
479 is t to display the done string, or the text to display."
481 (concat "... " working-donestring)
482 (format "... %s" text)))
484 (defun working-spinner-display (length number)
485 "Return a string displaying a spinner based on a number.
486 LENGTH is the amount of display that has been used. NUMBER
487 is t to display the done string, or the number to display."
488 (cond ((eq number t) (concat "... " working-donestring))
489 ;; All the % signs because it then gets passed to message.
490 (t (format "... %c" (aref [ ?- ?/ ?| ?\\ ] (% working-ref1 4))))))
492 (defun working-dotgrowth-display (length number)
493 "Return a string displaying growing dots due to activity.
494 LENGTH is the amount of display that has been used. NUMBER
495 is t to display the done string, or the number to display.
496 This display happens to ignore NUMBER."
497 (let* ((width (- (working-message-frame-width) 4 length))
498 (num-wrap (/ working-ref1 width))
499 (num-. (% working-ref1 width))
500 (dots [ ?. ?, ?o ?* ?O ?@ ?# ]))
501 (concat " (" (make-string num-. (aref dots (% num-wrap (length dots)))) ")"
502 (if (eq number t) (concat " " working-donestring) ""))))
504 (defun working-frame-animation-display (length number frames)
505 "Manage a simple frame-based animation for working functions.
506 LENGTH is the number of characters left. NUMBER is a passed in
507 number (which happens to be ignored.). While coders pass t into
508 NUMBER, functions using this should convert NUMBER into a vector
509 describing how to render the done message.
510 Argument FRAMES are the frames used in the animation."
511 (cond ((vectorp number)
512 (let ((zone (- (length (aref frames 0)) (length (aref number 0))
513 (length (aref number 1)))))
514 (if (< (length working-donestring) zone)
515 (concat " " (aref number 0)
517 (ceiling (/ (- (float zone)
518 (length working-donestring)) 2)) ? )
521 (floor (/ (- (float zone)
522 (length working-donestring)) 2)) ? )
524 (concat " " (aref frames (% working-ref1 (length frames)))
525 " " working-donestring))))
526 (t (concat " " (aref frames (% working-ref1 (length frames)))))))
528 (defvar working-celeron-strings
529 [ "[O ]" "[oO ]" "[-oO ]" "[ -oO ]" "[ -oO ]" "[ -oO]"
530 "[ -O]" "[ O]" "[ Oo]" "[ Oo-]" "[ Oo- ]" "[ Oo- ]"
532 "Strings representing a silly celeron.")
534 (defun working-celeron-display (length number)
535 "Return a string displaying a celeron as things happen.
536 LENGTH is the amount of display that has been used. NUMBER
537 is t to display the done string, or the number to display."
539 (working-frame-animation-display length [ "[" "]" ]
540 working-celeron-strings))
541 ;; All the % signs because it then gets passed to message.
542 (t (working-frame-animation-display length number
543 working-celeron-strings))))
545 (defvar working-bounce-strings
559 "Strings for the bounce animation.")
561 (defun working-bounce-display (length number)
562 "Return a string displaying a celeron as things happen.
563 LENGTH is the amount of display that has been used. NUMBER
564 is t to display the done string, or the number to display."
566 (working-frame-animation-display length [ "[" "]" ]
567 working-bounce-strings))
568 ;; All the % signs because it then gets passed to message.
569 (t (working-frame-animation-display length number
570 working-bounce-strings))))
572 ;;; Some edebug hooks
577 (def-edebug-spec working-status-forms (form form def-body))
578 (def-edebug-spec working-status-timeout (form form form def-body))))
580 ;;; Example function using `working'
582 (defun working-verify-parenthesis-a ()
583 "Verify all the parenthesis in an elisp program buffer."
585 (working-status-forms "Scanning" "done"
587 (goto-char (point-min))
589 ;; Use default buffer position.
597 (defun working-verify-parenthesis-b ()
598 "Verify all the parenthesis in an elisp program buffer."
600 (working-status-forms "Scanning" "done"
602 (goto-char (point-min))
604 ;; Use default buffer position.
605 (working-dynamic-status nil)
609 (working-dynamic-status t))
612 (defun working-wait-for-keypress ()
613 "Display funny graphics while waiting for a keypress."
615 (working-status-timeout .1 "Working Test: Press a key" "done"
616 (while (sit-for 10)))
617 (when (input-pending-p)
618 (if (fboundp 'read-event)
623 (defun working-verify-sleep ()
624 "Display funny graphics while waiting for sleep to sleep."
626 (working-status-call-process .1 "Zzzzz" "Snort" "sleep" nil nil nil "2"))
628 (defun working-verify-mode-line ()
629 "Display graphics in the mode-line for timeout."
631 (let ((working-use-echo-area-p nil))
632 (message "Pres a Key")
633 (working-status-timeout .1 "" ""
634 (while (sit-for 10)))
639 ;;; working.el ends here