branch: master
commit 60205a4dafcf81271304166e2c48fd6107db95ee
Author: Artur Malabarba <bruce.connor...@gmail.com>
Commit: Artur Malabarba <bruce.connor...@gmail.com>

    Rewrite spinners completely.
---
 README.org |   56 +++++++-----
 spinner.el |  303 +++++++++++++++++++++++++++++++++++------------------------
 2 files changed, 214 insertions(+), 145 deletions(-)

diff --git a/README.org b/README.org
index 9eab94f..0d830cc 100644
--- a/README.org
+++ b/README.org
@@ -28,32 +28,44 @@ You can also define your own as a vector of strings (see 
the examples
 in ~spinner-types~).
 
 ** Minor-modes
-Minor-modes can create a spinner (that can be added to the mode’s
-lighter) with ~spinner-make-construct~. They can then start the
-spinner by setting a variable and calling ~spinner-start-timer~.
-Finally, they can stop the spinner (and the timer) by just setting the
-same variable to nil.
+Minor-modes can create a spinner with ~make-spinner~ and then add it
+to their mode-line lighter. They can then start the spinner by setting
+a variable and calling ~spinner-start-timer~. Finally, they can stop
+the spinner (and the timer) by just setting the same variable to nil.
+
+Here’s an example for a minor-mode named ~foo~. Assuming that
+~foo--lighter~ is used as the mode-line lighter, the following code
+will add an *inactive* global spinner to the mode-line.
+#+begin_src emacs-lisp
+(defvar foo--spinner (make-spinner 'rotating-line))
+(defconst foo--lighter
+  '(" foo" (:eval (spinner-print foo--spinner))))
+#+end_src
+
+1. To activate the spinner, just call ~(spinner-start foo--spinner)~.
+   It will show up on the mode-line and start animating.
+2. To get rid of it, call ~(spinner-stop foo--spinner)~. It will then
+   disappear again.
+
+Some minor-modes will need spinners to be buffer-local. To achieve
+that, just make the ~foo--spinner~ variable buffer-local and use the
+third argument of the ~make-spinner~ function. The snippet below is an 
example. 
 
-Here’s an example for a minor-mode named ~foo~.
 #+begin_src emacs-lisp
-(defvar foo--spinner nil)
-(defvar foo--timer nil)
+(defvar-local foo--spinner nil)
 (defconst foo--lighter
-  (list " foo"
-        (spinner-make-construct 'foo--spinner 'foo--timer)))
-
-(defun foo--start-spinning ()
-  "Start foo's spinner."
-  (setq foo--spinner
-        (cdr (assq 'horizontal-bar spinner-types)))
-  (spinner-start-timer 'foo--spinner 'foo--timer))
-
-(defun foo--stop-spinning ()
-  "Stop foo's spinner"
-  (setq foo--spinner nil))
+  '(" foo" (:eval (spinner-print foo--spinner))))
+(defun foo--start-spinner ()
+  "Create and start a spinner on this buffer."
+  (unless foo--spinner
+    (setq foo--spinner (make-spinner 'moon t)))
+  (spinner-start foo--spinner))
 #+end_src
 
-This will use the ~horizontal-bar~ spinner, but you can use anything
-defined in the ~spinner-types~ variable, or even define your own.
+1. To activate the spinner, just call ~(foo--start-spinner)~.
+2. To get rid of it, call ~(spinner-stop foo--spinner)~.
+
+This will use the ~moon~ spinner, but you can use any of the names
+defined in the ~spinner-types~ variable or even define your own.
 
 
diff --git a/spinner.el b/spinner.el
index f81fa29..34427b2 100644
--- a/spinner.el
+++ b/spinner.el
@@ -3,8 +3,7 @@
 ;; Copyright (C) 2015 Free Software Foundation, Inc.
 
 ;; Author: Artur Malabarba <em...@endlessparentheses.com>
-;; Version: 1.2.1
-;; Package-Requires: ((cl-lib "0.5"))
+;; Version: 1.3
 ;; URL: https://github.com/Malabarba/spinner.el
 ;; Keywords: processes mode-line
 
@@ -22,6 +21,7 @@
 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
+;;
 ;; 1 Usage
 ;; ═══════
 ;;
@@ -54,36 +54,51 @@
 ;; 1.2 Minor-modes
 ;; ───────────────
 ;;
-;;   Minor-modes can create a spinner (that can be added to the mode’s
-;;   lighter) with `spinner-make-construct'. They can then start the
-;;   spinner by setting a variable and calling `spinner-start-timer'.
-;;   Finally, they can stop the spinner (and the timer) by just setting the
-;;   same variable to nil.
+;;   Minor-modes can create a spinner with `make-spinner' and then add it
+;;   to their mode-line lighter. They can then start the spinner by setting
+;;   a variable and calling `spinner-start-timer'. Finally, they can stop
+;;   the spinner (and the timer) by just setting the same variable to nil.
+;;
+;;   Here’s an example for a minor-mode named `foo'. Assuming that
+;;   `foo--lighter' is used as the mode-line lighter, the following code
+;;   will add an *inactive* global spinner to the mode-line.
+;;   ┌────
+;;   │ (defvar foo--spinner (make-spinner 'rotating-line))
+;;   │ (defconst foo--lighter
+;;   │   '(" foo" (:eval (spinner-print foo--spinner))))
+;;   └────
+;;
+;;   1. To activate the spinner, just call `(spinner-start foo--spinner)'.
+;;      It will show up on the mode-line and start animating.
+;;   2. To get rid of it, call `(spinner-stop foo--spinner)'. It will then
+;;      disappear again.
+;;
+;;   Some minor-modes will need spinners to be buffer-local. To achieve
+;;   that, just make the `foo--spinner' variable buffer-local and use the
+;;   third argument of the `make-spinner' function. The snippet below is an
+;;   example.
 ;;
-;;   Here’s an example for a minor-mode named `foo'.
 ;;   ┌────
-;;   │ (defvar foo--spinner nil)
-;;   │ (defvar foo--timer nil)
+;;   │ (defvar-local foo--spinner nil)
 ;;   │ (defconst foo--lighter
-;;   │   (list " foo"
-;;   │         (spinner-make-construct 'foo--spinner 'foo--timer)))
-;;   │
-;;   │ (defun foo--start-spinning ()
-;;   │   "Start foo's spinner."
-;;   │   (setq foo--spinner
-;;   │         (cdr (assq 'horizontal-bar spinner-types)))
-;;   │   (spinner-start-timer 'foo--spinner 'foo--timer))
-;;   │
-;;   │ (defun foo--stop-spinning ()
-;;   │   "Stop foo's spinner"
-;;   │   (setq foo--spinner nil))
+;;   │   '(" foo" (:eval (spinner-print foo--spinner))))
+;;   │ (defun foo--start-spinner ()
+;;   │   "Create and start a spinner on this buffer."
+;;   │   (unless foo--spinner
+;;   │     (setq foo--spinner (make-spinner 'moon t)))
+;;   │   (spinner-start foo--spinner))
 ;;   └────
 ;;
-;;   This will use the `horizontal-bar' spinner, but you can use anything
-;;   defined in the `spinner-types' variable, or even define your own.
+;;   1. To activate the spinner, just call `(foo--start-spinner)'.
+;;   2. To get rid of it, call `(spinner-stop foo--spinner)'.
+;;
+;;   This will use the `moon' spinner, but you can use any of the names
+;;   defined in the `spinner-types' variable or even define your own.
+
 
 ;;; Code:
-(require 'cl-lib)
+(eval-when-compile
+  (require 'cl))
 
 (defconst spinner-types
   '((3-line-clock . ["┤" "┘" "┴" "└" "├" "┌" "┬" "┐"])
@@ -109,127 +124,169 @@ Each car is a symbol identifying the spinner, and each 
cdr is a
 vector, the spinner itself.")
 
 (defvar spinner-current nil
-  "Spinner curently being displayed on the mode-line.")
+  "Spinner curently being displayed on the `mode-line-process'.")
 (make-variable-buffer-local 'spinner-current)
 
-(defvar spinner--counter 0
-  "Current frame of the spinner.")
-(make-variable-buffer-local 'spinner--counter)
-
-(defun spinner-make-construct (spinner-var timer-var)
-  "Make a mode-line spinner construct, using symbol SPINNER-VAR.
-SPINNER-VAR is the name of the variable holding the spinner type
-to be used (one of the cdr's in `spinner-types').  To st"
-  `(,spinner-var
-    (:eval (elt ,spinner-var
-                (% spinner--counter
-                   (length ,spinner-var))))
-    (,timer-var
-     (:eval (spinner-stop ',spinner-var ',timer-var)))))
-
 (defconst spinner--mode-line-construct
-  '(spinner-current
-    (" " (:eval (elt spinner-current
-                     (% spinner--counter
-                        (length spinner-current)))))
-    (spinner--timer
-     (:eval (spinner-stop 'spinner-current 'spinner--timer))))
-  "Construct used to display the spinner.")
+  '(:eval (spinner-print spinner-current))
+  "Construct used to display a spinner in `mode-line-process'.")
 (put 'spinner--mode-line-construct 'risky-local-variable t)
 
-(defvar spinner--timer nil
-  "Holds the timer being used on the current buffer.")
-(make-variable-buffer-local 'spinner--timer)
-
-(defvar spinner-frames-per-second 5
+(defvar spinner-frames-per-second 10
   "Default speed at which spinners spin, in frames per second.
-Applications can override this value.")
+Each spinner can override this value.")
 
 
-;;; The main functions
-(defun spinner-start-timer (spinner-var timer-var &optional fps)
-  "Start a spinner timer at FPS frames per second.
-SPINNER-VAR is the name of the variable holding the spinner type,
-and TIMER-VAR is the name of the variable that will be used to
-hold the timer."
-  (let ((old-timer (symbol-value timer-var)))
+;;; The spinner object.
+(defun spinner--type-to-frames (type)
+  "Return a vector of frames corresponding to TYPE.
+The list of possible built-in spinner types is given by the
+`spinner-types' variable, but you can also use your own (see
+below).
+
+If TYPE is nil, the frames of this spinner are given by the first
+element of `spinner-types'.
+If TYPE is a symbol, it specifies an element of `spinner-types'.
+If TYPE is 'random, use a random element of `spinner-types'.
+If TYPE is a list, it should be a list of symbols, and a random
+one is chosen as the spinner type.
+If TYPE is a vector, it should be a vector of strings and these
+are used as the spinner's frames.  This allows you to make your
+own spinner animations."
+  (cond
+   ((vectorp type) type)
+   ((not type) (cdr (car spinner-types)))
+   ((eq type 'random)
+    (cdr (elt spinner-types
+              (random (length spinner-types)))))
+   ((listp type)
+    (cdr (assq (elt type (random (length type)))
+               spinner-types)))
+   ((symbolp type) (cdr (assq type spinner-types)))
+   (t (error "Unknown spinner type: %s" type))))
+
+;;;###autoload
+(defstruct (spinner
+            (:copier nil)
+            (:conc-name spinner--)
+            (:constructor make-spinner (&optional type buffer-local fps)
+                          "Create a spinner of the given TYPE.
+The possible TYPEs are described in `spinner--type-to-frames'.
+
+FPS, if given, is the number of desired frames per second.
+Default is `spinner-frames-per-second'.
+
+If BUFFER-LOCAL is non-nil, the spinner will be automatically
+deactivated if the buffer is killed.  If BUFFER-LOCAL is a
+buffer, use that instead of current buffer.
+
+When started, in order to function properly, the spinner runs a
+timer which periodically calls `force-mode-line-update' in the
+curent buffer.  If BUFFER-LOCAL was set at creation time, then
+`force-mode-line-update' is called in that buffer instead.  When
+the spinner is stopped, the timer is deactivated."))
+  (frames (spinner--type-to-frames type))
+  (counter 0)
+  (fps spinner-frames-per-second)
+  (timer (timer-create) :read-only)
+  (active-p nil)
+  (buffer (when buffer-local
+            (if (bufferp buffer-local)
+                buffer-local
+              (current-buffer)))))
+
+(defun spinner-print (spinner)
+  "Return a string of the current frame of SPINNER.
+If SPINNER is nil, just return nil.
+Designed to be used in the mode-line with:
+    (:eval (spinner-print some-spinner))"
+  (when (and spinner (spinner--active-p spinner))
+    (elt (spinner--frames spinner)
+         (spinner--counter spinner))))
+
+(defun spinner--timer-function (spinner)
+  "Function called to update SPINNER.
+If SPINNER is no longer active, or if its buffer has been killed,
+stop the SPINNER's timer."
+  (let ((buffer (spinner--buffer spinner)))
+    (if (or (not (spinner--active-p spinner))
+            (and buffer (not (buffer-live-p buffer))))
+        (spinner-stop spinner)
+      ;; Increment
+      (callf (lambda (x) (% (1+ x) (length (spinner--frames spinner))))
+          (spinner--counter spinner))
+      ;; Update mode-line.
+      (if (buffer-live-p buffer)
+          (with-current-buffer buffer
+            (force-mode-line-update))
+        (force-mode-line-update)))))
+
+(defun spinner--start-timer (spinner)
+  "Start a SPINNER's timer at FPS frames per second."
+  (let ((old-timer (spinner--timer spinner)))
     (when (timerp old-timer)
       (cancel-timer old-timer))
+
+    (setf (spinner--active-p spinner) t)
     ;; Create timer.
-    (let ((buffer (current-buffer))
-          ;; Create the timer as a lex variable so it can cancel itself.
-          (timer (run-at-time t
-                              (/ 1.0 (or fps spinner-frames-per-second))
-                              #'ignore)))
-      (timer-set-function
-       timer (lambda ()
-               (if (buffer-live-p buffer)
-                   (with-current-buffer buffer
-                     (setq spinner--counter (1+ spinner--counter))
-                     (force-mode-line-update))
-                 (ignore-errors (cancel-timer timer)))))
-      (set timer-var timer)
+    (let* ((repeat (/ 1.0 (or (spinner--fps spinner)
+                              spinner-frames-per-second)))
+           (time (timer-next-integral-multiple-of-time (current-time) repeat))
+           ;; Create the timer as a lex variable so it can cancel itself.
+           (timer (spinner--timer spinner)))
+      (timer-set-time timer time repeat)
+      (timer-set-function timer #'spinner--timer-function (list spinner))
+      (timer-activate timer)
       ;; Return a stopping function.
-      (lambda () (when (buffer-live-p buffer)
-              (with-current-buffer buffer
-                (spinner-stop spinner-var timer-var)))))))
+      (lambda () (spinner-stop spinner)))))
 
+
+;;; The main functions
 ;;;###autoload
-(defun spinner-start (&optional type fps noadd)
-  "Start a mode-line spinner of given TYPE.
-Spinners are buffer local. It is added to the mode-line in the
-buffer where `spinner-start' is called.
+(defun spinner-start (&optional type-or-object fps)
+  "Start a mode-line spinner of given TYPE-OR-OBJECT.
+If TYPE-OR-OBJECT is an object created with `make-spinner',
+simply activate it.  This method is designed for minor modes, so
+they can use the spinner as part of their lighter by doing:
+    '(:eval (spinner-print THE-SPINNER))
+To stop this spinner, call `spinner-stop' on it.
 
-Return value is a function which can be called anywhere to stop
-this spinner.  You can also call `spinner-stop' in the same
-buffer where the spinner was created.
+If TYPE-OR-OBJECT is anything else, a buffer-local spinner is
+created with this type, and it is displayed in the
+`mode-line-process' of the buffer it was created it.  Both
+TYPE-OR-OBJECT and FPS are passed to `make-spinner' (which see).
+To stop this spinner, call `spinner-stop' in the same buffer.
 
-FPS, if given, is the number of desired frames per second.
-Default is `spinner-frames-per-second'.
+Either way, the return value is a function which can be called
+anywhere to stop this spinner.  You can also call `spinner-stop'
+in the same buffer where the spinner was created.
 
-If NOADD is non-nil, the spinner is not added to the mode-line.
-It is then your responsibility to add the symbol
-`spinner--mode-line-construct' somewhere in the mode-line,
-probably as part of a minor-mode lighter.
-
-If TYPE is nil, use the first element of `spinner-types'.
-If TYPE is `random', use a random element of `spinner-types'.
-If it is a symbol, it specifies an element of `spinner-types'.
-If it is a vector, it used as the spinner.
-If it is a list, it should be a list of symbols, and a random one
-is chosen as the spinner type."
-  ;; Choose type.
-  (setq spinner-current
-        (cond
-         ((vectorp type) type)
-         ((not type) (cdr (car spinner-types)))
-         ((eq type 'random)
-          (cdr (elt spinner-types
-                    (random (length spinner-types)))))
-         ((listp type)
-          (cdr (assq (elt type (random (length type)))
-                     spinner-types)))
-         ((symbolp type) (cdr (assq type spinner-types)))
-         (t (error "Unknown spinner type: %s" type))))
-  (setq spinner--counter 0)
-
-  ;; Maybe add to mode-line.
-  (unless (or noadd
-              (memq 'spinner--mode-line-construct mode-line-process))
-    (setq mode-line-process
-          (list (or mode-line-process "")
-                'spinner--mode-line-construct)))
+FPS, if given, is the number of desired frames per second.
+Default is `spinner-frames-per-second'."
+  (unless (spinner-p type-or-object)
+    ;; Choose type.
+    (if (spinner-p spinner-current)
+        (setf (spinner--frames spinner-current)
+              (spinner--type-to-frames type-or-object))
+      (setq spinner-current (make-spinner type-or-object (current-buffer) 
fps)))
+    (setq type-or-object spinner-current)
+    ;; Maybe add to mode-line.
+    (unless (memq 'spinner--mode-line-construct mode-line-process)
+      (setq mode-line-process
+            (list (or mode-line-process "")
+                  'spinner--mode-line-construct))))
 
   ;; Create timer.
-  (spinner-start-timer 'spinner-current 'spinner--timer fps))
+  (when fps (setf (spinner--fps type-or-object) fps))
+  (spinner--start-timer type-or-object))
 
-(defun spinner-stop (&optional spinner-var timer-var)
+(defun spinner-stop (&optional spinner)
   "Stop the current buffer's spinner."
-  (let ((timer (symbol-value timer-var)))
+  (let* ((spinner (or spinner spinner-current))
+         (timer (spinner--timer spinner)))
     (when (timerp timer)
       (cancel-timer timer))
-    (set (or timer-var 'spinner--timer) nil)
-    (set (or spinner-var 'spinner-current) nil)))
+    (setf (spinner--active-p spinner) nil)))
 
 (provide 'spinner)
 

Reply via email to