;; Copyright (C) 2015 Free Software Foundation, Inc.
;; Author: Artur Malabarba <emacs@endlessparentheses.com>
-;; Version: 1.3
+;; Version: 1.7.1
;; URL: https://github.com/Malabarba/spinner.el
;; Keywords: processes mode-line
;; 1.2 Minor-modes
;; ───────────────
;;
-;; Minor-modes can create a spinner with `make-spinner' and then add it
+;; Minor-modes can create a spinner with `spinner-create' 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.
;; `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))
+;; │ (defvar foo--spinner (spinner-create 'rotating-line))
;; │ (defconst foo--lighter
;; │ '(" foo" (:eval (spinner-print foo--spinner))))
;; └────
;;
;; 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
+;; third argument of the `spinner-create' function. The snippet below is an
;; example.
;;
;; ┌────
;; │ (defun foo--start-spinner ()
;; │ "Create and start a spinner on this buffer."
;; │ (unless foo--spinner
-;; │ (setq foo--spinner (make-spinner 'moon t)))
+;; │ (setq foo--spinner (spinner-create 'moon t)))
;; │ (spinner-start foo--spinner))
;; └────
;;
Each car is a symbol identifying the spinner, and each cdr is a
vector, the spinner itself.")
+(defun spinner-make-progress-bar (width &optional char)
+ "Return a vector of strings of the given WIDTH.
+The vector is a valid spinner type and is similar to the
+`progress-bar' spinner, except without the sorrounding brackets.
+CHAR is the character to use for the moving bar (defaults to =)."
+ (let ((whole-string (concat (make-string (1- width) ?\s)
+ (make-string 4 (or char ?=))
+ (make-string width ?\s))))
+ (apply #'vector (mapcar (lambda (n) (substring whole-string n (+ n width)))
+ (number-sequence (+ width 3) 0 -1)))))
+
(defvar spinner-current nil
"Spinner curently being displayed on the `mode-line-process'.")
(make-variable-buffer-local 'spinner-current)
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 `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
((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.
+ (:constructor make-spinner (&optional type buffer-local frames-per-second delay-before-start)))
+ (frames (spinner--type-to-frames type))
+ (counter 0)
+ (fps (or frames-per-second spinner-frames-per-second))
+ (timer (timer-create) :read-only)
+ (active-p nil)
+ (buffer (when buffer-local
+ (if (bufferp buffer-local)
+ buffer-local
+ (current-buffer))))
+ (delay (or delay-before-start 0)))
+
+;;;###autoload
+(defun spinner-create (&optional type buffer-local fps delay)
+ "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.
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)))))
+the spinner is stopped, the timer is deactivated.
+
+DELAY, if given, is the number of seconds to wait after starting
+the spinner before actually displaying it. It is safe to cancel
+the spinner before this time, in which case it won't display at
+all."
+ (make-spinner type buffer-local fps delay))
(defun spinner-print (spinner)
"Return a string of the current frame of SPINNER.
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))))
+ (let ((frame (spinner--counter spinner)))
+ (when (>= frame 0)
+ (elt (spinner--frames spinner) frame)))))
(defun spinner--timer-function (spinner)
"Function called to update SPINNER.
(and buffer (not (buffer-live-p buffer))))
(spinner-stop spinner)
;; Increment
- (callf (lambda (x) (% (1+ x) (length (spinner--frames spinner))))
+ (callf (lambda (x) (if (< x 0)
+ (1+ x)
+ (% (1+ x) (length (spinner--frames spinner)))))
(spinner--counter spinner))
;; Update mode-line.
(if (buffer-live-p buffer)
(force-mode-line-update)))))
(defun spinner--start-timer (spinner)
- "Start a SPINNER's timer at FPS frames per second."
+ "Start a SPINNER's timer."
(let ((old-timer (spinner--timer spinner)))
(when (timerp old-timer)
(cancel-timer old-timer))
(setf (spinner--active-p spinner) t)
+
+ (unless (ignore-errors (> (spinner--fps spinner) 0))
+ (error "A spinner's FPS must be a positive number"))
+ (setf (spinner--counter spinner) (round (- (* (or (spinner--delay spinner) 0)
+ (spinner--fps spinner)))))
;; Create timer.
- (let* ((repeat (/ 1.0 (or (spinner--fps spinner)
- spinner-frames-per-second)))
+ (let* ((repeat (/ 1.0 (spinner--fps spinner)))
(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)))
\f
;;; The main functions
;;;###autoload
-(defun spinner-start (&optional type-or-object fps)
+(defun spinner-start (&optional type-or-object fps delay)
"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))
+ \\='(:eval (spinner-print THE-SPINNER))
To stop this spinner, call `spinner-stop' on it.
If TYPE-OR-OBJECT is anything else, a buffer-local spinner is
in the same buffer where the spinner was created.
FPS, if given, is the number of desired frames per second.
-Default is `spinner-frames-per-second'."
+Default is `spinner-frames-per-second'.
+
+DELAY, if given, is the number of seconds to wait until actually
+displaying the spinner. It is safe to cancel the spinner before
+this time, in which case it won't display at all."
(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)))
+ (setf (spinner--frames spinner-current) (spinner--type-to-frames type-or-object))
+ (setq spinner-current (make-spinner type-or-object (current-buffer) fps delay)))
(setq type-or-object spinner-current)
;; Maybe add to mode-line.
(unless (memq 'spinner--mode-line-construct mode-line-process)
;; Create timer.
(when fps (setf (spinner--fps type-or-object) fps))
+ (when delay (setf (spinner--delay type-or-object) delay))
(spinner--start-timer type-or-object))
(defun spinner-start-print (spinner)
(spinner-print spinner))
(defun spinner-stop (&optional spinner)
- "Stop the current buffer's spinner."
- (let* ((spinner (or spinner spinner-current))
- (timer (spinner--timer spinner)))
- (when (timerp timer)
- (cancel-timer timer))
- (setf (spinner--active-p spinner) nil)))
+ "Stop SPINNER, defaulting to the current buffer's spinner.
+It is always safe to call this function, even if there is no
+active spinner."
+ (let ((spinner (or spinner spinner-current)))
+ (when (spinner-p spinner)
+ (let ((timer (spinner--timer spinner)))
+ (when (timerp timer)
+ (cancel-timer timer)))
+ (setf (spinner--active-p spinner) nil)
+ (force-mode-line-update))))
(provide 'spinner)