;;; twiddle.el --- mode-line display hack ;; Copyright (C) 1997 Noah S. Friedman ;; Author: Noah Friedman ;; Maintainer: friedman@prep.ai.mit.edu ;; Keywords: extensions ;; Status: Works in Emacs 19 and XEmacs. ;; Created: 1997-03-12 ;; $Id: twiddle.el,v 1.3 1997/03/14 09:14:16 friedman Exp $ ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, you can either send email to this ;; program's maintainer or write to: The Free Software Foundation, ;; Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA. ;;; Commentary: ;; Inspired by a similar hack by Jim Blandy . ;; There are two user commands of interest: twiddle-start and twiddle-compile. ;; If you write new twiddles, try to minimize or avoid consing, since those ;; functions are called constantly. ;;; Code: (eval-and-compile (defconst twiddle-xemacs-p (save-match-data (string-match "XEmacs" (emacs-version)))) (if twiddle-xemacs-p (require 'itimer) (require 'timer)) ) ;; end eval-and-compile (defvar twiddle-properties nil "*Text properties to put on the twiddle text.") (defconst twiddle-delay 1 "*Default amount of time between mode line updates, in seconds. This can be overridden for specific hacks in `twiddle-hacks'.") (defconst twiddle-default-hack "twiddle" "*Default twiddle to run.") (defconst twiddle-hacks '(("twiddle" twiddle-frob-twiddle 0 " - ") ("roll" twiddle-frob-roll 5 ?\ ) ("asterisk" twiddle-frob-asterisk 10 ?-)) "*Twiddle hacks. This is an alist of hacks, where each member contains the following elts: 0. A name used for completion by `twiddle-start' and `twiddle-compile'. 1. A twiddle function. 2. The length of the twiddle string in the mode line. 3. The initial char in each position of the twiddle string. This may also be a string, in which case the contents of this string is used as the initial value and the length parameter is ignored. 4. (optional) A time delay between mode line updates, in seconds. If not specified, the value of `twiddle-delay' is used. 5. (optional) This and any remaining arguments are passed to the function specified in field 1 each time it's called.") ;; Internal twiddle data (defvar twiddle-mode-string nil) (defvar twiddle-timer nil) (defvar twiddle-current-pos 0) (defvar twiddle-current-saved-char nil) (defvar twiddle-direction 'identity) (defvar twiddle-temp nil) (defconst twiddle-rotate-chars '(?| ?/ ?- ?\\)) ;;;###autoload (defun twiddle-start (&optional hack) "Start a mode line display hack. If called interactively with a prefix argument, prompt for the name of a hack to run. If called from lisp, optional argument HACK is the name of a hack to run. Named hacks are defined in the table `twiddle-hacks'." (interactive (list (and current-prefix-arg (twiddle-read-hack-complete)))) (or hack (setq hack twiddle-default-hack)) (let ((hack-data (assoc hack twiddle-hacks))) (cond ((null hack-data) (if hack (error "Unknown twiddle hack: %s" hack) (error "No twiddle hack specified."))) (t (apply 'twiddle-start-twiddling (cdr hack-data)))))) ;;;###autoload (defun twiddle-compile (&rest compile-args) "Like \\[compile], but run a twiddle hack during compilation. If called with a prefix argument, prompt for a specific hack to run." (interactive) (let* ((hack (if current-prefix-arg (twiddle-read-hack-complete) twiddle-default-hack)) (hack-data (assoc hack twiddle-hacks))) (and (null hack-data) (if hack (error "Unknown twiddle hack: %s" hack) (error "No twiddle hack specified."))) (setq hack-data (copy-alist (cdr hack-data))) (twiddle-insert hack-data (car hack-data) 3) (twiddle-insert hack-data nil 3) (setcar hack-data 'twiddle-frob-compile) (if (interactive-p) (call-interactively 'compile) (apply 'compile compile-args)) ;; Start twiddle after compilation begins, to insure that ;; compilation-in-progress has been set. (apply 'twiddle-start-twiddling hack-data))) (defun twiddle-stop () "Stop twiddling." (interactive) (twiddle-timer-stop twiddle-timer) (setq twiddle-timer nil) (twiddle-unfrob-mode-line-format) (setq twiddle-mode-string nil) (twiddle-mode-line-update)) (defun twiddle-start-twiddling (fn len char &optional delay &rest fn-args) (twiddle-stop) (twiddle-initialize-data len char) (twiddle-frob-mode-line-format) (setq twiddle-timer (apply 'twiddle-timer-start 0 (or delay twiddle-delay) fn fn-args))) (defun twiddle-initialize-data (len init-char) (if (stringp init-char) (setq twiddle-mode-string (copy-sequence init-char)) (setq twiddle-mode-string (make-string len init-char))) (setq twiddle-current-pos 0) (setq twiddle-current-saved-char (aref twiddle-mode-string twiddle-current-pos)) (setq twiddle-direction '1+) (and twiddle-properties (boundp 'add-text-properties) (add-text-properties 0 len twiddle-properties twiddle-mode-string))) ;; Edit global mode-line-format to include the twiddles. ;; "Destructively" modifies the global mode-line-format list, since XEmacs ;; makes the symbol local in every buffer. (defun twiddle-frob-mode-line-format () (let* ((format (default-value 'mode-line-format)) ;; XEmacs 19.14 has "-%-" as the last elt by default. (end (or (member "%-" format) (member "-%-" format)))) (cond (end (setcdr end (cons (car end) (cdr end))) (setcar end 'twiddle-mode-string)) (t (nconc format 'twiddle-mode-string))))) (defun twiddle-unfrob-mode-line-format () (setq-default mode-line-format (delq 'twiddle-mode-string (default-value 'mode-line-format)))) ;; Insert NEW-ELT in the INDEX position of LIST. ;; LIST is destructively modified. (defun twiddle-insert (list new-elt index) (let ((inspoint (nthcdr index list))) (cond ((consp inspoint) (setcdr inspoint (cons (car inspoint) (cdr inspoint))) (setcar inspoint new-elt)) ((> index (length list)) (signal 'error (list "List too short" list new-elt index))) (t (nconc list (cons new-elt nil))))) list) (defun twiddle-read-hack-complete () (completing-read "Twiddle hack: " twiddle-hacks nil t twiddle-default-hack)) ;; Return a function of no arguments which calls fn with args. ;; The args are quoted to avoid double-evaluation: they are evaluated ;; when passed to twiddle-make-thunk, never afterward. (defun twiddle-make-thunk (fn args) (and (symbolp fn) (setq fn (list 'quote fn))) (list 'lambda '() (list 'apply fn (list 'quote args)))) ;; Returns the timer object. (defun twiddle-timer-start (secs repeat function &rest args) (cond (twiddle-xemacs-p ;; The initial timeout must be greater than zero. (and (zerop secs) (setq secs (1+ secs))) ;; The XEmacs timer interface doesn't allow one to specify ;; arguments to the function to call, but we can work around this ;; by wrapping the call in a thunk. (start-itimer "twiddle" (if args (twiddle-make-thunk function args) function) secs repeat)) (t (apply 'run-with-timer secs repeat function args)))) (defun twiddle-timer-stop (timer) (cond (twiddle-xemacs-p (and (itimerp timer) (delete-itimer timer))) ((timerp timer) ;; If this function is called from the timer itself, the timer ;; object isn't present on timer-list so cancel-timer won't do ;; anything useful. To work around this case, disable the timer ;; repeat so it will expire on its own. (timer-set-time timer '(0 0) 0) (cancel-timer timer)))) ;; Subroutine of twiddle-compile. (defun twiddle-frob-compile (&optional twiddle-fn &rest args) (if compilation-in-progress (apply (or twiddle-fn (nth 1 (assoc twiddle-default-hack twiddle-hacks))) args) (twiddle-stop))) (defalias 'twiddle-mode-line-update (if twiddle-xemacs-p 'redraw-modeline 'force-mode-line-update)) ;;; Hacks. (defun twiddle-frob-twiddle () (setq twiddle-current-pos 0) (while (< twiddle-current-pos (length twiddle-mode-string)) (setq twiddle-current-saved-char (memq (aref twiddle-mode-string twiddle-current-pos) twiddle-rotate-chars)) (and twiddle-current-saved-char (aset twiddle-mode-string twiddle-current-pos (or (car (cdr twiddle-current-saved-char)) (car twiddle-rotate-chars)))) (setq twiddle-current-pos (1+ twiddle-current-pos))) (twiddle-mode-line-update)) (defun twiddle-frob-roll () (setq twiddle-temp (or (car (cdr (memq (aref twiddle-mode-string twiddle-current-pos) twiddle-rotate-chars))) (car twiddle-rotate-chars))) (cond ((= twiddle-current-pos 0) (setq twiddle-direction '1+)) ((= twiddle-current-pos (1- (length twiddle-mode-string))) (setq twiddle-direction '1-))) (aset twiddle-mode-string twiddle-current-pos twiddle-current-saved-char) (setq twiddle-current-pos (funcall twiddle-direction twiddle-current-pos)) (setq twiddle-current-saved-char (aref twiddle-mode-string twiddle-current-pos)) (aset twiddle-mode-string twiddle-current-pos twiddle-temp) (twiddle-mode-line-update)) (defun twiddle-frob-asterisk () (aset twiddle-mode-string twiddle-current-pos twiddle-current-saved-char) (cond ((= twiddle-current-pos 0) (setq twiddle-direction '1+)) ((= twiddle-current-pos (1- (length twiddle-mode-string))) (setq twiddle-direction '1-))) (setq twiddle-current-pos (funcall twiddle-direction twiddle-current-pos)) (setq twiddle-current-saved-char (aref twiddle-mode-string twiddle-current-pos)) (aset twiddle-mode-string twiddle-current-pos ?*) (twiddle-mode-line-update)) (provide 'twiddle) ;;; twiddle.el ends here.